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.rhkb.cn/news/464779.html

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

相关文章

React 组件生命周期与 Hooks 简明指南

文章目录 一、类组件的生命周期方法1. 挂载阶段2. 更新阶段3. 卸载阶段 二、函数组件中的 Hooks1. useState2. useEffect3. useContext4. useReducer 结论 好的&#xff0c;我们来详细讲解一下 React 类组件的生命周期方法和函数组件中的钩子&#xff08;hooks&#xff09;。 …

软考(中级-软件设计师)数据库篇(1101)

第6章 数据库系统基础知识 一、基本概念 1、数据库 数据库&#xff08;Database &#xff0c;DB&#xff09;是指长期存储在计算机内的、有组织的、可共享的数据集合。数据库中的数据按一定的数据模型组织、描述和存储&#xff0c;具有较小的冗余度、较高的数据独立性和扩展…

FITS论文解析

在本文中&#xff0c;作者探讨了如何将复杂的频域特征提取与简单的线性模型&#xff08;如DLinear&#xff09;结合&#xff0c;以优化时间序列预测任务的效率和解释性。本文的核心思想是利用频域处理和DLinear的简化结构来达到高效的预测能力&#xff0c;同时保留对复杂特征的…

Ubuntu 搭建Yapi服务

新手上路&#xff0c;小心开车 1. 安装mongo数据库 第一步&#xff1a;docker pull mongo 拉取mongo镜像&#xff1b; 第二步&#xff1a;启动mongo镜像 docker network create yapi_networkdocker run -d \-p 27017:27017 \--name mongodb \-e MONGO_INITDB_ROOT_USERNAMEya…

【进度猫-注册/登录安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造成亏损无底洞…

JAVA 插入 JSON 对象到 PostgreSQL

博主主页:【南鸢1.0】 本文专栏&#xff1a;JAVA 目录 ​编辑 简介 所用&#xff1a; 1、 确保 PostgreSQL 数据库支持 JSON&#xff1a; 2、添加 PostgreSQL JDBC 驱动 3、安装和运行 PostgreSQL 4、建立数据库的连接 简介 在现代软件开发中&#xff0c;由于 JSON 数据…

前端通过nginx部署一个本地服务的方法

前端通过nginx部署一个本地服务的方法&#xff1a; 1.下载ngnix nginx 下载完成后解压缩后运行nginx.exe文件 2.打包你的前端项目文件 yarn build 把生成的dist文件复制出来&#xff0c;替换到nginx的html文件下 3.配置conf目录的nginx.conf文件 主要配置server监听 ser…

深度学习基础知识-损失函数

目录 1. 均方误差&#xff08;Mean Squared Error, MSE&#xff09; 2. 平均绝对误差&#xff08;Mean Absolute Error, MAE&#xff09; 3. Huber 损失 4. 交叉熵损失&#xff08;Cross-Entropy Loss&#xff09; 5. KL 散度&#xff08;Kullback-Leibler Divergence&…

如何在BSV区块链上实现可验证AI

​​发表时间&#xff1a;2024年10月2日 nChain的顶尖专家们已经找到并成功测试了一种方法&#xff1a;通过区块链技术来验证AI&#xff08;人工智能&#xff09;系统的输出结果。这种方法可以确保AI模型既按照规范运行&#xff0c;避免严重错误&#xff0c;遵守诸如公平、透明…

网络原理(应用层)->HTTPS解

前言&#xff1a; 大家好我是小帅&#xff0c;今天我们来了解HTTPS, 个人主页&#xff1a;再无B&#xff5e;U&#xff5e;G 文章目录 1.HTTPS1.1HTTPS 是什么&#xff1f;1.2 "加密" 是什么1.3 HTTPS 的⼯作过程1.3. 1对称加密1.3.2⾮对称加密 1.4中间人攻击1.5 证书…

TOEIC 词汇专题:娱乐休闲篇

TOEIC 词汇专题&#xff1a;娱乐休闲篇 在娱乐和休闲活动中&#xff0c;我们会接触到许多特定的词汇。这些词汇涉及到活动入场、观众互动、评论等各个方面&#xff0c;帮助你在相关场景中更加自如。 1. 入场和观众 一些常用词汇帮助你轻松应对观众与入场管理相关的场景&#…

Spring框架---AOP技术

AOP概念的引入 第一步创建普通Maven项目 导入依赖 <dependencies><!--spring的核心--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version&g…

双指针算法篇——一快一慢须臾之间解决问题的飘逸与灵动(2)

前言&#xff1a; 上篇我们讲解了双指针算法的含义以及相关题型讲解&#xff0c;本次则趁热打铁&#xff0c;通过进阶题目的分析与讲解&#xff0c;促使我们更深入和灵活的理解运用双指针算法。 相关题目及讲解 一. 盛最多水的容器 题目链接&#xff1a;11. 盛最多水的容器 -…

koa项目实战 == 实现注册登录鉴权

一. 项目的初始化 1 npm 初始化 npm init -y生成package.json文件: 记录项目的依赖 2 git 初始化 git init生成’.git’隐藏文件夹, git 的本地仓库 3 创建 ReadMe 文件 二. 搭建项目 1 安装 Koa 框架 npm install koa2 编写最基本的 app 创建src/main.js const Koa…

ONLYOFFICE 文档8.2更新评测:PDF 协作编辑、性能优化及更多新功能体验

文章目录 &#x1f340;引言&#x1f340;ONLYOFFICE 产品简介&#x1f340;功能与特点&#x1f340;体验与测评ONLYOFFICE 8.2&#x1f340;邀请用户使用&#x1f340; ONLYOFFICE 项目介绍&#x1f340;总结 &#x1f340;引言 在日常办公软件的选择中&#xff0c;WPS 和微软…

MATLAB下的四个模型的IMM例程(CV、CT左转、CT右转、CA四个模型),附下载链接

基于IMM算法的目标跟踪。利用卡尔曼滤波和多模型融合技术&#xff0c;能够在含噪声的环境中提高估计精度&#xff0c;带图像输出 文章目录 概述源代码运行结果代码结构与功能1. 初始化2. 仿真参数设置3. 模型参数设置4. 生成量测数据5. IMM算法初始化6. IMM迭代7. 绘图8. 辅助函…

Segmentation fault 问题解决

问题描述 执行有import torch代码的py 文件报Segmentation fault 原因分析&#xff1a; 查了网上说的几种可能性 import torch 时出现 “Segmentation fault” 错误&#xff0c;通常表示 PyTorch 的安装或配置存在问题 可能的原因 不兼容的库版本: PyTorch、CUDA 或其他依赖…

如何搭建汽车行业AI知识库:定义+好处+方法步骤

在汽车行业&#xff0c;大型车企面临着员工众多、价值链长、技术密集和知识传播难等挑战。如何通过有效的知识沉淀与应用&#xff0c;提升各部门协同效率&#xff0c;快速响应客户咨询&#xff0c;降低销售成本&#xff0c;并开启体系化、可持续性的知识管理建设&#xff0c;成…

QGIS:HCMGIS插件

插件GitHub地址&#xff1a;https://github.com/thangqd/HCMGIS。 以下对HCMGIS插件进行简单介绍&#xff0c;并演示如何进行地图数据下载。 插件简介 HCMGIS - Basemaps, Download OpenData, Batch Converter, VN-2000 Projections, and Field Calculation Utilities for QGI…

SpringBoot集成Shiro+Jwt+Redis

1. 概述 首先需要知道为什么使用 ShiroJwtRedis 进行登录认证和权限控制。 1. 为什么用Shiro&#xff1f; 主要用的是 shiro 的登录认证和权限控制功能。 Shiro 参见本栏目文章 &#x1f343;《Shiro实战》 2. 为什么用Jwt&#xff1f; Shiro 默认的 Session 机制来帮助实现…