鸿蒙网络编程系列28-服务端证书锁定防范中间人攻击示例

1. TLS通讯中间人攻击及防范简介

TLS安全通讯的基础是基于对操作系统或者浏览器根证书的信任,如果CA证书签发机构被入侵,或者设备内置证书被篡改,都会导致TLS握手环节面临中间人攻击的风险。其实,这种风险被善意利用的情况还是很常见的,比如著名的HTTPS调试工具Fiddler,就是利用了这一点,通过让使用者信任自己签发的证书,达到替换服务端证书的目的,最终可以实现对HTTPS通讯的监听。

那么,如何防范这种风险呢?HttpRequest的请求参数配置HttpRequestOptions提供了certificatePinning属性:

certificatePinning?: CertificatePinning | CertificatePinning[]

该属性接收一个或者多个证书的PIN码;在和服务端通讯前,配置服务端证书的PIN码到此属性中,在和服务端通讯时,HttpRequest请求会自动检查服务端证书的PIN码是否匹配该属性,如果不匹配就拒绝连接,从而达到只信任指定的服务端证书的目的,这样,即使中间人攻击得逞,应用也能拒绝和服务端通讯,从而避免了通讯被监听和破解。

2.服务端证书锁定演示

本示例以百度网站首页为例来演示服务端证书的锁定,需要先获取百度的服务端证书并保存到模拟器或者手机上。

本示例运行后的界面如下图所示。

img

单击“选择”按钮,在弹出的文件选择窗口里选择一个其他的证书,然后单击“请求”按钮,响应如下图所示,可以看到错误信息为公钥不匹配,请求失败。

img

然后继续单击“选择”按钮,这次选择本文开始时导出的百度证书,然后单击“请求”按钮,如下图所示,这次响应是正确的。

img

通过在应用中内置指定服务端证书的方式,达到了只信任特定证书的目的,从而可以有效防范中间人的攻击。

3.服务端证书锁定示例编写

下面详细介绍创建该示例的步骤。

步骤1:创建Empty Ability项目。

步骤2:在module.json5配置文件加上对权限的声明:

"requestPermissions": [{"name": "ohos.permission.INTERNET"}]

这里添加了访问互联网的权限。

步骤3:在Index.ets文件里添加如下的代码:

import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import { BusinessError } from '@kit.BasicServicesKit';
import { http } from '@kit.NetworkKit';
import { cert } from '@kit.DeviceCertificateKit';
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';
​
@Entry
@Component
struct Index {@State title: string = '服务端证书锁定防范中间人攻击示例';//连接、通讯历史记录@State msgHistory: string = ''//请求的HTTPS地址@State httpsUrl: string = "https://www.baidu.com/"//是否锁定服务端证书@State isServerCertFixed: boolean = true//选择的锁定的证书文件@State fixedCertFileUri: string = ''//锁定证书的公钥哈希@State fixedCertPubKeyHash: string = ""scroller: Scroller = new Scroller()
​build() {Row() {Column() {Text(this.title).fontSize(14).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Center).padding(10)
​Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Column() {Text("是否锁定服务端证书").fontSize(14).width(150)}
​Column() {Text('不锁定').fontSize(14)Radio({ value: '0', group: 'rgFixed' }).checked(!this.isServerCertFixed).height(30).width(30).onChange((isChecked: boolean) => {if (isChecked) {this.isServerCertFixed = false}})}.width(100)
​Column() {Text('锁定').fontSize(14)Radio({ value: '1', group: 'rgFixed' }).checked(this.isServerCertFixed).height(30).width(30).onChange((isChecked: boolean) => {if (isChecked) {this.isServerCertFixed = true}})}.width(100)}.width('100%').padding(10)
​Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Text("锁定的服务端证书").fontSize(14).width(90).flexGrow(1)
​Button("选择").onClick(async () => {this.fixedCertFileUri = await this.selectFile()this.fixedCertPubKeyHash = await this.getPubKeyHash(this.fixedCertFileUri)}).width(70).fontSize(14)}.width('100%').padding(10).visibility(this.isServerCertFixed ? Visibility.Visible : Visibility.None)
​Text("服务端证书公钥SHA256摘要:").width('100%').fontSize(14).padding(10).visibility(this.isServerCertFixed ? Visibility.Visible : Visibility.None)
​Text(this.fixedCertPubKeyHash).width('100%').fontSize(14).padding(10).visibility(this.isServerCertFixed ? Visibility.Visible : Visibility.None)
​Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Text("请求地址:").fontSize(14).width(80)TextInput({ text: this.httpsUrl }).onChange((value) => {this.httpsUrl = value}).width(110).fontSize(12).flexGrow(1)Button("请求").onClick(() => {this.doHttpRequest()}).width(60).fontSize(14)}.width('100%').padding(10)
​Scroll(this.scroller) {Text(this.msgHistory).textAlign(TextAlign.Start).padding(10).width('100%').backgroundColor(0xeeeeee)}.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width('100%').justifyContent(FlexAlign.Start).height('100%')}.height('100%')}
​//发起http请求doHttpRequest() {//http请求对象let httpRequest = http.createHttp();
​let opt: http.HttpRequestOptions = {method: http.RequestMethod.GET,expectDataType: http.HttpDataType.STRING}
​//配置服务端证书PIN码if (this.isServerCertFixed) {let certPinning: http.CertificatePinning = { publicKeyHash: this.fixedCertPubKeyHash, hashAlgorithm: "SHA-256" }opt.certificatePinning = certPinning}
​httpRequest.request(this.httpsUrl, opt).then((resp) => {this.msgHistory += "响应码:" + resp.responseCode + "\r\n"this.msgHistory += '请求响应信息: ' + resp.result + "\r\n";}).catch((err: BusinessError) => {this.msgHistory += `请求失败:err code is ${err.code}, err message is ${JSON.stringify(err)}\r\n`;})}
​//选择文件async selectFile() {let selectFile = ""let documentPicker = new picker.DocumentViewPicker();await documentPicker.select().then((result) => {if (result.length > 0) {selectFile = result[0]this.msgHistory += "select file: " + selectFile + "\r\n";}}).catch((err: BusinessError) => {this.msgHistory += `选择文件失败:err code is ${err.code}, err message is ${JSON.stringify(err)}\r\n`;});return selectFile}
​//加载文件内容getContent(filePath: string): ArrayBuffer | undefined {let content: ArrayBuffer | undefined = undefinedtry {let buf = new ArrayBuffer(1024 * 64);let file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);let readLen = fs.readSync(file.fd, buf, { offset: 0 });content = buf.slice(0, readLen)fs.closeSync(file);} catch (e) {this.msgHistory += '读取文件内容失败 ' + e.message + "\r\n";}return content}
​//获取证书文件对应的公钥SHA256摘要async getPubKeyHash(filePath: string) {let result = ""if (filePath != "") {let fixedCert = await this.getCertFromFile(filePath)if (fixedCert) {try {//获取公钥let pubKey = fixedCert.getItem(cert.CertItemType.CERT_ITEM_TYPE_PUBLIC_KEY);let mdSHA256 = cryptoFramework.createMd("SHA256")mdSHA256.updateSync({ data: pubKey.data });//公钥摘要计算结果let mdResult = mdSHA256.digestSync();let tool = new util.Base64Helper()//公钥摘要转换为base64编码字符串result = tool.encodeToStringSync(mdResult.data)} catch (e) {this.msgHistory += '获取公钥摘要失败 ' + e.message + "\r\n";}}}return result}
​//从文件获取X509证书async getCertFromFile(filePath: string): Promise<cert.X509Cert | undefined> {let newCert: cert.X509Cert | undefined = undefinedlet certData = this.getContent(filePath);if (certData) {let encodingBlob: cert.EncodingBlob = {data: new Uint8Array(certData),encodingFormat: cert.EncodingFormat.FORMAT_PEM};
​await cert.createX509Cert(encodingBlob).then((x509Cert: cert.X509Cert) => {newCert = x509Cert}).catch((err: BusinessError) => {this.msgHistory += `创建X509证书失败:err code is ${err.code}, err message is ${JSON.stringify(err)}\r\n`;})}return newCert}
}

步骤4:编译运行,可以使用模拟器或者真机。

步骤5:按照本节第2部分“服务端证书锁定演示”操作即可。

4.代码分析

本示例的关键点有三处,第一处是根据选择的证书文件生成X509证书,方法为getCertFromFile,其中使用getContent方法读取文件内容;第二处为生成证书公钥的摘要,方法为getPubKeyHash,这里特别注意的是获取公钥内容的方法,不能使用X509Cert接口的getPublicKey方法,而是使用getItem方法,生成摘要时要使用SHA256算法,摘要结果要通过Base64编码后作为字符串使;第三处是设置请求属性的PIN码,这里只设置了一个PIN码,如果信任多个证书,可以设置多个。

(本文作者原创,除非明确授权禁止转载)

本文源码地址:

https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/http/CertificatePinningDemo

本系列源码地址:

https://gitee.com/zl3624/harmonyos_network_samples

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

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

相关文章

PHP企业门店订货通进销存系统小程序源码

订货通进销存系统&#xff0c;企业运营好帮手&#xff01; &#x1f4e6; 开篇&#xff1a;告别繁琐&#xff0c;企业运营新选择 嘿&#xff0c;各位企业主和创业者们&#xff01;今天我要给大家介绍一款超实用的企业运营神器——“订货通进销存系统”。在这个数字化时代&…

Docker入门之构建

Docker构建概述 Docker Build 实现了客户端-服务器架构&#xff0c;其中&#xff1a; 客户端&#xff1a;Buildx 是用于运行和管理构建的客户端和用户界面。服务器&#xff1a;BuildKit 是处理构建执行的服务器或构建器。 当您调用构建时&#xff0c;Buildx 客户端会向 Bui…

Element UI

Element ui 就是基于vue的一个ui框架,该框架基于vue开发了很多相关组件,方便我们快速开发页面。 官网: https://element.eleme.io/#/zh-CN 安装Element UI vue init webpack element(项目名)确认项目是否构建成功&#xff1a;进入到项目的根路径 执行 npm start 访问 h…

NSSCTF

[NSSRound#1 Basic]basic_check nikto扫描 nikto -h url PUT请求&#xff0c;如果不存在这个路径下的文件&#xff0c;将会创建&#xff0c;如果存在&#xff0c;会执行覆盖操作。 [NSSRound#8 Basic]MyDoor if (isset($_GET[N_S.S])) {eval($_GET[N_S.S]); } php特性&#…

形式架构定义语言(ADL)

简介 形式规范 多年来&#xff0c;学术界一直在试图通过使用与测试截然不同且更加主动的方法来确保程序语义的正确执行&#xff1a;形式化方法。研究者们认为这种方法通过更加精确、无二义性的描述来达到让程序绝对地按照设计者的思想执行的目的。这种思想早期体现在Floyd在1…

STM32之OLED驱动函数

类似51单片机中的LCD1602驱动差不多&#xff0c; 1.oled驱动代码 oled.c #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ #define OLED_W_SCL(x) GPIO_WriteBit(GPIOB, GPIO_Pin_8, (BitAction)(x)) #define OLED_W_SDA(x) GPIO_WriteBi…

Python入门(二)编程中的“真”与“假”,单双向选择的判断

编程中的“真”与“假” 在编程中&#xff0c;这种“真”、“假”状态我们用布尔数来表示&#xff0c;“真”是True&#xff0c;“假”是False。 另一种方式&#xff0c;是通过比较运算得到。 如图&#xff0c;3赋值给a&#xff0c;1赋值给b&#xff0c;进行大小的比较。 a &g…

U9的插件开发之BE插件(1)

U9插件可分为&#xff1a;BE插件、BP插件、UI插件&#xff1b; BE(Business Entity) 简单就是指实体&#xff0c;U9的元数据。 我的案例是设置BE默认值&#xff0c;即在单据新增时&#xff0c;设置单据某一个字段的默认值&#xff0c;具体如下&#xff1a; 1.插件开发工具&a…

Linux的目录结构 常用基础命令(2)

Linux的目录结构 根目录&#xff1a; 所有分区、目录、文件等的位置起点 整个树形目录结构中&#xff0c;使用独立的一个“/”表示 常见的子目录 /root /bin /boot /dev /etc /home /var /usr /sbin 基础知识 以 . 开头的文件均为隐藏文件 路径用/分开 / 不在第一位就…

plsql 高版本用不了 expaste 插件 问题

plsql 高版本用不了 expaste 插件 问题 其实不是版本问题&#xff0c;而是高版本的咩有在用这个插件&#xff0c;在另外一个功能里面&#xff0c; 查询你要的数据&#xff0c; 选择数据&#xff0c;右键&#xff0c;点 右键 复制为表达式列表&#xff0c;即可 在空白处粘贴…

【C++】C++11基础入门

目录 一、C11发展史&#xff1a; 二、列表初始化&#xff1a; 1、初始化&#xff1a; 2、initializer_list函数&#xff1a; 三、声明&#xff1a; 1、auto自动识别类型&#xff1a; 2、decltype&#xff1a; 3、nullptr&#xff1a; 四、范围for&#xff1a; 五、STL…

vue3+vue-baidu-map-3x 实现地图定位

文档地址&#xff1a;一个是2一个是3 https://dafrok.github.io/vue-baidu-map/#/zh/index vue-baidu-map-3x 1.首先要到百度地图开放平台上建一个账号&#xff0c;如果有百度账号可以直接登录百度地图-百万开发者首选的地图服务商,提供专属的行业解决方案 2.点击控制台&am…

V2X介绍

文章目录 什么是V2XV2X的发展史早期的DSRC后起之秀C-V2XC-V2X 和DSRC 两者的对比 什么是V2X 所谓V2X&#xff0c;与流行的B2B、B2C如出一辙&#xff0c;意为vehicle to everything&#xff0c;即车对外界的信息交换。车联网通过整合全球定位系统&#xff08;GPS&#xff09;导…

C#使用log4net结合sqlite数据库记录日志

0 前言 为什么要把日志存到数据库里? 因为结构化的数据库存储的日志信息,可以写专门的软件读取历史日志信息,通过各种条件筛选,可操作性极大增强,有这方面需求的开发人员可以考虑。 为什么选择SQLite? 轻量级数据库,免安装,数据库的常用的基本功能都有,可以随程序…

如何打开/解包星露谷物语XNB文件(附软件资源)

一、什么是 XNB 文件&#xff1f; 游戏将数据、地图和纹理存储在 .xnb 这种压缩数据文件中&#xff0c;它们在游戏的 Content 文件夹中。例如&#xff0c;对话期间显示的阿比盖尔的头像来自这个文件&#xff1a; Content\Portraits\Abigail.xnb。解包这个文件&#xff0c;你会…

SIP 业务举例之 Call Forwarding - No Answer(无应答呼叫转移)

目录 1. Call Forwarding - No Answer 简介 2. RFC5359 的 Call Forwarding - No Answer 信令流程 呼转开始 呼转完成 3. Call Forwording - No Answer 过程总结 博主wx:yuanlai45_csdn 博主qq:2777137742 想要 深入学习 5GC IMS 等通信知识(加入 51学通信),或者想要 …

ISO21434 信息安全开发流程咨询合规内容和步骤

ISO 21434是汽车网络安全风险管理的一项国际标准&#xff0c;旨在帮助汽车制造商和供应商识别、评估和管理车辆整个生命周期中的网络安全风险。以下是ISO 21434咨询可能包含的内容以及实施咨询的方法论步骤&#xff1a; 咨询内容&#xff1a; 标准解读与培训&#xff1a;帮助…

【Unity】Unity中文本中插入超链接且可点击响应,TextMeshPro的进阶用法

一、需求和尝试 今天遇到这样一个需求&#xff1a;在文本中插入超链接&#xff0c;且这个链接可以点击跳转对应的url&#xff0c;具体形式如下图所示。 其实这个有一个简单粗暴的方法&#xff0c;就是把需要加超链接的文本单独拿出来&#xff0c;和其他文本进行拼接&#xf…

【数据结构与算法】之队列详解

队列&#xff08;Queue&#xff09;是一种重要的线性数据结构&#xff0c;遵循先进先出、后进后出的原则。本文将更详细地介绍队列的概念、特点、Java 实现以及应用场景。 模运算小复习&#xff1a; a % b 的值总是小于b 5 % 4 1 5 % 2 1 1 % 5 1 4 % 5 4 1. 队列…

windows|常见的文件伪装方法

几种常见的文件伪装方法&#xff1a; 扩展名伪装unicode字符伪装压缩包伪装隐写术 方法仅限于学习目的&#xff0c;不用于任何恶意或非法用途。 ———— 一、扩展名伪装&#xff1a;假装是另一种类型的文件 修改文件的扩展名&#xff0c;使得文件看起来像其他类型的文件&a…