From 09354e8519d33faf702292aedc5b92834bf83816 Mon Sep 17 00:00:00 2001 From: Jorge Aguado Recio Date: Tue, 26 May 2026 13:06:26 +0200 Subject: [PATCH 1/3] fix: verify peer in ssl certificate --- .../android/ui/dialog/SslUntrustedCertDialog.java | 14 ++++++++++++-- .../android/lib/common/http/HttpClient.java | 7 ++++--- .../common/network/AdvancedX509TrustManager.java | 11 +++++++++-- .../common/operations/RemoteOperationResult.java | 14 ++++++++++++-- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java b/owncloudApp/src/main/java/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java index 97dffec8f28..946484c605d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/dialog/SslUntrustedCertDialog.java @@ -4,7 +4,9 @@ * @author masensio * @author David A. Velasco * @author Christian Schabesberger - * Copyright (C) 2020 ownCloud GmbH. + * + * Copyright (C) 2026 ownCloud GmbH. + * *

* This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, @@ -82,7 +84,7 @@ public static SslUntrustedCertDialog newInstanceForFullSslError(CertificateCombi throw new IllegalArgumentException("Trying to create instance with parameter sslException == null"); } SslUntrustedCertDialog dialog = new SslUntrustedCertDialog(); - dialog.m509Certificate = sslException.getServerCertificate(); + dialog.m509Certificate = sslException.getSslPeerUnverifiedException() == null ? sslException.getServerCertificate() : null; dialog.mErrorViewAdapter = new CertificateCombinedExceptionViewAdapter(sslException); dialog.mCertificateViewAdapter = new X509CertificateViewAdapter(sslException.getServerCertificate()); return dialog; @@ -144,6 +146,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa Button cancel = mView.findViewById(R.id.btnCancel); cancel.setOnClickListener(new OnCertificateNotTrusted()); + if (m509Certificate == null) { + ok.setText(android.R.string.ok); + mView.findViewById(R.id.question).setVisibility(View.GONE); + cancel.setVisibility(View.GONE); + } + Button details = mView.findViewById(R.id.details_btn); details.setOnClickListener(new OnClickListener() { @@ -219,6 +227,8 @@ public void onClick(View v) { ((OnSslUntrustedCertListener) activity).onFailedSavingCertificate(); Timber.e(e, "Server certificate could not be saved in the known-servers trust store "); } + } else if (mHandler == null) { + ((OnSslUntrustedCertListener) getActivity()).onCancelCertificate(); } } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java index e141320fda2..dbbf9b74945 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java @@ -1,5 +1,7 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2020 ownCloud GmbH. +/** + * ownCloud Android Library is available under MIT license + * + * Copyright (C) 2026 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -125,7 +127,6 @@ private OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X50 .connectTimeout(HttpConstants.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) .followRedirects(false) .sslSocketFactory(sslSocketFactory, trustManager) - .hostnameVerifier((asdf, usdf) -> true) .cookieJar(cookieJar) .build(); } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/AdvancedX509TrustManager.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/AdvancedX509TrustManager.java index b84c03de766..9d8dd786d31 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/AdvancedX509TrustManager.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/AdvancedX509TrustManager.java @@ -1,5 +1,7 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2016 ownCloud GmbH. +/** + * ownCloud Android Library is available under MIT license + * + * Copyright (C) 2026 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -84,11 +86,16 @@ public void checkClientTrusted(X509Certificate[] certificates, String authType) mStandardTrustManager.checkClientTrusted(certificates, authType); } + public static final ThreadLocal sLastCert = new ThreadLocal<>(); + /** * @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], * String authType) */ public void checkServerTrusted(X509Certificate[] certificates, String authType) { + if (certificates != null && certificates.length > 0) { + sLastCert.set(certificates[0]); + } if (!isKnownServer(certificates[0])) { CertificateCombinedException result = new CertificateCombinedException(certificates[0]); try { diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java index b975417ea86..5fdeeabe913 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/operations/RemoteOperationResult.java @@ -1,5 +1,7 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2022 ownCloud GmbH. +/** + * ownCloud Android Library is available under MIT license + * + * Copyright (C) 2026 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,6 +34,7 @@ import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.http.HttpConstants; import com.owncloud.android.lib.common.http.methods.HttpBaseMethod; +import com.owncloud.android.lib.common.network.AdvancedX509TrustManager; import com.owncloud.android.lib.common.network.CertificateCombinedException; import okhttp3.Headers; import org.apache.commons.lang3.exception.ExceptionUtils; @@ -50,6 +53,7 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -148,6 +152,12 @@ public RemoteOperationResult(Exception e) { } else if (e instanceof SSLException || e instanceof RuntimeException) { if (e instanceof SSLPeerUnverifiedException) { + X509Certificate lastCert = AdvancedX509TrustManager.sLastCert.get(); + AdvancedX509TrustManager.sLastCert.remove(); + CertificateCombinedException sslPeerUnverifiedException = new CertificateCombinedException(lastCert); + sslPeerUnverifiedException.setSslPeerUnverifiedException((SSLPeerUnverifiedException) e); + sslPeerUnverifiedException.initCause(e); + mException = sslPeerUnverifiedException; mCode = ResultCode.SSL_RECOVERABLE_PEER_UNVERIFIED; } else { CertificateCombinedException se = getCertificateCombinedException(e); From 9a15056ed6443d2a78c37ee5656da5253f456be8 Mon Sep 17 00:00:00 2001 From: Jorge Aguado Recio Date: Tue, 26 May 2026 13:09:47 +0200 Subject: [PATCH 2/3] fix: tls trust known cert hostname --- .../android/lib/common/http/HttpClient.java | 2 + .../network/KnownServersHostnameVerifier.java | 62 +++++++++++++++++++ .../lib/common/network/NetworkUtils.java | 18 +++++- 3 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/KnownServersHostnameVerifier.java diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java index dbbf9b74945..baadc1ec9fd 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/http/HttpClient.java @@ -30,6 +30,7 @@ import com.owncloud.android.lib.common.http.logging.LogInterceptor; import com.owncloud.android.lib.common.network.AdvancedX509TrustManager; +import com.owncloud.android.lib.common.network.KnownServersHostnameVerifier; import com.owncloud.android.lib.common.network.NetworkUtils; import okhttp3.Cookie; import okhttp3.CookieJar; @@ -127,6 +128,7 @@ private OkHttpClient buildNewOkHttpClient(SSLSocketFactory sslSocketFactory, X50 .connectTimeout(HttpConstants.DEFAULT_CONNECTION_TIMEOUT, TimeUnit.MILLISECONDS) .followRedirects(false) .sslSocketFactory(sslSocketFactory, trustManager) + .hostnameVerifier(new KnownServersHostnameVerifier(mContext)) .cookieJar(cookieJar) .build(); } diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/KnownServersHostnameVerifier.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/KnownServersHostnameVerifier.java new file mode 100644 index 00000000000..49ad16fc3d4 --- /dev/null +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/KnownServersHostnameVerifier.java @@ -0,0 +1,62 @@ +/** + * ownCloud Android client application + * + * Copyright (C) 2026 ownCloud GmbH. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * 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, see . + */ + +package com.owncloud.android.lib.common.network; + +import android.content.Context; +import okhttp3.internal.tls.OkHostnameVerifier; +import timber.log.Timber; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +public class KnownServersHostnameVerifier implements HostnameVerifier { + + private final Context mContext; + private final HostnameVerifier mDelegate; + + public KnownServersHostnameVerifier(Context context) { + this(context, OkHostnameVerifier.INSTANCE); + } + + KnownServersHostnameVerifier(Context context, HostnameVerifier delegate) { + if (context == null) { + throw new IllegalArgumentException("Context may not be NULL!"); + } + mContext = context.getApplicationContext() != null ? context.getApplicationContext() : context; + mDelegate = delegate; + } + + @Override + public boolean verify(String hostname, SSLSession session) { + if (mDelegate.verify(hostname, session)) { + return true; + } + try { + Certificate[] peerCerts = session.getPeerCertificates(); + if (peerCerts.length > 0 && peerCerts[0] instanceof X509Certificate) { + return NetworkUtils.isCertInKnownServersStore(peerCerts[0], mContext); + } + } catch (SSLPeerUnverifiedException e) { + Timber.d(e, "No peer certificates during hostname verification for %s", hostname); + } + return false; + } +} diff --git a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/NetworkUtils.java b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/NetworkUtils.java index f6013cb8498..541bce0d033 100644 --- a/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/NetworkUtils.java +++ b/owncloudComLibrary/src/main/java/com/owncloud/android/lib/common/network/NetworkUtils.java @@ -1,5 +1,7 @@ -/* ownCloud Android Library is available under MIT license - * Copyright (C) 2016 ownCloud GmbH. +/** + * ownCloud Android Library is available under MIT license + * + * Copyright (C) 2026 ownCloud GmbH. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -94,4 +96,16 @@ public static void addCertToKnownServersStore(Certificate cert, Context context) } } + public static boolean isCertInKnownServersStore(Certificate cert, Context context) { + if (cert == null || context == null) { + return false; + } + try { + return getKnownServersStore(context).getCertificateAlias(cert) != null; + } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) { + Timber.e(e, "Fail while checking certificate in the known-servers store"); + return false; + } + } + } From 3b3a2755fbdcdcc707be185e292cdc617efd9dcd Mon Sep 17 00:00:00 2001 From: Jorge Aguado Recio Date: Tue, 26 May 2026 14:00:16 +0200 Subject: [PATCH 3/3] chore: add calens file --- changelog/unreleased/4861 | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog/unreleased/4861 diff --git a/changelog/unreleased/4861 b/changelog/unreleased/4861 new file mode 100644 index 00000000000..8bd429bf3b9 --- /dev/null +++ b/changelog/unreleased/4861 @@ -0,0 +1,6 @@ +Security: SSL certificate verification and trusted host handling + +SSL certificate verification flow has been improved by enhancing +host validation and trusted certificate handling. + +https://github.com/owncloud/android/pull/4861