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

news2024/11/26 20:29:34

一、前言

接着上回说,最初是为了写一个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 end
         return 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 end
         return 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 end
         return 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 end
         return 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 end
         return (issuers != null) ? issuers : Collections.<X509Certificate>emptySet();
     }
 
@@ -604,6 +658,45 @@
         // install the user cert
         writeCertificate(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 end
         if (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 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
        writeCertificate(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.coloradmin.cn/o/2233837.html

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

相关文章

【Java语言】继承和多态(一)

继承 继承就是实现代码的复用&#xff1b;简而言之就是重复的代码作为父类&#xff08;基类或超类&#xff09;&#xff0c;而不同的可以作为子类&#xff08;派生类&#xff09;。如果子类想要继承父类的成员就一定需要extends进行修饰&#xff08;如&#xff1a;&#xff08;…

04_CC2530+Uart串口通信

04_CC2530UART串口通信 串口通信基本概念 串行通信: 数据字节一位位地依次传送的通信方式, 串行通信的速度慢, 但用的传输线条数少, 成本低&#xff0c;适用于远距离的数据传送并行通信: 数据字节的各位同事传送的通信方式, 优点是数据传送速度快, 缺点是占用的传输线条数多,…

【计算机网络安全】湖北大学–DNS欺骗实验

目录 0x00 ettercap-原理 0x01 ettercap-arp欺骗 0x02 ettercap-dns劫持 0x00 ettercap-原理 攻击者冒充域名服务器&#xff0c;然后把查询的IP地址设为攻击者的IP地址。这样的话&#xff0c;用户上网就只能看到攻击者的主页&#xff0c;而不是用户想要取得的网站。 首先&…

雷池社区版新版本功能防绕过人机验证解析

前两天&#xff0c;2024.10.31&#xff0c;雷池社区版更新7.1版本&#xff0c;其中有一个功能&#xff0c;新增请求防重放 更新记录&#xff1a;hhttps://docs.waf-ce.chaitin.cn/zh/%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95 仔细研究了这个需求&#xff0c;…

【深度学习基础】深入理解 卷积与卷积核

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;深度学习_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 卷积 1.1 …

SpringBoot集成Shiro+Jwt+Redis

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

基于Python的乡村居民信息管理系统【附源码】

基于Python的乡村居民信息管理系统 效果如下&#xff1a; 系统主页面 系统登录页面 管理员主页面 居民管理页面 政务学习页面 土地信息管理页面 个人信息管理页面 居民登陆页面 村委人员主页面 研究背景 随着信息技术的飞速发展和乡村振兴战略的深入实施&#xff0c;传统的乡…

HTML 基础标签——表单标签<form>

文章目录 1. `<form>` 标签:定义表单容器2. `<input>` 标签:多用途输入控件3. `<textarea>` 标签:多行文本输入框4. `<select>` 标签:下拉选择框5. `<option>` 标签:下拉菜单选项6. `<button>` 标签:按钮元素7. `<label>` 标签…

Debian的基本使用

前言 本人撰写的相关文档中&#xff0c;部分技术已经不再提供支持了&#xff08;不得不感慨&#xff0c;菜鸡的个人进步追不上技术更新啊&#xff09;&#xff0c;比如Centos、EasyExcel&#xff0c;虽然说目前仅使用还没有什么问题&#xff0c;但是还是要了解一下备用方案。 …

比亚迪能不能打败特斯拉?

文/孔文清 比亚迪在第三季度的财报发布后&#xff0c;首次在营收上超越了特斯拉&#xff0c;这是电动汽车行业的重要时刻&#xff0c;也反映了中国产业在全球市场中的崛起。 比亚迪在其2024年第三季度财报中首次实现了2011.2亿元的营业收入&#xff0c;相比特斯拉的1793亿元&a…

什么情况下,不推荐建立索引?

一般有以下几种情况不推荐建立索引&#xff1a; 1&#xff09;对于数据量很小的表 当表的数据量很小&#xff08;如几百条记录&#xff09;时&#xff0c;建立索引并不会显著提高查询性能&#xff0c;反而可能增加管理的复杂性&#xff1b; 2&#xff09;频繁更新的表 对于…

深度学习基础知识-残差网络ResNet

目录 一、ResNet 的核心思想&#xff1a;残差学习&#xff08;Residual Learning&#xff09; 二、ResNet 的基本原理 三、ResNet 网络结构 1. 残差块&#xff08;Residual Block&#xff09; ResNet 的跳跃连接类型 2. 网络结构图示 四、ResNet 的特点和优势 五、ResNe…

做反向代购没货源,也能靠“东方玄学”风生水起?

在全球化日益加深的今天&#xff0c;文化的交流与碰撞愈发频繁。近年来&#xff0c;一股神秘的东方力量——风水玄学&#xff0c;在海外社交媒体上悄然走红&#xff0c;成为众多外国友人追捧的新风尚。从TikTok到Instagram&#xff0c;无数华人博主通过分享风水知识、解读玄学饰…

新闻稿件管理:SpringBoot框架实战指南

3系统分析 3.1可行性分析 通过对本新闻稿件管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本新闻稿件管理系统采用SSM框架&#xff0c;JAVA作为开发语…

web实操2——idea创建普通web项目

创建项目 就是普通的java项目&#xff0c;项目右键add framework support&#xff08;添加框架支持&#xff09;,然后点击Web Application&#xff08;web应用程序&#xff09;&#xff0c;然后点击OK。即可。 文件下就会多一个web文件夹&#xff0c;里面是WEB-INF文件夹&…

冰雪奇缘!中科院一区算法+双向深度学习+注意力机制!SAO-BiTCN-BiGRU-Attention雪消融算法优化回归预测

冰雪奇缘&#xff01;中科院一区算法双向深度学习注意力机制&#xff01;SAO-BiTCN-BiGRU-Attention雪消融算法优化回归预测&#xff08;Matlab&#xff09; 目录 冰雪奇缘&#xff01;中科院一区算法双向深度学习注意力机制&#xff01;SAO-BiTCN-BiGRU-Attention雪消融算法优…

LabVIEW适合开发的软件

LabVIEW作为一种图形化编程环境&#xff0c;主要用于测试、测量和控制系统的开发。以下是LabVIEW在不同应用场景中的适用性和优势。 一、测试与测量系统 LabVIEW在测试与测量系统中的应用广泛&#xff0c;是工程测试领域的主流工具之一。利用其强大的数据采集与处理功能&…

ssm校园线上订餐系统的设计与实现+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码看文章最下面 需要定制看文章最下面 目 录 摘 要 I 目 录 III 第1章 绪论 1 1.1 研究背景 1 1.2目的和意义 1 1.3 论文研究内容 1 …

stm32使用串口的轮询模式,实现数据的收发

------内容以b站博主keysking为原型&#xff0c;整理而来&#xff0c;用作个人学习记录。 首先在STM32CubeMX中配置 前期工作省略&#xff0c;只讲重点设置。 这里我配置的是USART2的模式。 会发现&#xff0c;PA2和PA3分别是TX与RX&#xff0c;在连接串口时需要TX对RX&…

Webserver(2.8)守护进程

目录 守护进程案例 守护进程案例 每隔2s获取系统时间&#xff0c;将这个时间写入到磁盘文件中 #include<stdio.h> #include<sys/stat.h> #include<sys/types.h> #include<unistd.h> #include<fcntl.h> #include<sys/time.h> #include<…