mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
New CA certificates manager
This commit is contained in:
@@ -14,6 +14,7 @@ Use Android Device owner privilege to manage your device.
|
|||||||
- System
|
- System
|
||||||
- Options: disable camera, disable screenshot, master volume mute, disable USB signal...
|
- Options: disable camera, disable screenshot, master volume mute, disable USB signal...
|
||||||
- Permission policy
|
- Permission policy
|
||||||
|
- Manage CA certificates
|
||||||
- _Wipe data_
|
- _Wipe data_
|
||||||
- ...
|
- ...
|
||||||
- Network
|
- Network
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
- 系统
|
- 系统
|
||||||
- 选项:禁用摄像头、禁止截屏、全局静音、禁用USB信号...
|
- 选项:禁用摄像头、禁止截屏、全局静音、禁用USB信号...
|
||||||
- 权限策略
|
- 权限策略
|
||||||
- 清除数据
|
- 管理CA证书
|
||||||
|
- _清除数据_
|
||||||
- ...
|
- ...
|
||||||
- 网络
|
- 网络
|
||||||
- 添加/修改/删除 Wi-Fi
|
- 添加/修改/删除 Wi-Fi
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
- 创建用户
|
- 创建用户
|
||||||
- ...
|
- ...
|
||||||
- 密码与锁屏
|
- 密码与锁屏
|
||||||
- 重置密码
|
- _重置密码_
|
||||||
- 要求密码复杂度
|
- 要求密码复杂度
|
||||||
- 设置屏幕超时
|
- 设置屏幕超时
|
||||||
- ...
|
- ...
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ fun parseTimestamp(timestamp: Long): String {
|
|||||||
return formatter.format(instant)
|
return formatter.format(instant)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun parseDate(date: Date)
|
||||||
|
= SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(date)
|
||||||
|
|
||||||
val Long.humanReadableDate: String
|
val Long.humanReadableDate: String
|
||||||
get() = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(Date(this))
|
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.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.List
|
import androidx.compose.material.icons.automirrored.filled.List
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.outlined.Delete
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.DatePicker
|
import androidx.compose.material3.DatePicker
|
||||||
import androidx.compose.material3.DatePickerDialog
|
import androidx.compose.material3.DatePickerDialog
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
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.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
@@ -125,6 +130,7 @@ import com.bintianqi.owndroid.R
|
|||||||
import com.bintianqi.owndroid.SharedPrefs
|
import com.bintianqi.owndroid.SharedPrefs
|
||||||
import com.bintianqi.owndroid.formatFileSize
|
import com.bintianqi.owndroid.formatFileSize
|
||||||
import com.bintianqi.owndroid.humanReadableDate
|
import com.bintianqi.owndroid.humanReadableDate
|
||||||
|
import com.bintianqi.owndroid.parseDate
|
||||||
import com.bintianqi.owndroid.showOperationResultToast
|
import com.bintianqi.owndroid.showOperationResultToast
|
||||||
import com.bintianqi.owndroid.ui.CheckBoxItem
|
import com.bintianqi.owndroid.ui.CheckBoxItem
|
||||||
import com.bintianqi.owndroid.ui.FunctionItem
|
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.RadioButtonItem
|
||||||
import com.bintianqi.owndroid.ui.SwitchItem
|
import com.bintianqi.owndroid.ui.SwitchItem
|
||||||
import com.bintianqi.owndroid.uriToStream
|
import com.bintianqi.owndroid.uriToStream
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.io.ByteArrayOutputStream
|
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.Date
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.Executors
|
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
|
@Serializable object CaCert
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalStdlibApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CaCertScreen(onNavigateUp: () -> Unit) {
|
fun CaCertScreen(onNavigateUp: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val dpm = context.getDPM()
|
val dpm = context.getDPM()
|
||||||
val receiver = context.getReceiver()
|
val receiver = context.getReceiver()
|
||||||
var dialog by remember { mutableStateOf(false) }
|
/** 0:none, 1:install, 2:info, 3:uninstall all */
|
||||||
var caCertByteArray = remember { byteArrayOf() }
|
var dialog by remember { mutableIntStateOf(0) }
|
||||||
val getFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
var caCertByteArray by remember { mutableStateOf(byteArrayOf()) }
|
||||||
uri ?: return@rememberLauncherForActivityResult
|
val coroutine = rememberCoroutineScope()
|
||||||
uriToStream(context, uri) {
|
val getCertLauncher = rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) {uri ->
|
||||||
caCertByteArray = it.readBytes()
|
if(uri != null) {
|
||||||
|
uriToStream(context, uri) {
|
||||||
|
caCertByteArray = it.readBytes()
|
||||||
|
}
|
||||||
|
dialog = 1
|
||||||
}
|
}
|
||||||
dialog = true
|
|
||||||
}
|
}
|
||||||
MyScaffold(R.string.ca_cert, 8.dp, onNavigateUp) {
|
val exportCertLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
|
||||||
Button(
|
if(uri != null) {
|
||||||
onClick = {
|
context.contentResolver.openOutputStream(uri)?.use {
|
||||||
getFileLauncher.launch("*/*")
|
it.write(caCertByteArray)
|
||||||
},
|
}
|
||||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
|
context.showOperationResultToast(true)
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.select_ca_cert))
|
|
||||||
}
|
}
|
||||||
Button(
|
}
|
||||||
onClick = {
|
val caCerts = remember { mutableStateListOf<CaCertInfo>() }
|
||||||
dpm.uninstallAllUserCaCerts(receiver)
|
fun refresh() {
|
||||||
context.showOperationResultToast(true)
|
caCerts.clear()
|
||||||
},
|
coroutine.launch(Dispatchers.IO) {
|
||||||
modifier = Modifier.fillMaxWidth()
|
val md = MessageDigest.getInstance("SHA-256")
|
||||||
) {
|
dpm.getInstalledCaCerts(receiver).forEach { ba ->
|
||||||
Text(stringResource(R.string.uninstall_all_user_ca_cert))
|
val hash = md.digest(ba).toHexString()
|
||||||
|
withContext(Dispatchers.Main) { caCerts += CaCertInfo(hash, ba) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if(dialog) {
|
}
|
||||||
val exist = dpm.hasCaCertInstalled(receiver, caCertByteArray)
|
LaunchedEffect(Unit) { refresh() }
|
||||||
AlertDialog(
|
Scaffold(
|
||||||
confirmButton = {
|
topBar = {
|
||||||
TextButton({
|
TopAppBar(
|
||||||
if(exist) {
|
title = { Text(stringResource(R.string.ca_cert)) },
|
||||||
dpm.uninstallCaCert(receiver, caCertByteArray)
|
navigationIcon = { NavIcon(onNavigateUp) },
|
||||||
} else {
|
actions = {
|
||||||
val result = dpm.installCaCert(receiver, caCertByteArray)
|
IconButton({ dialog = 3 }) {
|
||||||
context.showOperationResultToast(result)
|
Icon(Icons.Outlined.Delete, stringResource(R.string.delete))
|
||||||
}
|
|
||||||
dialog = false
|
|
||||||
}) {
|
|
||||||
Text(stringResource(if(exist) R.string.uninstall else R.string.install))
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
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_keyguard">Разрешить блокировку экрана</string>
|
||||||
<string name="ltf_block_activity_start_in_task">Блокировать запуск активности в задаче</string>
|
<string name="ltf_block_activity_start_in_task">Блокировать запуск активности в задаче</string>
|
||||||
<string name="ca_cert">CA-сертификат</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="uninstall_all_user_ca_cert">Удалить все пользовательские CA-сертификаты</string>
|
||||||
<string name="security_logging">Журнал безопасности</string>
|
<string name="security_logging">Журнал безопасности</string>
|
||||||
<string name="pre_reboot_security_logs">Журналы безопасности перед перезагрузкой</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_global_actions">Küresel eylemlere izin ver</string>
|
||||||
<string name="ltf_keyguard">Ekran kilidine 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="ltf_block_activity_start_in_task">Görevde etkinlik başlatmayı engelle</string>
|
||||||
<string name="ca_cert">CA sertifikası</string> <!--TODO-->
|
<string name="ca_cert">CA sertifikası</string>
|
||||||
<string name="select_ca_cert" tools:ignore="TypographyEllipsis">Sertifika seç...</string> <!--TODO-->
|
<!--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="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="security_logging">Security logging</string> <!--TODO-->
|
||||||
<string name="pre_reboot_security_logs">Yeniden başlatmadan önce güvenlik kayıtları</string>
|
<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="package_name">包名</string>
|
||||||
<string name="not_exist">不存在</string>
|
<string name="not_exist">不存在</string>
|
||||||
<string name="ca_cert">CA证书</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="uninstall_all_user_ca_cert">卸载所有用户证书</string>
|
||||||
<string name="security_logging">安全日志</string>
|
<string name="security_logging">安全日志</string>
|
||||||
<string name="pre_reboot_security_logs">重启前安全日志</string>
|
<string name="pre_reboot_security_logs">重启前安全日志</string>
|
||||||
|
|||||||
@@ -222,7 +222,10 @@
|
|||||||
<string name="ltf_keyguard">Allow keyguard</string>
|
<string name="ltf_keyguard">Allow keyguard</string>
|
||||||
<string name="ltf_block_activity_start_in_task">Block activity start in task</string>
|
<string name="ltf_block_activity_start_in_task">Block activity start in task</string>
|
||||||
<string name="ca_cert">CA certificate</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="uninstall_all_user_ca_cert">Uninstall all user CA certificate</string>
|
||||||
<string name="security_logging">Security logging</string>
|
<string name="security_logging">Security logging</string>
|
||||||
<string name="pre_reboot_security_logs">Pre-reboot security logs</string>
|
<string name="pre_reboot_security_logs">Pre-reboot security logs</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user