Android13 系统/用户证书安装相关分析总结(二) 如何增加一个安装系统证书的接口

一、前言

接着上回说,最初是为了写一个SDK的接口,需求大致是增加证书安装卸载的接口(系统、用户)。于是了解了一下证书相关的处理逻辑,在了解了功能和流程之后,发现settings中支持安装的证书,只能安装到指定路径,并且是user 证书。那么到目前为止,安装用户证书的需求算是可行,可以完成。但是还遗留着一个问题,如何安装系统证书呢?

在上篇文章里边笔者给了两个方案:
1、一种是把证书复制到系统证书的存放路径 /system/etc/security/cacerts
2、另一种是创建一个新的目录

下面开始分析两个方案的可行性

二、可行性分析

1、把证书复制到系统证书的存放路径 /system/etc/security/cacerts

我们可以发现该路径是system分区,system分区一般是只读的,而参考了一下settings里边对系统证书的处理,没有删除,只有禁用和启用。所以不推荐在原有路径下操作。我们顺便看一下对应路径的selinux权限。
在这里插入图片描述
可以看到证书路径下除了root 用户都没有写权限,且selinux 的域为system_security_cacerts_file。我们也看一下这域的对dir 和file 权限范围的定义:
在这里插入图片描述
我们可以看到,te文件中对该路径文件和目录权限范围的约束和linux的一致,都是只读权限,所以放弃在该路径创建删除自定义删除是正确的。另外如果选择了该方案,也有误删系统证书的风险。

2、另一种是创建一个新的目录

这种方案相对来说就比较安全,但也存在一个问题,那就是比较复杂。

说复杂是因为,我们在接到这个需求的时候,对证书这一块儿的了解并不多。对系统证书和应用证书的使用场景几乎也不了解,这样在增加一个额外的证书路径是就会存在一个问题,那就是增加后,对对应流程的处理必然会存在遗漏。

令人遗憾的是,这个问题没有办法解决,只能先按照这个方案实现。然后把自己已经知道的流程和现象做验证,遇到问题的时候,再根据问题,修修补补。

所以下面开始梳理一下如何按照这个方案对接口进行实验

三、具体实现步骤

1、系统接口路径证书选择

首先这个问题,其实也不算个很难的问题。笔者选择的路径是/system/etc/security/cacerts。
至于原因是因为data分区可以读写,其次路径保持和系统证书路径方便记忆

2、实现步骤

确定了证书路径,下一步就是实现了。那么首先我们要在一个特定的时候创建它。在这个系列的上篇文章,我们提到安装、卸载最终的实现了是TrustedCertificateStore.java
那么我们就看一下这个类的实现

//external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
public class TrustedCertificateStore implements ConscryptCertStore {private static final String PREFIX_SYSTEM = "system:";private static final String PREFIX_USER = "user:";public static final boolean isSystem(String alias) {return alias.startsWith(PREFIX_SYSTEM);}public static final boolean isUser(String alias) {return alias.startsWith(PREFIX_USER);}private static class PreloadHolder {private static File defaultCaCertsSystemDir;private static File defaultCaCertsAddedDir;private static File defaultCaCertsDeletedDir;static {String ANDROID_ROOT = System.getenv("ANDROID_ROOT");String ANDROID_DATA = System.getenv("ANDROID_DATA");defaultCaCertsSystemDir = new File(ANDROID_ROOT + "/etc/security/cacerts");setDefaultUserDirectory(new File(ANDROID_DATA + "/misc/keychain"));}}private static final CertificateFactory CERT_FACTORY;static {try {CERT_FACTORY = CertificateFactory.getInstance("X509");} catch (CertificateException e) {throw new AssertionError(e);}}public static void setDefaultUserDirectory(File root) {PreloadHolder.defaultCaCertsAddedDir = new File(root, "cacerts-added");PreloadHolder.defaultCaCertsDeletedDir = new File(root, "cacerts-removed");}private final File systemDir;private final File addedDir;private final File deletedDir;

这里只选取了一下开头的部分,可以看到这里说明了 system user 证书的路径。我们可以在这里加一个自己定义的系统证书的路径。不过值得注意的是,除了在这里增加文件目录创建,还要看一下其他的。比如说证书别名,证书个数这些根据分类扫描目录的方法也需要增加相关的处理。

那么下面就先贴出修改

diff --git a/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java b/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
index 333450d..e12a88c 100644
--- a/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
+++ b/external/conscrypt/platform/src/main/java/org/conscrypt/TrustedCertificateStore.java
@@ -122,7 +122,10 @@private final File systemDir;private final File addedDir;private final File deletedDir;
+    private final File systemEditDir = new File("/data/etc/security/cacerts");+    @android.compat.annotation.UnsupportedAppUsage
+    @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)public TrustedCertificateStore() {this(PreloadHolder.defaultCaCertsSystemDir, PreloadHolder.defaultCaCertsAddedDir,PreloadHolder.defaultCaCertsDeletedDir);
@@ -132,6 +135,7 @@this.systemDir = systemDir;this.addedDir = addedDir;this.deletedDir = deletedDir;
+        systemEditDir.mkdirs();}public Certificate getCertificate(String alias) {
@@ -159,8 +163,15 @@throw new NullPointerException("alias == null");}File file;
+        //modify start
+        File systemEditFile  = new File(systemEditDir, alias.substring(PREFIX_SYSTEM.length()));if (isSystem(alias)) {
-            file = new File(systemDir, alias.substring(PREFIX_SYSTEM.length()));
+            if(systemEditFile.exists()){
+                file = systemEditFile;
+            }else{
+                file = new File(systemDir, alias.substring(PREFIX_SYSTEM.length()));
+            }
+        //modify end    } else if (isUser(alias)) {file = new File(addedDir, alias.substring(PREFIX_USER.length()));} else {
@@ -238,6 +249,9 @@Set<String> result = new HashSet<String>();addAliases(result, PREFIX_USER, addedDir);addAliases(result, PREFIX_SYSTEM, systemDir);
+        //add start
+        addAliases(result, PREFIX_SYSTEM, systemEditDir);
+        //add endreturn result;}@@ -272,6 +286,15 @@result.add(alias);}}
+        //add start
+        String[] systemEditFiles = systemEditDir.list();
+        for (String filename : systemEditFiles) {
+            String alias = PREFIX_SYSTEM + filename;
+            if (containsAlias(alias, true)) {
+                result.add(alias);
+            }
+        }
+        //add endreturn result;}@@ -300,7 +323,10 @@return null;}File system = getCertificateFile(systemDir, x);
-        if (system.exists()) {
+        //add start
+        File systemEdit = getCertificateFile(systemEditDir, x);
+        //add end
+        if (system.exists() || systemEdit.exists()) {return PREFIX_SYSTEM + system.getName();}return null;
@@ -365,6 +391,15 @@if (system != null && !isDeletedSystemCertificate(system)) {return system;}
+        //add start
+        X509Certificate systemEdit = findCert(systemEditDir,
+                                          c.getSubjectX500Principal(),
+                                          selector,
+                                          X509Certificate.class);
+        if (systemEdit != null) {
+            return systemEdit;
+        }
+        //add endreturn null;}@@ -395,6 +430,15 @@if (system != null && !isDeletedSystemCertificate(system)) {return system;}
+        //add start
+        X509Certificate systemEdit = findCert(systemEditDir,
+                                          c.getSubjectX500Principal(),
+                                          selector,
+                                          X509Certificate.class);
+        if (systemEdit != null) {
+            return systemEdit;
+        }
+        //add endreturn null;}@@ -439,6 +483,16 @@issuers = systemCerts;}}
+        //add start
+        Set<X509Certificate> systemEditCerts = findCertSet(systemEditDir,issuer,selector);
+        if (systemEditCerts != null) {
+            if (issuers != null) {
+                issuers.addAll(systemEditCerts);
+            } else {
+                issuers = systemEditCerts;
+            }
+        }
+        //add endreturn (issuers != null) ? issuers : Collections.<X509Certificate>emptySet();}@@ -604,6 +658,45 @@// install the user certwriteCertificate(user, cert);}
+    
+    //add start
+    public void installCertificateWithType(boolean isSystem,X509Certificate cert) throws IOException, CertificateException {
+        if (cert == null) {
+            throw new NullPointerException("cert == null");
+        }
+        File system = getCertificateFile(systemDir, cert);
+        if (system.exists()) {
+            File deleted = getCertificateFile(deletedDir, cert);
+            if (deleted.exists()) {
+                // we have a system cert that was marked deleted.
+                // remove the deleted marker to expose the original
+                if (!deleted.delete()) {
+                    throw new IOException("Could not remove " + deleted);
+                }
+                return;
+            }
+            // otherwise we just have a dup of an existing system cert.
+            // return taking no further action.
+            return;
+        }
+        File user = getCertificateFile(addedDir, cert);
+        if (user.exists()) {
+            // we have an already installed user cert, bail.
+            return;
+        }
+        // install the user cert
+        File systemEdit = getCertificateFile(systemEditDir, cert);
+        if (systemEdit.exists()) {
+            // we have an already installed user cert, bail.
+            return;
+        }
+        if(isSystem){
+            writeCertificate(systemEdit, cert);
+        }else{
+            writeCertificate(user, cert);
+        }
+    }
+    //add end/*** This could be considered the implementation of {@code
@@ -620,7 +713,9 @@if (file == null) {return;}
-        if (isSystem(alias)) {
+        File parent = file.getParentFile();
+        boolean isSystemEdit = parent.getAbsolutePath().equals("/data/etc/security/cacerts");
+        if (isSystem(alias) && !isSystemEdit) {X509Certificate cert = readCertificate(file);if (cert == null) {// skip problem certificates
@@ -635,6 +730,13 @@writeCertificate(deleted, cert);return;}
+        //add start
+        if(isSystemEdit){
+            new FileOutputStream(file).close();
+            removeSystemUnnecessaryTombstones(alias);
+            return;
+        }
+        //add endif (isUser(alias)) {// truncate the file to make a tombstone by opening and closing.// we need ensure that we don't leave a gap before a valid cert.
@@ -671,4 +773,29 @@lastTombstoneIndex--;}}
+    
+    // add start
+    private void removeSystemUnnecessaryTombstones(String alias) throws IOException {
+        int dotIndex = alias.lastIndexOf('.');
+        if (dotIndex == -1) {
+            throw new AssertionError(alias);
+        }
+        String hash = alias.substring(PREFIX_SYSTEM.length(), dotIndex);
+        int lastTombstoneIndex = Integer.parseInt(alias.substring(dotIndex + 1));
+
+        if (file(systemEditDir, hash, lastTombstoneIndex + 1).exists()) {
+            return;
+        }
+        while (lastTombstoneIndex >= 0) {
+            File file = file(systemEditDir, hash, lastTombstoneIndex);
+            if (!isTombstone(file)) {
+                break;
+            }
+            if (!file.delete()) {
+                throw new IOException("Could not remove " + file);
+            }
+            lastTombstoneIndex--;
+        }
+    }
+    //add end}

好了,先来说一下这个文件修改了什么?以及为什么修改?

首先,这个文件主要负责证书的检索、安装和卸载。如果我们需要自定义一个路径,那么必然要在这个文件里修改,增加自定义系统证书的路径的实现。这就是在构造函数中增加路径创建的原因。

其次,看了这个类安装证书的函数实现,具体实现如下:

    /*** This non-{@code KeyStoreSpi} public interface is used by the* {@code KeyChainService} to install new CA certificates. It* silently ignores the certificate if it already exists in the* store.*/public void installCertificate(X509Certificate cert) throws IOException, CertificateException {if (cert == null) {throw new NullPointerException("cert == null");}File system = getCertificateFile(systemDir, cert);if (system.exists()) {File deleted = getCertificateFile(deletedDir, cert);if (deleted.exists()) {// we have a system cert that was marked deleted.// remove the deleted marker to expose the originalif (!deleted.delete()) {throw new IOException("Could not remove " + deleted);}return;}// otherwise we just have a dup of an existing system cert.// return taking no further action.return;}File user = getCertificateFile(addedDir, cert);if (user.exists()) {// we have an already installed user cert, bail.return;}// install the user certwriteCertificate(user, cert);}//安装实现核心函数private void writeCertificate(File file, X509Certificate cert)throws IOException, CertificateException {File dir = file.getParentFile();dir.mkdirs();dir.setReadable(true, false);dir.setExecutable(true, false);OutputStream os = null;try {os = new FileOutputStream(file);os.write(cert.getEncoded());} finally {IoUtils.closeQuietly(os);}file.setReadable(true, false);}

从实现,我们可以看到,对于系统证书来说安装和卸载只是新增一个路径对其进行标记。比如在安装的这个接口,我们发现首先会将证书文件在系统路径中对比一下,如果存在,就清除delete标记 。然后才是在用户证书路径下进行检索,如果存在就终止安装直接return,如果不存在就继续执行writeCertificate。我们简单看一下writeCertificate的实现就能发现,安装证书在最底层TrustedCertificateStore中就是把文件通过文件流写到指定目录,如果目录不存在先创建父目录。

综上,我们知道了,如果要新增自定义系统证书安装,不仅要自定义路径,还要修改安装卸载接口,查找接口等等。

这也就是为什么主要修改这个累的原因,简单来说就是系统没实现。到这里我们可以先在安装卸载查找几个接口开动,加上我们自己定义路径的这些功能。

当笔者这样做了之后,封装了接口,本地写了demo之后发现,settings中的系统证书界面也能读到了证书。这样看来一些好像都正常了。于是笔者松了一口气,看上去搞定了,先出个版本验证一下。

四、疑问

当然,心里还是没底。因为当时提的这个需求,自己没有完全了解原理,而网上的资料也比较零散,于是笔者在出一版之后也把心里的疑问记录了下来

1、证书安装除了settings中能够看到(一种接口调用),还有没有其他方式?
2、证书安装之后有什么用途?比如网络证书的验证流程是怎么样的?
3、VPN证书和WIFI证书验证流程又是怎么样的?
4、我需要怎么测试呢?

好了带着这些疑问,我等待着反馈,当然后面又遇到了更多的问题,我们下回说

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/59429.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

矩阵特殊打印方式

小伙伴们大家好&#xff0c;好几天没更新了&#xff0c;主要有个比赛。从今天起继续给大家更新&#xff0c;今天给大家带来一种新的题型&#xff1a;矩阵特殊打印方式。 螺旋打印矩阵 解题思路 首先给大家看一下什么是螺旋方式打印&#xff1a; 就像这样一直转圈圈。 我想大多…

C语言 流程控制语句

时间&#xff1a;2024.11.5 一、学习内容 流程控制语句&#xff1a; 通过一些语句&#xff0c;控制程序的执行流程。 1、顺序结构 从上往下依次执行&#xff0c;是程序默认的执行过程。 2、if的第一种格式 if(关系表达式) { 语句体&#xff1b; } //考试奖励&#xff1a;…

03集合基础

目录 1.集合 Collection Map 常用集合 List 接口及其实现 Set 接口及其实现 Map 接口及其实现 Queue 接口及其实现 Deque 接口及其实现 Stack类 并发集合类 工具类 2.ArrayList 3.LinkedList 单向链表的实现 1. 节点类&#xff08;Node&#xff09; 2. 链表类&a…

HTMLCSS:3D 旋转卡片的炫酷动画

效果演示 这段代码是一个HTML和CSS的组合&#xff0c;用于创建一个具有3D效果的动画卡片。 HTML <div class"obj"><div class"objchild"><span class"inn6"><h3 class"text">我是谁&#xff1f;我在那<…

网络自动化03:简单解释send_config_set方法并举例

目录 拓扑图设备信息 netmiko涉及方法send_config_set()方法的简单示例代码输出结果代码解释导入模块配置信息config_device_interface_description 函数主程序块总结 send_config_set方法参数&#xff1a;1. enter_config_mode2. config_commands3. enter_config_mode4. error…

什么是实验室信息(lis)系统?

医院LIS系统定义&#xff1a; 医院LIS系统&#xff0c;即实验室信息系统&#xff08;Laboratory Information System&#xff09;&#xff0c;是专为医院检验科设计的信息管理系统。它通过计算机网络技术实现实验仪器与计算机的联网&#xff0c;智能化、自动化地管理病人样品登…

MySQL45讲 第十六讲 “order by”是怎么工作的?

文章目录 MySQL45讲 第十六讲 “order by”是怎么工作的&#xff1f;一、引言二、全字段排序&#xff08;一&#xff09;索引创建与执行情况分析&#xff08;二&#xff09;执行流程&#xff08;三&#xff09;查看是否使用临时文件 三、rowid 排序&#xff08;一&#xff09;参…

网页版五子棋—— WebSocket 协议

目录 前言 一、背景介绍 二、原理解析 1.连接过程&#xff08;握手&#xff09; 2.报文格式 三、代码示例 1.服务端代码 &#xff08;1&#xff09;TestAPI 类 &#xff08;2&#xff09;WebSocketConfig 类 2.客户端代码 3.代码演示 结尾 前言 从本篇文章开始&am…

【综合案例】使用React编写B站评论案例

一、效果展示 默认效果&#xff0c;一开始默认按照最热进行排序 发布了一条评论 按照最新进行排序 按照最新进行排序 二、效果说明 页面上默认有3条评论&#xff0c;且一开始进入页面的时候是按照点赞数量进行倒序排列展示&#xff0c;可以点击【最热 、最新】进行排序的切换。…

docker镜像文件导出导入

1. 导出容器&#xff08;包含内部服务&#xff09;为镜像文件&#xff08;docker commit方法&#xff09; 原理&#xff1a;docker commit命令允许你将一个容器的当前状态保存为一个新的镜像。这个新镜像将包含容器内所有的文件系统更改&#xff0c;包括安装的软件、配置文件等…

区块链技术与应用-PKU 学习笔记

课程地址 资料&#xff1a; ETH-Security 区块链学习记录_比特币 BTC 密码学原理 比特币&#xff0c;又称加密货币(crypto-currency)&#xff0c;它主要利用了密码学中的哈希函数(cryptographic hash function)的抗碰撞特性(collision resistance)和单向散列特性(hiding) …

在Java中,实现数据库连接通常使用JDBC

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……&#xff09; 2、学会Oracle数据库入门到入土用法(创作中……&#xff09; 3、手把手教你开发炫酷的vbs脚本制作(完善中……&#xff09; 4、牛逼哄哄的 IDEA编程利器技巧(编写中……&#xff09; 5、面经吐血整理的 面试技…

【优选算法 — 双指针】双指针小专题

和为 s 的两个数 和为s的两个数 题目描述 解法一&#xff1a;暴力枚举 暴力枚举&#xff0c;先固定一个数&#xff0c;然后让这个数和另一个数匹配相加&#xff0c; 如果当前的数 所有剩余的数 target&#xff0c;则返回这两个数&#xff0c;否则固定下一个数&#…

并查集(基础学习与应用)

并查集 基本原理&#xff1a; 对于多个集合&#xff0c;每个集合中的多个元素用一颗树的形式表示&#xff0c;根节点的编号即为整个集合的编号&#xff0c;每个树上节点存储其父节点&#xff0c;使得当前集合的每个子节点都可以通过对父节点的询问来找到根节点&#xff0c;根…

003-Kotlin界面开发之声明式编程范式

概念本源 在界面程序开发中&#xff0c;有两个非常典型的编程范式&#xff1a;命令式编程和声明式编程。命令式编程是指通过编写一系列命令来描述程序的运行逻辑&#xff0c;而声明式编程则是通过编写一系列声明来描述程序的状态。在命令式编程中&#xff0c;程序员需要关心程…

Spring Boot 与 Vue 共筑地方特色美食分享卓越平台

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

react使用Fullcalendar 实战用法

使用步骤请参考&#xff1a;react使用Fullcalendar 卡片式的日历&#xff1a; 需求图&#xff1a; 卡片式的日历&#xff0c;其实我是推荐 antd的&#xff0c;我两个都写了一下都能实现。 antd 的代码&#xff1a; antd的我直接用的官网示例&#xff1a;antd 日历示例 i…

Flutter 正在切换成 Monorepo 和支持 workspaces

其实关于 Monorepo 和 workspaces 相关内容在之前《Dart 3.5 发布&#xff0c;全新 Dart Roadmap Update》 和 《Flutter 之 ftcon24usa 大会&#xff0c;创始人分享 Flutter 十年发展史》 就有简单提到过&#xff0c;而目前来说刚好看到 flaux 这个新进展&#xff0c;所以就再…

在做题中学习(74):比较含退格的字符串

解法&#xff1a;用栈来模拟 思路&#xff1a;不用真的定义一个栈,用字符串string来模拟栈的行为 入栈&#xff1a;s[i] ! #时 push_back(s[i]) 出栈:s[i] # 的时候&#xff0c;并且s.size() > 0&#xff0c;pop_back(s[i])循环结束得到结果 注意&#xff1a;如果真的…

前后端交互通用排序策略

目录 排序场景 排序实现思路 1. 静态代码排序实现 2.数据库驱动排序实现 3. 基于Java反射的动态排序实现 通用排序工具 SortListUtil 结语 排序场景 在面向前端数据展示的应用场景中&#xff0c;我们旨在实现一个更加灵活的排序机制&#xff0c;该机制能够支持对从后端传递…