Android 根证书管理与证书验证

news2024/11/24 1:03:56

大部分的安卓应用都免不了与后端服务器进行通信。在通信过程中,主要面临两方面的风险:1、中间人攻击。当通信使用 HTTP 等明文协议,通信内容可被嗅探甚至篡改。2、通信内容被攻击者分析。使用加密的协议,虽然避免了中间人攻击,但是攻击者仍然可以通过各种方式获取请求内容,然后针对性的进行漏洞挖掘。

一、背景知识

1、SSL/TLS 简介

为了应对中间人攻击,通常会采用安全套接字层 (SSL,现在技术上称为传输层安全协议 (TLS))来对通信进行加密传输。

当 SSL 与 HTTP 协议相结合就形成了 HTTPS,当 SSL 与 POP3 相结合就形成了 POP3S。

SSL/TLS 的握手过程如下图(了解下就好):

2、数字证书

证书通常是指 X.509 格式证书,是由Netscape 当初设计 SSL 协议的时候定义的。其是服务端公钥的载体,同时还包含很多其他信息,如签发者信息、以及证书申请者相关的信息(国家、组织名称、常用名称等等)、加密算法、签名算法、签名指纹等。下图展示了 baidu.com 的证书部分信息。

在证书校验时候,签发者(CA)信息很重要。那么,为什么一个证书还需要有签发者呢?我们知道任何组织或个人都可以生成自己的数字证书,并设置证书中的任何信息。当客户端与服务端通信的时候,客户端就很难确认当前证书是否真的是服务端组织的真实证书。这个时候,一个比较可行的方案就是有个客户端信任的第三方,这个第三方为服务端证书进行签名,来证明证书的真实性。以 Android 系统为例,其预置了知名的权威签名机构公钥证书,可以判断服务端证书是否为这些机构签发。如果不是知名的权威机构签发,则认为证书是非法的,不会建立 SSL/TLS 通信。

除了签发者(CA)信息之外,证书中的常用名称(Common Name)也是比较重要的信息,常被用来校验HTTPS通信的域名是否正确。

3.自签名证书

自签名证书是无需别的证书为其签名来证明其合法性的证书,根证书都是自签名证书。私有 CA 签名证书则是指,为域名证书签名的 CA,其合法有效性没有得到广泛的认可,该 CA 的根证书没有被内置到系统中。

在实际的开发过程中,有时为了节省昂贵的购买证书的费用,而想要自己给自己的服务器的域名签发域名证书,这即是私有 CA 签名的证书。为了能够使用这种证书,需要在客户端预埋根证书,并对客户端证书合法性验证的过程进行干预,通过我们预埋的根证书为服务端的证书做合法性验证,而不依赖系统的根证书库。

二 .Android 的根证书管理

在 AOSP 源码库中,CA 根证书主要存放在 system/ca-certificates 目录下,而在 Android 系统中,则存放在 /system/etc/security/cacerts 目录下

它们都是 PEM 格式的 X.509 证书。Android 系统通过 SystemCertificateSource、DirectoryCertificateSource 和 CertificateSource 等类管理系统根证书库。CertificateSource定义(位于frameworks/base/core/java/android/security/net/config/CertificateSource.java)了可以对根证书库执行的操作,主要是对根证书的获取和查找:

public interface CertificateSource {
    Set<X509Certificate> getCertificates();
    X509Certificate findBySubjectAndPublicKey(X509Certificate cert);
    X509Certificate findByIssuerAndSignature(X509Certificate cert);
    Set<X509Certificate> findAllByIssuerAndSignature(X509Certificate cert);
    void handleTrustStorageUpdate();
}


 @Override
    public Set<X509Certificate> getCertificates() {
        // TODO: loading all of these is wasteful, we should instead use a keystore style API.
        synchronized (mLock) {
            if (mCertificates != null) {
                return mCertificates;
            }

            Set<X509Certificate> certs = new ArraySet<X509Certificate>();
            if (mDir.isDirectory()) {
                for (String caFile : mDir.list()) {
                    if (isCertMarkedAsRemoved(caFile)) {
                        continue;
                    }
                    X509Certificate cert = readCertificate(caFile);
                    if (cert != null) {
                        certs.add(cert);
                    }
                }
            }
            mCertificates = certs;
            return mCertificates;
        }
    }

获取根证书库的 getCertificates() 操作在第一次被调用时,遍历文件系统,并加载系统所有的根证书文件,并缓存起来,以备后面访问。根证书的查找操作,主要依据证书文件的文件名进行,证书文件被要求以 [SubjectName 的哈希值].[Index] 的形式命名。

三.OKHTTP中的SSL,双向验证

    public Builder sslSocketFactory(SSLSocketFactory sslSocketFactory) {
            if (sslSocketFactory == null)
                throw new NullPointerException("sslSocketFactory == null");
            this.sslSocketFactory = sslSocketFactory;
            this.certificateChainCleaner = Platform.get().buildCertificateChainCleaner
                    (sslSocketFactory);
            return this;
        }
    //如果没有设置sslSocketFactory,就会使用默认的systemDefaultSslSocketFactory
    if (builder.sslSocketFactory != null || !isTLS) {
            this.sslSocketFactory = builder.sslSocketFactory;
            this.certificateChainCleaner = builder.certificateChainCleaner;
        } else {
            X509TrustManager trustManager = Util.platformTrustManager();
            this.sslSocketFactory = newSslSocketFactory(trustManager);
            this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
        }

默认的SslSocketFactory

  private static SSLSocketFactory newSslSocketFactory(X509TrustManager trustManager) {
    try {
      SSLContext sslContext = Platform.get().getSSLContext();
      sslContext.init(null, new TrustManager[] { trustManager }, null);
      return sslContext.getSocketFactory();
    } catch (GeneralSecurityException e) {
      throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.
    }
  }

在newSslSocketFactory方法中
1.调用SSLContext初始化获取SSLContext对象
2.调用init方法
3.获取SocketFactory

在SSL中有两种类型的校验模式:
单向校验:服务器向客户端发送证书进行校验
双向校验:除了服务器向客户端发送证书以外,客户端需要发送证书到服务器进行校验
sslContext.init(null, new TrustManager[]{trustManager}, null);
init函数中第一个传NULL,默认表示Okhttp只支持单向校验。
TrustManager[]{trustManager} 参数表示信任证书

X509TrustManager

 public static X509TrustManager platformTrustManager() {
    try {
      TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
          TrustManagerFactory.getDefaultAlgorithm());
      trustManagerFactory.init((KeyStore) null);
      TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
      if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
        throw new IllegalStateException("Unexpected default trust managers:"
            + Arrays.toString(trustManagers));
      }
      return (X509TrustManager) trustManagers[0];
    } catch (GeneralSecurityException e) {
      throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.
    }
  }

getTrustManagers

public TrustManager[] engineGetTrustManagers() {
        if (keyStore == null) {
            throw new IllegalStateException(
                    "TrustManagerFactory is not initialized");
        }
        return new TrustManager[] { new TrustManagerImpl(keyStore) };
    }

keyStore:

 public void engineInit(KeyStore ks) throws KeyStoreException {
        if (ks != null) {
            keyStore = ks;
        } else {
            keyStore = Platform.getDefaultCertKeyStore();  //获取默认的系统根证书
        }
    }

系统根证书查看

代码如下:

        try {
        KeyStore keyStore = KeyStore.getInstance("AndroidCAStore");
            keyStore.load(null, null);
            Enumeration<String> aliases = keyStore.aliases();
            while (aliases.hasMoreElements()){
                String alias = aliases.nextElement();
                Certificate certificate = keyStore.getCertificate(alias);
                Log.i(TAG, "alias: "+alias);
                Log.i(TAG, "certificate: "+certificate);
            }
        } catch (IOException | CertificateException | NoSuchAlgorithmException | KeyStoreException e) {
        }

打印结果:

2024-06-26 18:58:10.416 6813-6813/com.jarvis.permission I/LX-MainActivity: alias: system:3c899c73.0
2024-06-26 18:58:10.418 6813-6813/com.jarvis.permission I/LX-MainActivity: certificate: Certificate:
        Data:
//版本号
            Version: 3 (0x2)
//序列号
            Serial Number:
                21:2a:56:0c:ae:da:0c:ab:40:45:bf:2b:a2:2d:3a:ea
//证书签名算法
        Signature Algorithm: ecdsa-with-SHA384
//证书发行者
            Issuer: C=CH, O=WISeKey, OU=OISTE Foundation Endorsed, CN=OISTE WISeKey Global Root GC CA
//证书有效时间
            Validity
                Not Before: May  9 09:48:34 2017 GMT
                Not After : May  9 09:58:33 2042 GMT
//证书主体名称
            Subject: C=CH, O=WISeKey, OU=OISTE Foundation Endorsed, CN=OISTE WISeKey Global Root GC CA
//证书公钥信息
            Subject Public Key Info:
//公钥算法
                Public Key Algorithm: id-ecPublicKey
//公钥
                    Public-Key: (384 bit)
                    00000000  04 4c e9 50 c0 c6 0f 72  18 bc d8 f1 ba b3 89 e2  |.L.P...r........|
                    00000010  79 4a a3 16 a7 6b 54 24  db 51 ff ea f4 09 24 c3  |yJ...kT$.Q....$.|
                    00000020  0b 22 9f cb 6a 27 82 81  0d d2 c0 af 31 e4 74 82  |."..j'......1.t.|
                    00000030  6e ca 25 d9 8c 75 9d f1  db d0 9a a2 4b 21 7e 16  |n.%..u......K!~.|
                    00000040  a7 63 90 d2 39 d4 b1 87  78 5f 18 96 0f 50 1b 35  |.c..9...x_...P.5|
                    00000050  37 0f 6a c6 dc d9 13 4d  a4 8e 90 37 e6 bd 5b 31  |7.j....M...7..[1|
                    00000060  91   
                    //扩展|.|
            X509v3 extensions:
                X509v3 Key Usage: critical
                    Certificate Sign, CRL Sign
                X509v3 Basic Constraints: critical
                    CA:TRUE
                X509v3 Subject Key Identifier: 
                    48:87:14:AC:E3:C3:9E:90:60:3A:D7:CA:89:EE:D3:AD:8C:B4:50:66
                1.3.6.1.4.1.311.21.1: 
                    ...
                      //签名算法
        Signature Algorithm: ecdsa-with-SHA384
             30:65:02:30:26:c7:69:5b:dc:d5:e7:b2:e7:c8:0c:8c:8c:c3:
             dd:79:8c:1b:63:d5:c9:52:94:4e:4d:82:4a:73:1e:b2:80:84:
             a9:25:c0:4c:5a:6d:49:29:60:78:13:e2:7e:48:eb:64:02:31:
             00:db:34:20:32:08:ff:9a:49:02:b6:88:de:14:af:5d:6c:99:
             71:8d:1a:3f:8b:d7:e0:a2:36:86:1c:07:82:3a:76:53:fd:c2:
             a2:ed:ef:7b:b0:80:4f:58:0f:4b:53:39:bd

如果目标 URL 服务器下发的证书不在已信任的证书列表里,或者该证书是自签名的,不是由权威机构颁发,那么会出异常,如何处理?

四.自签名证书处理:

方式一:修改 X509TrustManager 所用的根证书库

private static TrustManager[] prepareTrustManager(InputStream certificates) {
        if (certificates == null) {
            return null;
        }
        try {
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            Collection<? extends Certificate> certs = certificateFactory.generateCertificates(certificates);
            for (Certificate certificate : certs) {
                String certificateAlias = Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificate);
            }
            TrustManagerFactory trustManagerFactory = TrustManagerFactory.
                    getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);

            return trustManagerFactory.getTrustManagers();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

方式二:自定义TrustManager

通过自定义TrustManager自己实现对服务器证书的校验

@SuppressLint("CustomX509TrustManager")
    private static class MyTrustManager implements X509TrustManager {
        private static final String TAG = MyTrustManager.class.getSimpleName();
        private final X509TrustManager defaultTrustManager;
        private final X509TrustManager localTrustManager;

        public MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
            TrustManagerFactory var4 = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            var4.init((KeyStore) null);
            defaultTrustManager = chooseTrustManager(var4.getTrustManagers());
            this.localTrustManager = localTrustManager;
        }


        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
            Log.i(TAG, "checkClientTrusted,authType=" + authType);
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                defaultTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException ce) {
                localTrustManager.checkServerTrusted(chain, authType);
            }
        }


        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }

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

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

相关文章

RocketMQ 顺序消息和事务消息及其原理

RocketMQ 顺序消息和事务消息 1、Spring Cloud Alibaba RocketMq 架构图2、RocketMQ 顺序消息2.1、RockerMQ 实现顺序消费2.1.1、顺序发消息2.1.2、顺序收消息 2.2、顺序发送的技术原理2.3、顺序消费的技术原理 3、RocketMQ 的事务消息3.1、RocketMQ 事务消息流程3.2、事务消息…

查看Windows启动时长

&#xff08;附图片&#xff09;电脑自带检测开机时长---查看方式_电脑开机时长命令-CSDN博客 eventvwr - Windows日志 - 系统 - 查找 - 6013.jpg

如何借助 LLM 设计和实现任务型对话 Agent

1 引言 在人工智能的快速发展中&#xff0c;任务型对话 Agent 正成为提升用户体验和工作效率的关键技术。这类系统通过自然语言交互&#xff0c;专注于高效执行特定任务&#xff0c;如预订酒店或查询天气。尽管市场上的开源框架如 Rasa 和 Microsoft Bot Framework 在对话理解…

使用ioDraw,AI绘图只需几秒钟!

只需几秒钟&#xff0c;就能将文字或图片转化为精准的思维导图、流程图、折线图、柱状图、饼图等各种图表&#xff01; 思维导图 思维导图工具使用入口 文字转思维导图 将文本大纲或想法转换成可视化的思维导图&#xff0c;以组织和结构化您的想法。 图片转思维导图 从现有…

汽车电子工程师入门系列——AUTOSAR通信服务框架(下)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

垃圾回收与算法

目录 一、判断对象已经 "死亡" 1、引用计数法 2、可达性分析 二、垃圾收集算法 1、标记清楚算法 2、复制算法 3、标记整理算法 4、分代收集算法 4.1、新生代与复制算法 4.2老年代与标记复制算法 一、判断对象已经 "死亡" 1、引用计数法 在 Java 中&#…

基于STM32的温湿度检测TFT屏幕proteus恒温控制仿真系统

一、引言 本文介绍了一个基于STM32的恒温控制箱检测系统&#xff0c;该系统通过DHT11温湿度传感器采集环境中的温湿度数据&#xff0c;并利用TFT LCD屏幕进行实时显示。通过按键切换页面显示&#xff0c;通过按键切换实现恒温控制箱的恒温控制。为了验证系统的可靠性和稳定性&…

低代码表单配置平台替代普通表单配置平台,前端部分重构的设计和思路

前言 最近将公司的旧表单配置平台重构为低代码表单配置平台&#xff0c;这里记录一下这个过程的设计和思路&#xff0c;不涉及具体的代码&#xff1b;另外这篇文章基本只涉及前端部分&#xff0c;也不涉及与后端数据交互部分。 需求 固化的表单配置平台 -> 灵活的表单配置…

python格式文件

python小白考后复习 CSV格式文件ini格式文件我们可以读取所有节点还可以输出一个节点下所有键值对组成的元组获取节点下的键对应的值判断节点是否存在添加节点还可以添加键值还可以删除节点 XML格式文件读取若是文件格式存在的xml若是以字符串形式存在的xml获取子标签还有获取子…

【最长公共前缀 动态规划】2430. 对字母串可执行的最大删除数

如果有不明白的&#xff0c;请加文末QQ群。 本文涉及知识点 最长公共前缀 动态规划 动态规划汇总 LeetCode 2430. 对字母串可执行的最大删除数 给你一个仅由小写英文字母组成的字符串 s 。在一步操作中&#xff0c;你可以&#xff1a; 删除 整个字符串 s &#xff0c;或者 …

Sping源码(九)—— Bean的初始化(非懒加载)— Bean的创建方式(构造器方法)

序言 前面几篇文章介绍了Spring中几种方式下Bean对象的实例化的过程&#xff0c;那如果之前的几种都不满足&#xff0c;按照Spring中正常Bean的实例化步骤&#xff0c;该如何创建这个Bean对象呢&#xff1f; 测试类 我们先创建几个debug中用到的栗子。 Person 以一个平平无…

pytest-两种不同写法

XUnit 的写法 熟悉 unittest 框架的人都知道&#xff0c;unittest 里面 fixture 的写法是 setUp 和 tearDown&#xff0c;setUp_class 和 tearDown_class&#xff0c;只有这一种写法&#xff0c;而且是固定的写法哈。 Pytest 是兼容 unittest 的&#xff0c;当然也支持这样写…

B : 斐波那契数列第n项Plus

Description 斐波那契数列即 1, 1, 2, 3, 5...&#xff0c;&#xfffd;(&#xfffd;)&#xfffd;(&#xfffd;−1)&#xfffd;(&#xfffd;−2) 。求斐波那契数列第 n 项 Input 每组数据给出 1≤&#xfffd;≤109 。 Output 斐波那契数列第 n 项 对 1097 取模 Sam…

hadoop词频统计

1 Hadoop 安装与伪分布的搭建 2 Hadoop词频统计 此文章基于搭建好hadoop之后做的词频统计实验&#xff0c;以上是链接为搭建hadoop的教程 目录 1 HDFS 文件系统常用命令 2 词频统计实验准备工作 2.1 启动hadoop 关闭防火墙 2.2 查看图形化界面 2.3 文件上传 3 词频统计…

实现点击按钮导出页面pdf

在Vue 3 Vite项目中&#xff0c;你可以使用html2canvas和jspdf库来实现将页面某部分导出为PDF文档的功能。以下是一个简单的实现方式&#xff1a; 1.安装html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue组件中使用这些库来实现导出功能&#xff1a;…

ios13多窗口(UIWindowScene)学习笔记

ios13引入了UIWindowScene类、UIWindowSceneDelegate协议以便支持多窗口功能&#xff0c;但其适用于ipad&#xff0c;不适用于iphone&#xff0c;因为iphone不支持多窗口功能。注意&#xff0c;这里说的窗口不是UIWindow&#xff0c;而是UIWindowScene。 ios13前后的app的UI架…

结构体 -------- 函数-------传参

在函数题中 return 只能传一个值 如果函数体&#xff08;struct fs a&#xff0c;struct fs b&#xff09;传来了两个值&#xff0c;怎么才能只输出一个值呢&#xff1f; 同样要定义一个struct fs 类型的变量 result&#xff1b; 这样不仅可以访问到结构体中的变量a&#…

ESP32实现UDP连接——micropython版本

代码&#xff1a; import network import socket import timedef wifiInit(name, port):ap network.WLAN(network.AP_IF) # 创建一个热点ap.config(essidname, authmodenetwork.AUTH_OPEN) # 无需密码ap.active(True) # 激活热点ip ap.ifconfig()[0] # 获取ip地址print(…

短视频矩阵系统:打造品牌影响力的新方式

一、短视频矩阵概念 短视频营销革命&#xff1a;一站式解决策略&#xff01;短视频矩阵系统是一款专为企业营销设计的高效工具&#xff0c;旨在通过整合和优化众多短视频平台资源&#xff0c;为企业呈现一个全面的短视频营销策略。该系统致力于协助企业以迅速且高效的方式制作…

Node.js全栈指南:静态资源服务器

上一章【认识 MIME 和 HTTP】。 我们认识和了解了 MIME 的概念和作用&#xff0c;也简单地学习了通过浏览器控制台查看请求和返回的用法。 通过对不同的 HTML、CSS、JS 文件进行判断&#xff0c;设置不同的 MIME 值&#xff0c;得以让我们的浏览器正正确地接收和显示不同的文…