mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 19:15:58 +00:00
New CA certificates manager
This commit is contained in:
@@ -87,6 +87,9 @@ fun parseTimestamp(timestamp: Long): String {
|
||||
return formatter.format(instant)
|
||||
}
|
||||
|
||||
fun parseDate(date: Date)
|
||||
= SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
|
||||
|
||||
val Long.humanReadableDate: String
|
||||
get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this))
|
||||
|
||||
|
||||
@@ -67,16 +67,21 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
@@ -125,6 +130,7 @@ import com.bintianqi.owndroid.R
|
||||
import com.bintianqi.owndroid.SharedPrefs
|
||||
import com.bintianqi.owndroid.formatFileSize
|
||||
import com.bintianqi.owndroid.humanReadableDate
|
||||
import com.bintianqi.owndroid.parseDate
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||
import com.bintianqi.owndroid.ui.FunctionItem
|
||||
@@ -135,10 +141,15 @@ import com.bintianqi.owndroid.ui.NavIcon
|
||||
import com.bintianqi.owndroid.ui.RadioButtonItem
|
||||
import com.bintianqi.owndroid.ui.SwitchItem
|
||||
import com.bintianqi.owndroid.uriToStream
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.Executors
|
||||
@@ -1228,62 +1239,165 @@ private fun ColumnScope.LockTaskFeatures() {
|
||||
}
|
||||
}
|
||||
|
||||
data class CaCertInfo(
|
||||
val hash: String,
|
||||
val data: ByteArray
|
||||
)
|
||||
|
||||
@Serializable object CaCert
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalStdlibApi::class)
|
||||
@Composable
|
||||
fun CaCertScreen(onNavigateUp: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val dpm = context.getDPM()
|
||||
val receiver = context.getReceiver()
|
||||
var dialog by remember { mutableStateOf(false) }
|
||||
var caCertByteArray = remember { byteArrayOf() }
|
||||
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
uri ?: return@rememberLauncherForActivityResult
|
||||
uriToStream(context, uri) {
|
||||
caCertByteArray = it.readBytes()
|
||||
/** 0:none, 1:install, 2:info, 3:uninstall all */
|
||||
var dialog by remember { mutableIntStateOf(0) }
|
||||
var caCertByteArray by remember { mutableStateOf(byteArrayOf()) }
|
||||
val coroutine = rememberCoroutineScope()
|
||||
val getCertLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {uri ->
|
||||
if(uri != null) {
|
||||
uriToStream(context, uri) {
|
||||
caCertByteArray = it.readBytes()
|
||||
}
|
||||
dialog = 1
|
||||
}
|
||||
dialog = true
|
||||
}
|
||||
MyScaffold(R.string.ca_cert, 8.dp, onNavigateUp) {
|
||||
Button(
|
||||
onClick = {
|
||||
getFileLauncher.launch("*/*")
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
|
||||
) {
|
||||
Text(stringResource(R.string.select_ca_cert))
|
||||
val exportCertLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
|
||||
if(uri != null) {
|
||||
context.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(caCertByteArray)
|
||||
}
|
||||
context.showOperationResultToast(true)
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
dpm.uninstallAllUserCaCerts(receiver)
|
||||
context.showOperationResultToast(true)
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(R.string.uninstall_all_user_ca_cert))
|
||||
}
|
||||
val caCerts = remember { mutableStateListOf<CaCertInfo>() }
|
||||
fun refresh() {
|
||||
caCerts.clear()
|
||||
coroutine.launch(Dispatchers.IO) {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
dpm.getInstalledCaCerts(receiver).forEach { ba ->
|
||||
val hash = md.digest(ba).toHexString()
|
||||
withContext(Dispatchers.Main) { caCerts += CaCertInfo(hash, ba) }
|
||||
}
|
||||
}
|
||||
if(dialog) {
|
||||
val exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
|
||||
AlertDialog(
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
if(exist) {
|
||||
dpm.uninstallCaCert(receiver, caCertByteArray)
|
||||
} else {
|
||||
val result = dpm.installCaCert(receiver, caCertByteArray)
|
||||
context.showOperationResultToast(result)
|
||||
}
|
||||
dialog = false
|
||||
}) {
|
||||
Text(stringResource(if(exist) R.string.uninstall else R.string.install))
|
||||
}
|
||||
LaunchedEffect(Unit) { refresh() }
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.ca_cert)) },
|
||||
navigationIcon = { NavIcon(onNavigateUp) },
|
||||
actions = {
|
||||
IconButton({ dialog = 3 }) {
|
||||
Icon(Icons.Outlined.Delete, stringResource(R.string.delete))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton({ dialog = false }) { Text(stringResource(R.string.cancel)) }
|
||||
},
|
||||
onDismissRequest = { dialog = false }
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton({
|
||||
Toast.makeText(context, R.string.select_ca_cert, Toast.LENGTH_SHORT).show()
|
||||
getCertLauncher.launch(arrayOf("*/*"))
|
||||
}) {
|
||||
Icon(Icons.Default.Add, stringResource(R.string.install))
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
Modifier.fillMaxSize().padding(paddingValues),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
items(caCerts, { it.hash }) { cert ->
|
||||
Column(
|
||||
Modifier.fillMaxWidth().clickable{
|
||||
caCertByteArray = cert.data
|
||||
dialog = 2
|
||||
}.animateItem().padding(vertical = 10.dp, horizontal = 8.dp)
|
||||
) {
|
||||
Text(cert.hash.substring(0..7))
|
||||
}
|
||||
HorizontalDivider()
|
||||
}
|
||||
item {
|
||||
if(caCerts.isEmpty()) Text(stringResource(R.string.no_ca_cert), Modifier.padding(top = 8.dp), colorScheme.onSurfaceVariant)
|
||||
else Spacer(Modifier.padding(vertical = 30.dp))
|
||||
}
|
||||
}
|
||||
if(dialog != 0) AlertDialog(
|
||||
text = {
|
||||
if(dialog == 3) Text(stringResource(R.string.uninstall_all_user_ca_cert))
|
||||
else {
|
||||
var text = ""
|
||||
val sha256 = MessageDigest.getInstance("SHA-256").digest(caCertByteArray).toHexString()
|
||||
try {
|
||||
val cf = CertificateFactory.getInstance("X.509")
|
||||
val cert = cf.generateCertificate(caCertByteArray.inputStream()) as X509Certificate
|
||||
text = "Serial number\n" + cert.serialNumber.toString(16) + "\n\n" +
|
||||
"Subject\n" + cert.subjectX500Principal.name + "\n\n" +
|
||||
"Issuer\n" + cert.issuerX500Principal.name + "\n\n" +
|
||||
"Issued on: " + parseDate(cert.notBefore) + "\n" +
|
||||
"Expires on: " + parseDate(cert.notAfter) + "\n\n" +
|
||||
"SHA-256 fingerprint" + "\n$sha256"
|
||||
} catch(e: Exception) {
|
||||
e.printStackTrace()
|
||||
text = stringResource(R.string.parse_cert_failed)
|
||||
}
|
||||
Column(Modifier.verticalScroll(rememberScrollState())) {
|
||||
SelectionContainer {
|
||||
Text(text)
|
||||
}
|
||||
if(dialog == 2) Row(Modifier.fillMaxWidth().padding(top = 4.dp), Arrangement.SpaceBetween) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
dpm.uninstallCaCert(receiver, caCertByteArray)
|
||||
refresh()
|
||||
dialog = 0
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.49F),
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = colorScheme.error)
|
||||
) {
|
||||
Text(stringResource(R.string.uninstall))
|
||||
}
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
exportCertLauncher.launch(sha256.substring(0..7) + ".0")
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(0.96F)
|
||||
) {
|
||||
Text(stringResource(R.string.export))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
try {
|
||||
if(dialog == 1) {
|
||||
context.showOperationResultToast(dpm.installCaCert(receiver, caCertByteArray))
|
||||
}
|
||||
if(dialog == 3) {
|
||||
dpm.uninstallAllUserCaCerts(receiver)
|
||||
}
|
||||
refresh()
|
||||
dialog = 0
|
||||
} catch(e: Exception) {
|
||||
e.printStackTrace()
|
||||
context.showOperationResultToast(false)
|
||||
}
|
||||
}) {
|
||||
Text(stringResource(if(dialog == 1) R.string.install else R.string.confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
if(dialog != 2) TextButton({ dialog = 0 }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { dialog = 0 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,7 +195,11 @@
|
||||
<string name="ltf_keyguard">Разрешить блокировку экрана</string>
|
||||
<string name="ltf_block_activity_start_in_task">Блокировать запуск активности в задаче</string>
|
||||
<string name="ca_cert">CA-сертификат</string>
|
||||
<string name="select_ca_cert" tools:ignore="TypographyEllipsis">Выберите сертификат...</string>
|
||||
<!--TODO: 4 strings-->
|
||||
<string name="no_ca_cert">No user CA certificate installed</string>
|
||||
<string name="parse_cert_failed">Failed to parse certificate</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="select_ca_cert">Select a CA certificate</string>
|
||||
<string name="uninstall_all_user_ca_cert">Удалить все пользовательские CA-сертификаты</string>
|
||||
<string name="security_logging">Журнал безопасности</string>
|
||||
<string name="pre_reboot_security_logs">Журналы безопасности перед перезагрузкой</string>
|
||||
|
||||
@@ -201,8 +201,12 @@
|
||||
<string name="ltf_global_actions">Küresel eylemlere izin ver</string>
|
||||
<string name="ltf_keyguard">Ekran kilidine izin ver</string>
|
||||
<string name="ltf_block_activity_start_in_task">Görevde etkinlik başlatmayı engelle</string>
|
||||
<string name="ca_cert">CA sertifikası</string> <!--TODO-->
|
||||
<string name="select_ca_cert" tools:ignore="TypographyEllipsis">Sertifika seç...</string> <!--TODO-->
|
||||
<string name="ca_cert">CA sertifikası</string>
|
||||
<!--TODO: 4 strings-->
|
||||
<string name="no_ca_cert">No user CA certificate installed</string>
|
||||
<string name="parse_cert_failed">Failed to parse certificate</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="select_ca_cert">Select a CA certificate</string>
|
||||
<string name="uninstall_all_user_ca_cert">Tüm kullanıcı sertifikalarını kaldır</string> <!--TODO-->
|
||||
<string name="security_logging">Security logging</string> <!--TODO-->
|
||||
<string name="pre_reboot_security_logs">Yeniden başlatmadan önce güvenlik kayıtları</string>
|
||||
|
||||
@@ -193,7 +193,10 @@
|
||||
<string name="package_name">包名</string>
|
||||
<string name="not_exist">不存在</string>
|
||||
<string name="ca_cert">CA证书</string>
|
||||
<string name="select_ca_cert" tools:ignore="TypographyEllipsis">选择证书...</string>
|
||||
<string name="no_ca_cert">没有已安装的CA证书</string>
|
||||
<string name="parse_cert_failed">解析证书失败</string>
|
||||
<string name="export">导出</string>
|
||||
<string name="select_ca_cert">选择一个CA证书</string>
|
||||
<string name="uninstall_all_user_ca_cert">卸载所有用户证书</string>
|
||||
<string name="security_logging">安全日志</string>
|
||||
<string name="pre_reboot_security_logs">重启前安全日志</string>
|
||||
|
||||
@@ -222,7 +222,10 @@
|
||||
<string name="ltf_keyguard">Allow keyguard</string>
|
||||
<string name="ltf_block_activity_start_in_task">Block activity start in task</string>
|
||||
<string name="ca_cert">CA certificate</string>
|
||||
<string name="select_ca_cert" tools:ignore="TypographyEllipsis">Select certificate...</string>
|
||||
<string name="no_ca_cert">No user CA certificate installed</string>
|
||||
<string name="parse_cert_failed">Failed to parse certificate</string>
|
||||
<string name="export">Export</string>
|
||||
<string name="select_ca_cert">Select a CA certificate</string>
|
||||
<string name="uninstall_all_user_ca_cert">Uninstall all user CA certificate</string>
|
||||
<string name="security_logging">Security logging</string>
|
||||
<string name="pre_reboot_security_logs">Pre-reboot security logs</string>
|
||||
|
||||
Reference in New Issue
Block a user