Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔑 Support for client certificates #603

Closed
mlsxlist opened this issue Jan 29, 2017 · 53 comments
Closed

🔑 Support for client certificates #603

mlsxlist opened this issue Jan 29, 2017 · 53 comments
Assignees
Labels
connectivity DNS, TLS, proxies, network connection, etc. related matter enhancement feature: authentication Authentication or accounts related security 🍀 2024-Spring

Comments

@mlsxlist
Copy link

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.

@ghost
Copy link

ghost commented Jan 30, 2017

Hello mlsxlist,

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

owncloud/android#163

Best wishes
riesnerd

@tobiasKaminsky
Copy link
Member

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 ?

@ghost
Copy link

ghost commented Feb 1, 2017

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

@mario
Copy link
Contributor

mario commented Feb 1, 2017

@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.

@seanlynch
Copy link

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.

@lucacarangelo
Copy link

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

@AndreasMettlen
Copy link

@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)

@mario
Copy link
Contributor

mario commented Feb 25, 2018

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

@AndreasMettlen
Copy link

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

@AndreasMettlen
Copy link

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

@mario
Copy link
Contributor

mario commented Apr 24, 2018

@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.

@mbrescia
Copy link

mbrescia commented May 15, 2018

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

@mario
Copy link
Contributor

mario commented May 15, 2018

@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 ^_^

@ClCfe
Copy link

ClCfe commented Jun 7, 2018

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

@mario mario closed this as completed Jun 7, 2018
@mario
Copy link
Contributor

mario commented Jun 7, 2018

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

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

@proton2b
Copy link

proton2b commented Jul 5, 2018

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

@mbrescia
Copy link

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

@proton2b
Copy link

@mbrescia
ok great, thanks in advance!

@carlos-sarmiento

This comment has been minimized.

@proton2b

This comment has been minimized.

@mirko

This comment has been minimized.

@steffenfritz

This comment has been minimized.

@ben423423n32j14e

This comment has been minimized.

@TJB-99

This comment has been minimized.

stephanritscher added a commit to stephanritscher/nextcloud-android that referenced this issue Jun 29, 2022
This change adds support for configurations where the webserver requests a client certificate, e.g. via nginx configuration options ssl_client_certificate and ssl_verify_client.
The client certificate handling is done via InteractiveKeyManager which prompts for a client certificate from a file or the devices keystore.

This change is NOT about client certificate authentication to the nextcloud server instance. The regular authentication mechanisms will be used as soon as the communication on TLS level is established.

The change addresses ticket nextcloud#603, while not being a generic solution IMO.
@avocadio
Copy link

avocadio commented Sep 5, 2022

@stephanritscher - Thanks it worked - well worth the APK compilation time!!!
There is one issue which may be due to my setup (mTLS internet facing/TLS internal) I just had to register internally, it was not working externally.

The selection UI should really not been seen as a blocker if we consider the security value brought by this feature.
Thanks again you made my day!

@mario Is there anyway to bring this to at least the 'dev' version?

@tobiasKaminsky
Copy link
Member

Is there anyway to bring this to at least the 'dev' version?

If you want to help us integrating this feature, then please create a PR both on this app and on library, so we can review and discuss it.

@peshovec
Copy link

@stephanritscher - Thanks it worked - well worth the APK compilation time!!! There is one issue which may be due to my setup (mTLS internet facing/TLS internal) I just had to register internally, it was not working externally.

The selection UI should really not been seen as a blocker if we consider the security value brought by this feature. Thanks again you made my day!

@mario Is there anyway to bring this to at least the 'dev' version?

Initial registration fails to me too (after workaround and registering, connects fine). was able to investigate a little bit:
it is faulting here:
log from the nginx:
ocs/v2.php/cloud/user?format=json HTTP/2.0" 444 0 "-" "Mozilla/5.0 (Android) Nextcloud-android/20220921"

tried to dig further (logcat) and this seems as possible code place:

android-library/library/src/main/java/com/owncloud/android/lib/resources/user/GetUserInfoRemoteOperation.java

public RemoteOperationResult run(NextcloudClient client) {
RemoteOperationResult result;
int status;
GetMethod get = null;

    String url = client.getBaseUri() + OCS_ROUTE_SELF;

    // get the user
    try {

        get = new GetMethod(url, true);
        HashMap<String, String> map = new HashMap<>();
        map.put("format", "json");

        get.setQueryString(map);
        status = client.execute(get);

@Torqu3Wr3nch
Copy link

Looks like we have multiple commits from Stephan (thank you, by the way).

@stephanritscher can you please issue a PR so we can get this implemented in the official Nextcloud Android client? It sounds like @tobiasKaminsky is onboard. I can't think of a more vitally significant piece of development for the Nextcloud community. Security of a file storage/synchronization platform is always highest priority.

Thank you again for your work.

stephanritscher added a commit to stephanritscher/nextcloud-android that referenced this issue Nov 26, 2022
This change adds support for configurations where the webserver requests a client certificate, e.g. via nginx configuration options ssl_client_certificate and ssl_verify_client.
The client certificate handling is done via InteractiveKeyManager which prompts for a client certificate from a file or the devices keystore.

This change is NOT about client certificate authentication to the nextcloud server instance. The regular authentication mechanisms will be used as soon as the communication on TLS level is established.

The change addresses ticket nextcloud#603, while not being a generic solution IMO.
@stephanritscher
Copy link
Contributor

@stephanritscher can you please issue a PR so we can get this implemented in the official Nextcloud Android client?

I rebased my repos and created PRs nextcloud/android-library#1005 and #11099 to start the discussion. I guess it might need some changes, but let's see.

Sorry, but I might not be able to respond/incorporate changes quickly due too lack of time.

@avocadio
Copy link

@AlvaroBrey in #11099 it was mentioned that there was an internal consensus,
would you mind describing a bit more? I have a bit of spare time over the coming break and would like to understand what would be accepted?

Just for reference major open source application in the IoT world has used similar method:
home-assistant/android#2023
Mattermost support this due to the use of react.
Not to forget Talk (and that is working great!)

@AlvaroBrey
Copy link
Member

@AlvaroBrey in #11099 it was mentioned that there was an internal consensus,
would you mind describing a bit more? I have a bit of spare time over the coming break and would like to understand what would be accepted?

As outlined in my original comment in 11099, we're just not comfortable using a third-party library for this purpose. It should all (logic, state and UI) be contained in our app or our library. Additionally it would be great if the solution could somewhat be aligned to the one Talk already uses, in hopes of a future reuse between apps.

If you want to contribute but are unsure of the approach, feel free to open a draft PR and ping me, so I can guide you during the development and not just at the end of it :)

@joshtrichards joshtrichards added feature: authentication Authentication or accounts related security connectivity DNS, TLS, proxies, network connection, etc. related matter labels Oct 17, 2023
@bestrocker221
Copy link

Any news on the topic? Still not implemented but would be awesome to have!

@tobiasKaminsky tobiasKaminsky self-assigned this Jan 15, 2024
@AndyScherzinger AndyScherzinger changed the title Support for client certificates 🔑 Support for client certificates Jan 18, 2024
@tobiasKaminsky
Copy link
Member

Done with
#12408
nextcloud/android-library#1308

Huge thanks to @Elv1zz who did 99,95% of the work 🎉

@bestrocker221
Copy link

Any plan on adding it to release?

@AndyScherzinger AndyScherzinger added this to the Nextcloud App 3.29.0 milestone Mar 17, 2024
@AndyScherzinger
Copy link
Member

It's planned to ship it with the next feature release 3.29 - scheduled for 24th April

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
connectivity DNS, TLS, proxies, network connection, etc. related matter enhancement feature: authentication Authentication or accounts related security 🍀 2024-Spring
Projects
Archived in project
Development

No branches or pull requests