From 2f70c936ac486ef43fccf4bceff4de7ba95c16c8 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Sat, 3 Jan 2026 17:54:19 -0500 Subject: [PATCH] Manual refactor --- .../java/io/heckel/ntfy/backup/Backuper.kt | 6 +-- .../java/io/heckel/ntfy/msg/ApiService.kt | 4 +- .../ntfy/msg/DownloadAttachmentWorker.kt | 15 ++++-- .../io/heckel/ntfy/msg/DownloadIconWorker.kt | 11 ++-- .../io/heckel/ntfy/msg/UserActionWorker.kt | 16 ++++-- .../io/heckel/ntfy/service/WsConnection.kt | 15 +++--- .../java/io/heckel/ntfy/ui/AddFragment.kt | 7 ++- .../io/heckel/ntfy/ui/CertificateFragment.kt | 17 +++---- .../ntfy/ui/CertificateSettingsFragment.kt | 10 ++-- .../ntfy/ui/CertificateTrustFragment.kt | 14 +++--- .../{tls/SSLManager.kt => util/CertUtil.kt} | 50 ++++++------------- app/src/main/res/values/strings.xml | 4 +- 12 files changed, 85 insertions(+), 84 deletions(-) rename app/src/main/java/io/heckel/ntfy/{tls/SSLManager.kt => util/CertUtil.kt} (87%) diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 2c88589c..9eebbc4a 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -10,7 +10,7 @@ import io.heckel.ntfy.app.Application import io.heckel.ntfy.db.Repository import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.msg.NotificationService -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.topicUrl import java.io.InputStreamReader @@ -229,8 +229,8 @@ class Backuper(val context: Context) { } certificates.forEach { c -> try { - val x509Cert = SSLManager.parsePemCertificate(c.pem) - val fingerprint = SSLManager.calculateFingerprint(x509Cert) + val cert = CertUtil.parseCertificate(c.pem) + val fingerprint = CertUtil.calculateFingerprint(cert) repository.addTrustedCertificate(fingerprint, c.pem) } catch (e: Exception) { Log.w(TAG, "Unable to restore trusted certificate: ${e.message}. Ignoring.", e) diff --git a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt index de0140d9..199c208a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt @@ -7,7 +7,7 @@ import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.User -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.* import okhttp3.* import okhttp3.RequestBody.Companion.toRequestBody @@ -19,7 +19,7 @@ import kotlin.random.Random class ApiService(private val context: Context) { private val repository = Repository.getInstance(context) - private val sslManager = SSLManager.getInstance(context) + private val sslManager = CertUtil.getInstance(context) private val gson = Gson() private val parser = NotificationParser() diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt index 3db7c879..14ab62ae 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadAttachmentWorker.kt @@ -12,8 +12,15 @@ import androidx.work.WorkerParameters import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.app.Application -import io.heckel.ntfy.db.* -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DONE +import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_FAILED +import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_INDETERMINATE +import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_NONE +import io.heckel.ntfy.db.Attachment +import io.heckel.ntfy.db.Notification +import io.heckel.ntfy.db.Repository +import io.heckel.ntfy.db.Subscription +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.ensureSafeNewFile import io.heckel.ntfy.util.extractBaseUrl @@ -24,11 +31,11 @@ import java.io.File import java.util.concurrent.TimeUnit class DownloadAttachmentWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { - private val sslManager = SSLManager.getInstance(context) + private val certUtil = CertUtil.getInstance(context) private fun createClient(url: String): OkHttpClient { val baseUrl = extractBaseUrl(url) - return sslManager.getOkHttpClientBuilder(baseUrl) + return certUtil.getOkHttpClientBuilder(baseUrl) .callTimeout(15, TimeUnit.MINUTES) .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt index 974eecb3..6c807eb2 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadIconWorker.kt @@ -7,8 +7,11 @@ import androidx.work.Worker import androidx.work.WorkerParameters import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.app.Application -import io.heckel.ntfy.db.* -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.db.Icon +import io.heckel.ntfy.db.Notification +import io.heckel.ntfy.db.Repository +import io.heckel.ntfy.db.Subscription +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.extractBaseUrl import io.heckel.ntfy.util.sha256 @@ -20,11 +23,11 @@ import java.util.Date import java.util.concurrent.TimeUnit class DownloadIconWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { - private val sslManager = SSLManager.getInstance(context) + private val certUtil = CertUtil.getInstance(context) private fun createClient(url: String): OkHttpClient { val baseUrl = extractBaseUrl(url) - return sslManager.getOkHttpClientBuilder(baseUrl) + return certUtil.getOkHttpClientBuilder(baseUrl) .callTimeout(1, TimeUnit.MINUTES) .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) diff --git a/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt index ca985947..9367936c 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/UserActionWorker.kt @@ -5,24 +5,30 @@ import androidx.work.Worker import androidx.work.WorkerParameters import io.heckel.ntfy.R import io.heckel.ntfy.app.Application -import io.heckel.ntfy.db.* +import io.heckel.ntfy.db.ACTION_PROGRESS_FAILED +import io.heckel.ntfy.db.ACTION_PROGRESS_ONGOING +import io.heckel.ntfy.db.ACTION_PROGRESS_SUCCESS +import io.heckel.ntfy.db.Action +import io.heckel.ntfy.db.Notification +import io.heckel.ntfy.db.Repository +import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_BROADCAST import io.heckel.ntfy.msg.NotificationService.Companion.ACTION_HTTP -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.extractBaseUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody -import java.util.* +import java.util.Locale import java.util.concurrent.TimeUnit class UserActionWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { - private val sslManager = SSLManager.getInstance(context) + private val certUtil = CertUtil.getInstance(context) private fun createClient(url: String): OkHttpClient { val baseUrl = extractBaseUrl(url) - return sslManager.getOkHttpClientBuilder(baseUrl) + return certUtil.getOkHttpClientBuilder(baseUrl) .callTimeout(60, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS) diff --git a/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt b/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt index baf828ec..e3f262de 100644 --- a/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt +++ b/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt @@ -3,11 +3,14 @@ package io.heckel.ntfy.service import android.app.AlarmManager import android.content.Context import android.os.Build -import io.heckel.ntfy.db.* -import io.heckel.ntfy.msg.ApiService +import io.heckel.ntfy.db.ConnectionState +import io.heckel.ntfy.db.Notification +import io.heckel.ntfy.db.Repository +import io.heckel.ntfy.db.Subscription +import io.heckel.ntfy.db.User import io.heckel.ntfy.msg.ApiService.Companion.requestBuilder import io.heckel.ntfy.msg.NotificationParser -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.topicShortUrl import io.heckel.ntfy.util.topicUrlWs @@ -15,7 +18,7 @@ import okhttp3.OkHttpClient import okhttp3.Response import okhttp3.WebSocket import okhttp3.WebSocketListener -import java.util.* +import java.util.Calendar import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference @@ -42,9 +45,9 @@ class WsConnection( private val alarmManager: AlarmManager ) : Connection { private val parser = NotificationParser() - private val sslManager = SSLManager.getInstance(context) + private val certUtil = CertUtil.getInstance(context) private val client: OkHttpClient by lazy { - sslManager.getOkHttpClientBuilder(connectionId.baseUrl) + certUtil.getOkHttpClientBuilder(connectionId.baseUrl) .readTimeout(0, TimeUnit.MILLISECONDS) .pingInterval(1, TimeUnit.MINUTES) // The server pings us too, so this doesn't matter much .connectTimeout(10, TimeUnit.SECONDS) diff --git a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt index 629f0183..0e91ce8b 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/AddFragment.kt @@ -19,7 +19,7 @@ import io.heckel.ntfy.R import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.User import io.heckel.ntfy.msg.ApiService -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -271,10 +271,9 @@ class AddFragment : DialogFragment(), CertificateTrustFragment.CertificateTrustL private fun handleSSLException(baseUrl: String) { // Try to fetch the server's certificate - val sslManager = SSLManager.getInstance(requireContext()) - val certificate = sslManager.fetchServerCertificate(baseUrl) - val activity = activity ?: return + val certUtil = CertUtil.getInstance(requireContext()) + val certificate = certUtil.fetchServerCertificate(baseUrl) activity.runOnUiThread { if (certificate != null) { showCertificateTrustDialog(baseUrl, certificate) diff --git a/app/src/main/java/io/heckel/ntfy/ui/CertificateFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/CertificateFragment.kt index 500fe5fd..0d769635 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/CertificateFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/CertificateFragment.kt @@ -22,7 +22,7 @@ import com.google.android.material.textfield.TextInputLayout import io.heckel.ntfy.R import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.TrustedCertificate -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.AfterChangedTextWatcher import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.validUrl @@ -30,7 +30,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.security.KeyStore -import java.security.cert.X509Certificate import java.io.ByteArrayInputStream import java.text.SimpleDateFormat import java.util.* @@ -244,13 +243,13 @@ class CertificateFragment : DialogFragment() { deleteMenuItem.isVisible = true try { - val x509Cert = SSLManager.parsePemCertificate(trustedCert.pem) + val cert = CertUtil.parseCertificate(trustedCert.pem) val dateFormat = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()) - subjectText.text = x509Cert.subjectX500Principal.name - issuerText.text = x509Cert.issuerX500Principal.name + subjectText.text = cert.subjectX500Principal.name + issuerText.text = cert.issuerX500Principal.name fingerprintText.text = trustedCert.fingerprint - validFromText.text = dateFormat.format(x509Cert.notBefore) - validUntilText.text = dateFormat.format(x509Cert.notAfter) + validFromText.text = dateFormat.format(cert.notBefore) + validUntilText.text = dateFormat.format(cert.notAfter) } catch (e: Exception) { fingerprintText.text = trustedCert.fingerprint } @@ -359,8 +358,8 @@ class CertificateFragment : DialogFragment() { } lifecycleScope.launch(Dispatchers.IO) { try { - val x509Cert = SSLManager.parsePemCertificate(certPem!!) - val fingerprint = SSLManager.calculateFingerprint(x509Cert) + val cert = CertUtil.parseCertificate(certPem!!) + val fingerprint = CertUtil.calculateFingerprint(cert) repository.addTrustedCertificate(fingerprint, certPem!!) withContext(Dispatchers.Main) { Toast.makeText(context, R.string.certificate_dialog_added_toast, Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt index 1509c7c6..893ccd16 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/CertificateSettingsFragment.kt @@ -8,7 +8,7 @@ import io.heckel.ntfy.R import io.heckel.ntfy.db.ClientCertificate import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.TrustedCertificate -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import io.heckel.ntfy.util.shortUrl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -50,11 +50,11 @@ class CertificateSettingsFragment : BasePreferenceFragment(), CertificateFragmen certs.forEach { trustedCert -> try { - val x509Cert = SSLManager.parsePemCertificate(trustedCert.pem) + val cert = CertUtil.parseCertificate(trustedCert.pem) val pref = Preference(preferenceScreen.context) - pref.title = getDisplaySubject(x509Cert) - pref.summary = if (isValid(x509Cert)) { - getString(R.string.settings_certificates_prefs_expires, dateFormat.format(x509Cert.notAfter)) + pref.title = getDisplaySubject(cert) + pref.summary = if (isValid(cert)) { + getString(R.string.settings_certificates_prefs_expires, dateFormat.format(cert.notAfter)) } else { getString(R.string.settings_certificates_prefs_expired) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/CertificateTrustFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/CertificateTrustFragment.kt index aab28ce5..0909ef09 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/CertificateTrustFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/CertificateTrustFragment.kt @@ -12,9 +12,10 @@ import androidx.lifecycle.lifecycleScope import com.google.android.material.appbar.MaterialToolbar import io.heckel.ntfy.R import io.heckel.ntfy.db.Repository -import io.heckel.ntfy.tls.SSLManager +import io.heckel.ntfy.util.CertUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.security.cert.CertificateFactory import java.security.cert.X509Certificate import java.text.SimpleDateFormat import java.util.* @@ -26,6 +27,7 @@ import java.util.* class CertificateTrustFragment : DialogFragment() { private lateinit var listener: CertificateTrustListener private lateinit var certificate: X509Certificate + private lateinit var certificatePem: String private lateinit var baseUrl: String private lateinit var repository: Repository @@ -61,8 +63,9 @@ class CertificateTrustFragment : DialogFragment() { ?: throw IllegalArgumentException("Base URL required") // Parse the certificate - val certFactory = java.security.cert.CertificateFactory.getInstance("X.509") + val certFactory = CertificateFactory.getInstance("X.509") certificate = certFactory.generateCertificate(java.io.ByteArrayInputStream(certBytes)) as X509Certificate + certificatePem = certBytes.toString() // Build the view val view = requireActivity().layoutInflater.inflate(R.layout.fragment_certificate_trust_dialog, null) @@ -118,7 +121,7 @@ class CertificateTrustFragment : DialogFragment() { // Populate certificate details subjectText.text = certificate.subjectX500Principal.name issuerText.text = certificate.issuerX500Principal.name - fingerprintText.text = SSLManager.calculateFingerprint(certificate) + fingerprintText.text = CertUtil.calculateFingerprint(certificate) validFromText.text = dateFormat.format(certificate.notBefore) validUntilText.text = dateFormat.format(certificate.notAfter) @@ -141,9 +144,8 @@ class CertificateTrustFragment : DialogFragment() { private fun trustCertificate() { lifecycleScope.launch(Dispatchers.IO) { - val fingerprint = SSLManager.calculateFingerprint(certificate) - val pem = SSLManager.encodeToPem(certificate) - repository.addTrustedCertificate(fingerprint, pem) + val fingerprint = CertUtil.calculateFingerprint(certificate) + repository.addTrustedCertificate(fingerprint, certificatePem) } listener.onCertificateTrusted(baseUrl, certificate) dismiss() diff --git a/app/src/main/java/io/heckel/ntfy/tls/SSLManager.kt b/app/src/main/java/io/heckel/ntfy/util/CertUtil.kt similarity index 87% rename from app/src/main/java/io/heckel/ntfy/tls/SSLManager.kt rename to app/src/main/java/io/heckel/ntfy/util/CertUtil.kt index d5c2142f..f89ca4b1 100644 --- a/app/src/main/java/io/heckel/ntfy/tls/SSLManager.kt +++ b/app/src/main/java/io/heckel/ntfy/util/CertUtil.kt @@ -1,4 +1,4 @@ -package io.heckel.ntfy.tls +package io.heckel.ntfy.util import android.annotation.SuppressLint import android.content.Context @@ -6,22 +6,25 @@ import android.util.Base64 import io.heckel.ntfy.db.ClientCertificate import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.TrustedCertificate -import io.heckel.ntfy.util.Log import kotlinx.coroutines.runBlocking import okhttp3.OkHttpClient import java.io.ByteArrayInputStream +import java.net.URL import java.security.KeyStore import java.security.MessageDigest import java.security.SecureRandom import java.security.cert.CertificateFactory import java.security.cert.X509Certificate +import javax.net.ssl.HttpsURLConnection import javax.net.ssl.KeyManager import javax.net.ssl.KeyManagerFactory import javax.net.ssl.SSLContext +import javax.net.ssl.SSLException import javax.net.ssl.SSLSocket import javax.net.ssl.TrustManager import javax.net.ssl.TrustManagerFactory import javax.net.ssl.X509TrustManager +import kotlin.collections.addAll /** * Manages SSL/TLS configuration for OkHttpClient instances. @@ -32,9 +35,9 @@ import javax.net.ssl.X509TrustManager * * Uses standard TrustManagerFactory and KeyManagerFactory (not custom implementations). */ -class SSLManager private constructor(context: Context) { +class CertUtil private constructor(context: Context) { private val appContext: Context = context.applicationContext - private val repository: Repository by lazy { Repository.getInstance(appContext) } + private val repository: Repository by lazy { Repository.Companion.getInstance(appContext) } /** * Get an OkHttpClient.Builder configured with custom SSL for a specific server @@ -87,7 +90,7 @@ class SSLManager private constructor(context: Context) { // Custom hostname verifier that bypasses only for user-trusted certs if (trustedFingerprints.isNotEmpty()) { builder.hostnameVerifier { hostname, session -> - val defaultVerifier = javax.net.ssl.HttpsURLConnection.getDefaultHostnameVerifier() + val defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier() if (defaultVerifier.verify(hostname, session)) { return@hostnameVerifier true } @@ -134,7 +137,7 @@ class SSLManager private constructor(context: Context) { capturedCert = chain[0] } // Always throw to prevent actual connection - throw javax.net.ssl.SSLException("Certificate captured for inspection") + throw SSLException("Certificate captured for inspection") } override fun getAcceptedIssuers(): Array = arrayOf() @@ -144,7 +147,7 @@ class SSLManager private constructor(context: Context) { sslContext.init(null, arrayOf(trustManager), null) try { - val url = java.net.URL(baseUrl) + val url = URL(baseUrl) val host = url.host val port = when { url.port != -1 -> url.port @@ -179,7 +182,7 @@ class SSLManager private constructor(context: Context) { // Add user-trusted certificates trustedCerts.forEachIndexed { index, trustedCert -> try { - val cert = parsePemCertificate(trustedCert.pem) + val cert = parseCertificate(trustedCert.pem) keyStore.setCertificateEntry("user$index", cert) } catch (e: Exception) { Log.w(TAG, "Failed to parse trusted certificate: ${trustedCert.fingerprint}", e) @@ -240,44 +243,23 @@ class SSLManager private constructor(context: Context) { @Volatile @SuppressLint("StaticFieldLeak") // Only holds applicationContext - private var instance: SSLManager? = null + private var instance: CertUtil? = null - fun getInstance(context: Context): SSLManager { + fun getInstance(context: Context): CertUtil { return instance ?: synchronized(this) { - instance ?: SSLManager(context).also { instance = it } + instance ?: CertUtil(context).also { instance = it } } } - /** - * Calculate SHA-256 fingerprint of a certificate - */ fun calculateFingerprint(cert: X509Certificate): String { val md = MessageDigest.getInstance("SHA-256") val digest = md.digest(cert.encoded) return digest.joinToString(":") { "%02X".format(it) } } - /** - * Encode certificate to PEM format - */ - fun encodeToPem(cert: X509Certificate): String { - val base64 = Base64.encodeToString(cert.encoded, Base64.NO_WRAP) - val sb = StringBuilder() - sb.append("-----BEGIN CERTIFICATE-----\n") - var i = 0 - while (i < base64.length) { - val end = minOf(i + 64, base64.length) - sb.append(base64.substring(i, end)) - sb.append("\n") - i += 64 - } - sb.append("-----END CERTIFICATE-----") - return sb.toString() - } - - fun parsePemCertificate(pem: String): X509Certificate { + fun parseCertificate(pem: String): X509Certificate { val factory = CertificateFactory.getInstance("X.509") return factory.generateCertificate(pem.byteInputStream()) as X509Certificate } } -} +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27a55c8a..cee15c99 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -502,11 +502,11 @@ Trusted server certificates Add certificate Add a trusted certificate - Import a PEM-formatted server or CA certificate file to trust + Import a server or CA certificate to the trust store (PEM format) Client certificates (mTLS) Add client certificate Add a client certificate - Import PKCS#12 certificate for mutual TLS authentication + Import certificate for mutual TLS authentication (PKCS#12 format) Client certificate configured Expires %1$s Expired