Merge pull request #13099 from nextcloud/backport/13076/stable-3.29

[stable-3.29] Bugfix NPE & Convert to Kotlin SSL Untrusted Cert Dialog
This commit is contained in:
Tobias Kaminsky 2024-06-14 11:04:11 +02:00 коммит произвёл GitHub
Родитель 050c1bffa8 ee7c6e7874
Коммит ed622c99db
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
6 изменённых файлов: 226 добавлений и 224 удалений

Просмотреть файл

@ -16,18 +16,20 @@ import com.owncloud.android.databinding.SslUntrustedCertLayoutBinding;
import com.owncloud.android.lib.common.network.CertificateCombinedException;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
import androidx.annotation.NonNull;
public class CertificateCombinedExceptionViewAdapter implements SslUntrustedCertDialog.ErrorViewAdapter {
//private final static String TAG = CertificateCombinedExceptionViewAdapter.class.getSimpleName();
private CertificateCombinedException mSslException;
private final CertificateCombinedException mSslException;
public CertificateCombinedExceptionViewAdapter(CertificateCombinedException sslException) {
mSslException = sslException;
}
@Override
public void updateErrorView(SslUntrustedCertLayoutBinding binding) {
public void updateErrorView(@NonNull SslUntrustedCertLayoutBinding binding) {
/// clean
binding.reasonNoInfoAboutError.setVisibility(View.GONE);

Просмотреть файл

@ -24,7 +24,7 @@ public class SslCertificateViewAdapter implements SslUntrustedCertDialog.Certifi
//private final static String TAG = SslCertificateViewAdapter.class.getSimpleName();
private SslCertificate mCertificate;
private final SslCertificate mCertificate;
/**
* Constructor
@ -36,7 +36,7 @@ public class SslCertificateViewAdapter implements SslUntrustedCertDialog.Certifi
}
@Override
public void updateCertificateView(SslUntrustedCertLayoutBinding binding) {
public void updateCertificateView(@NonNull SslUntrustedCertLayoutBinding binding) {
if (mCertificate != null) {
binding.nullCert.setVisibility(View.GONE);
showSubject(mCertificate.getIssuedTo(), binding);

Просмотреть файл

@ -14,6 +14,8 @@ import android.view.View;
import com.owncloud.android.databinding.SslUntrustedCertLayoutBinding;
import com.owncloud.android.ui.dialog.SslUntrustedCertDialog;
import androidx.annotation.NonNull;
/**
* Dialog to show an Untrusted Certificate
*/
@ -21,14 +23,14 @@ public class SslErrorViewAdapter implements SslUntrustedCertDialog.ErrorViewAdap
//private final static String TAG = SslErrorViewAdapter.class.getSimpleName();
private SslError mSslError;
private final SslError mSslError;
public SslErrorViewAdapter(SslError sslError) {
mSslError = sslError;
}
@Override
public void updateErrorView(SslUntrustedCertLayoutBinding binding) {
public void updateErrorView(@NonNull SslUntrustedCertLayoutBinding binding) {
/// clean
binding.reasonNoInfoAboutError.setVisibility(View.GONE);

Просмотреть файл

@ -37,7 +37,7 @@ import androidx.annotation.NonNull;
*/
public class X509CertificateViewAdapter implements SslUntrustedCertDialog.CertificateViewAdapter {
private X509Certificate mCertificate;
private final X509Certificate mCertificate;
private static final String TAG = X509CertificateViewAdapter.class.getSimpleName();
@ -46,7 +46,7 @@ public class X509CertificateViewAdapter implements SslUntrustedCertDialog.Certif
}
@Override
public void updateCertificateView(SslUntrustedCertLayoutBinding binding) {
public void updateCertificateView(@NonNull SslUntrustedCertLayoutBinding binding) {
if (mCertificate != null) {
binding.nullCert.setVisibility(View.GONE);
showSubject(mCertificate.getSubjectX500Principal(), binding);
@ -97,7 +97,7 @@ public class X509CertificateViewAdapter implements SslUntrustedCertDialog.Certif
private String getDigestHexBytesWithColonsAndNewLines(Context context, final String digestType, final byte [] cert) {
final byte[] rawDigest;
final String newLine = System.getProperty("line.separator");
final String newLine = System.lineSeparator();
rawDigest = getDigest(digestType, cert);

Просмотреть файл

@ -1,215 +0,0 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2018-2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2020 Stefan Niedermann <info@niedermann.it>
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2014 David A. Velasco <dvelasco@solidgear.es>
* SPDX-FileCopyrightText: 2014 María Asensio Valverde <masensio@solidgear.es>
* SPDX-License-Identifier: GPL-2.0-only AND AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog;
import android.app.Activity;
import android.app.Dialog;
import android.net.http.SslError;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.webkit.SslErrorHandler;
import android.widget.Button;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.nextcloud.client.di.Injectable;
import com.owncloud.android.R;
import com.owncloud.android.databinding.SslUntrustedCertLayoutBinding;
import com.owncloud.android.lib.common.network.CertificateCombinedException;
import com.owncloud.android.lib.common.network.NetworkUtils;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.ui.adapter.CertificateCombinedExceptionViewAdapter;
import com.owncloud.android.ui.adapter.SslCertificateViewAdapter;
import com.owncloud.android.ui.adapter.SslErrorViewAdapter;
import com.owncloud.android.ui.adapter.X509CertificateViewAdapter;
import com.owncloud.android.utils.theme.ViewThemeUtils;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
/**
* Dialog to show information about an untrusted certificate and allow the user to decide trust on it or not.
* Abstract implementation of common functionality for different dialogs that get the information about the error and
* the certificate from different classes.
*/
public class SslUntrustedCertDialog extends DialogFragment implements Injectable {
private final static String TAG = SslUntrustedCertDialog.class.getSimpleName();
@Inject ViewThemeUtils viewThemeUtils;
protected SslUntrustedCertLayoutBinding binding;
protected SslErrorHandler mHandler;
protected X509Certificate m509Certificate;
private ErrorViewAdapter mErrorViewAdapter;
private CertificateViewAdapter mCertificateViewAdapter;
public static SslUntrustedCertDialog newInstanceForEmptySslError(SslError error, SslErrorHandler handler) {
if (error == null) {
throw new IllegalArgumentException("Trying to create instance with parameter error == null");
}
if (handler == null) {
throw new IllegalArgumentException("Trying to create instance with parameter handler == null");
}
SslUntrustedCertDialog dialog = new SslUntrustedCertDialog();
dialog.mHandler = handler;
dialog.mErrorViewAdapter = new SslErrorViewAdapter(error);
dialog.mCertificateViewAdapter = new SslCertificateViewAdapter(error.getCertificate());
return dialog;
}
public static SslUntrustedCertDialog newInstanceForFullSslError(CertificateCombinedException sslException) {
if (sslException == null) {
throw new IllegalArgumentException("Trying to create instance with parameter sslException == null");
}
SslUntrustedCertDialog dialog = new SslUntrustedCertDialog();
dialog.m509Certificate = sslException.getServerCertificate();
dialog.mErrorViewAdapter = new CertificateCombinedExceptionViewAdapter(sslException);
dialog.mCertificateViewAdapter = new X509CertificateViewAdapter(sslException.getServerCertificate());
return dialog;
}
public static SslUntrustedCertDialog newInstanceForFullSslError(X509Certificate cert, SslError error, SslErrorHandler handler) {
if (cert == null) {
throw new IllegalArgumentException("Trying to create instance with parameter cert == null");
}
if (error == null) {
throw new IllegalArgumentException("Trying to create instance with parameter error == null");
}
if (handler == null) {
throw new IllegalArgumentException("Trying to create instance with parameter handler == null");
}
SslUntrustedCertDialog dialog = new SslUntrustedCertDialog();
dialog.m509Certificate = cert;
dialog.mHandler = handler;
dialog.mErrorViewAdapter = new SslErrorViewAdapter(error);
dialog.mCertificateViewAdapter = new X509CertificateViewAdapter(cert);
return dialog;
}
@Override
public void onAttach(@NonNull Activity activity) {
Log_OC.d(TAG, "onAttach");
super.onAttach(activity);
if (!(activity instanceof OnSslUntrustedCertListener)) {
throw new IllegalArgumentException("The host activity must implement " + OnSslUntrustedCertListener.class.getCanonicalName());
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
Log_OC.d(TAG, "onCreate, savedInstanceState is " + savedInstanceState);
super.onCreate(savedInstanceState);
setRetainInstance(true); // force to keep the state of the fragment on configuration changes (such as device rotations)
setCancelable(false);
binding = null;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Log_OC.d(TAG, "onCreateDialog, savedInstanceState is " + savedInstanceState);
binding = SslUntrustedCertLayoutBinding.inflate(getLayoutInflater(), null, false);
binding.detailsScroll.setVisibility(View.GONE);
mErrorViewAdapter.updateErrorView(binding);
binding.ok.setOnClickListener(new OnCertificateTrusted());
binding.cancel.setOnClickListener(new OnCertificateNotTrusted());
binding.detailsBtn.setOnClickListener(v -> {
if (binding.detailsScroll.getVisibility() == View.VISIBLE) {
binding.detailsScroll.setVisibility(View.GONE);
((Button) v).setText(R.string.ssl_validator_btn_details_see);
} else {
binding.detailsScroll.setVisibility(View.VISIBLE);
((Button) v).setText(R.string.ssl_validator_btn_details_hide);
mCertificateViewAdapter.updateCertificateView(binding);
}
});
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(binding.getRoot().getContext());
builder.setView(binding.getRoot());
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(binding.getRoot().getContext(), builder);
final Dialog dialog = builder.create();
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
@Override
public void onDestroyView() {
Log_OC.d(TAG, "onDestroyView");
if (getDialog() != null && getRetainInstance()) {
getDialog().setDismissMessage(null);
}
super.onDestroyView();
}
private class OnCertificateNotTrusted implements OnClickListener {
@Override
public void onClick(View v) {
getDialog().cancel();
if (mHandler != null) {
mHandler.cancel();
}
}
}
private class OnCertificateTrusted implements OnClickListener {
@Override
public void onClick(View v) {
dismiss();
if (mHandler != null) {
mHandler.proceed();
}
if (m509Certificate != null) {
Activity activity = getActivity();
try {
NetworkUtils.addCertToKnownServersStore(m509Certificate, activity); // TODO make this asynchronously, it can take some time
((OnSslUntrustedCertListener)activity).onSavedCertificate();
} catch (GeneralSecurityException | IOException e) {
((OnSslUntrustedCertListener)activity).onFailedSavingCertificate();
Log_OC.e(TAG, "Server certificate could not be saved in the known-servers trust store ", e);
}
}
}
}
public interface OnSslUntrustedCertListener {
void onSavedCertificate();
void onFailedSavingCertificate();
}
public interface ErrorViewAdapter {
void updateErrorView(SslUntrustedCertLayoutBinding binding);
}
public interface CertificateViewAdapter {
void updateCertificateView(SslUntrustedCertLayoutBinding binding);
}
}

Просмотреть файл

@ -0,0 +1,213 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-FileCopyrightText: 2018-2022 Andy Scherzinger <info@andy-scherzinger.de>
* SPDX-FileCopyrightText: 2020 Stefan Niedermann <info@niedermann.it>
* SPDX-FileCopyrightText: 2018 Tobias Kaminsky <tobias@kaminsky.me>
* SPDX-FileCopyrightText: 2015 ownCloud Inc.
* SPDX-FileCopyrightText: 2014 David A. Velasco <dvelasco@solidgear.es>
* SPDX-FileCopyrightText: 2014 María Asensio Valverde <masensio@solidgear.es>
* SPDX-License-Identifier: GPL-2.0-only AND AGPL-3.0-or-later
*/
package com.owncloud.android.ui.dialog
import android.app.Dialog
import android.content.Context
import android.net.http.SslError
import android.os.Bundle
import android.view.View
import android.view.Window
import android.webkit.SslErrorHandler
import android.widget.Button
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nextcloud.client.di.Injectable
import com.owncloud.android.R
import com.owncloud.android.databinding.SslUntrustedCertLayoutBinding
import com.owncloud.android.lib.common.network.CertificateCombinedException
import com.owncloud.android.lib.common.network.NetworkUtils
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.ui.adapter.CertificateCombinedExceptionViewAdapter
import com.owncloud.android.ui.adapter.SslCertificateViewAdapter
import com.owncloud.android.ui.adapter.SslErrorViewAdapter
import com.owncloud.android.ui.adapter.X509CertificateViewAdapter
import com.owncloud.android.utils.theme.ViewThemeUtils
import java.io.IOException
import java.security.GeneralSecurityException
import java.security.cert.X509Certificate
import javax.inject.Inject
/**
* Dialog to show information about an untrusted certificate and allow the user to decide trust on it or not.
* Abstract implementation of common functionality for different dialogs that get the information about the error and
* the certificate from different classes.
*/
open class SslUntrustedCertDialog : DialogFragment(), Injectable {
@JvmField
@Inject
var viewThemeUtils: ViewThemeUtils? = null
protected var binding: SslUntrustedCertLayoutBinding? = null
protected var sslErrorHandler: SslErrorHandler? = null
protected var x509Certificate: X509Certificate? = null
private var errorViewAdapter: ErrorViewAdapter? = null
private var certificateViewAdapter: CertificateViewAdapter? = null
override fun onAttach(context: Context) {
Log_OC.d(TAG, "onAttach")
super.onAttach(context)
require(activity is OnSslUntrustedCertListener) {
"The host activity must implement " + OnSslUntrustedCertListener::class.java.canonicalName
}
}
override fun onCreate(savedInstanceState: Bundle?) {
Log_OC.d(TAG, "onCreate, savedInstanceState is $savedInstanceState")
super.onCreate(savedInstanceState)
// force to keep the state of the fragment on configuration changes (such as device rotations)
retainInstance = true
isCancelable = false
binding = null
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Log_OC.d(TAG, "onCreateDialog, savedInstanceState is $savedInstanceState")
val layoutBinding = SslUntrustedCertLayoutBinding.inflate(layoutInflater, null, false)
this.binding = layoutBinding
layoutBinding.run {
detailsScroll.visibility = View.GONE
errorViewAdapter?.updateErrorView(layoutBinding)
ok.setOnClickListener(OnCertificateTrusted())
cancel.setOnClickListener(OnCertificateNotTrusted())
detailsBtn.setOnClickListener { v: View ->
if (detailsScroll.visibility == View.VISIBLE) {
detailsScroll.visibility = View.GONE
(v as Button).setText(R.string.ssl_validator_btn_details_see)
} else {
detailsScroll.visibility = View.VISIBLE
(v as Button).setText(R.string.ssl_validator_btn_details_hide)
certificateViewAdapter?.updateCertificateView(layoutBinding)
}
}
}
val builder = MaterialAlertDialogBuilder(requireContext()).apply {
setView(layoutBinding.getRoot())
}
viewThemeUtils?.dialog?.colorMaterialAlertDialogBackground(requireContext(), builder)
return builder.create().apply {
requestWindowFeature(Window.FEATURE_NO_TITLE)
}
}
override fun onDestroyView() {
Log_OC.d(TAG, "onDestroyView")
if (retainInstance) {
dialog?.setDismissMessage(null)
}
super.onDestroyView()
}
private inner class OnCertificateNotTrusted : View.OnClickListener {
override fun onClick(v: View) {
dialog?.cancel()
sslErrorHandler?.cancel()
}
}
private inner class OnCertificateTrusted : View.OnClickListener {
override fun onClick(v: View) {
dismiss()
sslErrorHandler?.proceed()
if (x509Certificate == null) {
Log_OC.d(TAG, "m509Certificate is null onClick dismissed")
return
}
if (activity == null) {
Log_OC.d(TAG, "activity is null onClick dismissed")
return
}
try {
// TODO make this asynchronously, it can take some time
NetworkUtils.addCertToKnownServersStore(x509Certificate, activity)
(activity as OnSslUntrustedCertListener?)?.onSavedCertificate()
} catch (e: GeneralSecurityException) {
(activity as OnSslUntrustedCertListener?)?.onFailedSavingCertificate()
Log_OC.e(TAG, "Server certificate could not be saved in the known-servers trust store ", e)
} catch (e: IOException) {
(activity as OnSslUntrustedCertListener?)?.onFailedSavingCertificate()
Log_OC.e(TAG, "Server certificate could not be saved in the known-servers trust store ", e)
}
}
}
interface OnSslUntrustedCertListener {
fun onSavedCertificate()
fun onFailedSavingCertificate()
}
interface ErrorViewAdapter {
fun updateErrorView(binding: SslUntrustedCertLayoutBinding)
}
interface CertificateViewAdapter {
fun updateCertificateView(binding: SslUntrustedCertLayoutBinding)
}
companion object {
private val TAG: String = SslUntrustedCertDialog::class.java.simpleName
@JvmStatic
fun newInstanceForEmptySslError(error: SslError?, handler: SslErrorHandler?): SslUntrustedCertDialog {
requireNotNull(error) { "Trying to create instance with parameter error == null" }
requireNotNull(handler) { "Trying to create instance with parameter handler == null" }
return SslUntrustedCertDialog().apply {
sslErrorHandler = handler
errorViewAdapter = SslErrorViewAdapter(error)
certificateViewAdapter = SslCertificateViewAdapter(error.certificate)
}
}
@JvmStatic
fun newInstanceForFullSslError(sslException: CertificateCombinedException?): SslUntrustedCertDialog {
requireNotNull(sslException) { "Trying to create instance with parameter sslException == null" }
return SslUntrustedCertDialog().apply {
x509Certificate = sslException.serverCertificate
errorViewAdapter = CertificateCombinedExceptionViewAdapter(sslException)
certificateViewAdapter = X509CertificateViewAdapter(sslException.serverCertificate)
}
}
fun newInstanceForFullSslError(
cert: X509Certificate?,
error: SslError?,
handler: SslErrorHandler?
): SslUntrustedCertDialog {
requireNotNull(cert) { "Trying to create instance with parameter cert == null" }
requireNotNull(error) { "Trying to create instance with parameter error == null" }
requireNotNull(handler) { "Trying to create instance with parameter handler == null" }
return SslUntrustedCertDialog().apply {
x509Certificate = cert
sslErrorHandler = handler
errorViewAdapter = SslErrorViewAdapter(error)
certificateViewAdapter = X509CertificateViewAdapter(cert)
}
}
}
}