Manual refactor
This commit is contained in:
parent
a7cf17ce36
commit
2f70c936ac
12 changed files with 85 additions and 84 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<X509Certificate> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -502,11 +502,11 @@
|
|||
<string name="settings_certificates_prefs_trusted_header">Trusted server certificates</string>
|
||||
<string name="settings_certificates_prefs_trusted_add">Add certificate</string>
|
||||
<string name="settings_certificates_prefs_trusted_add_title">Add a trusted certificate</string>
|
||||
<string name="settings_certificates_prefs_trusted_add_summary">Import a PEM-formatted server or CA certificate file to trust</string>
|
||||
<string name="settings_certificates_prefs_trusted_add_summary">Import a server or CA certificate to the trust store (PEM format)</string>
|
||||
<string name="settings_certificates_prefs_client_header">Client certificates (mTLS)</string>
|
||||
<string name="settings_certificates_prefs_client_add">Add client certificate</string>
|
||||
<string name="settings_certificates_prefs_client_add_title">Add a client certificate</string>
|
||||
<string name="settings_certificates_prefs_client_add_summary">Import PKCS#12 certificate for mutual TLS authentication</string>
|
||||
<string name="settings_certificates_prefs_client_add_summary">Import certificate for mutual TLS authentication (PKCS#12 format)</string>
|
||||
<string name="settings_certificates_prefs_client_configured">Client certificate configured</string>
|
||||
<string name="settings_certificates_prefs_expires">Expires %1$s</string>
|
||||
<string name="settings_certificates_prefs_expired">Expired</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue