Support for client certificates
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.
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
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 ?
Okay thanks! In some way this also enhances security. ;)
@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.
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.
Any news on this ? This is a very important enhancement !
@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 yes I do. Send me the required cert + url, server and pass to [email protected] :)
@mario Did you have a chance yet to look into this ?
@mario Did the second certificate and the talk app help you in making progress on this issue ? Anything I can do to support ?
@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.
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 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 ^_^
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 can you file a bug here, preferably with stacktrace?
https://github.com/nextcloud/talk-android
@mbrescia is there any chance that you upload your whole example, because i tried your workaround but without success. best regards
@proton2b Sure, I'll start a branch as soon as I can
@mbrescia ok great, thanks in advance!
Has there been any update on this? It would be an excellent addition to security
Has there been any update on this? It would be an excellent addition to security
Unfortunately not.
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.
+1
I would also like to see client certificates supported in the nextcloud app.
+1
Instead of adding +1 comments to this ticket please use the thumb up emoji at the top. Thanks.
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 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.
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).
@stephanritscher seems to have made a working patch. Why hasn't it been integrated yet ?
+1 vote for client cert auth from me! please!