鸿蒙网络编程系列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 = undefined
    try {
      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 = undefined
    let 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux基础项目开发day06:量产工具——业务系统

文章目录 前言一、流程代码框架1、业务系统框架流程2、主页面流程图3、main.c实现流程 二、处理配置文件1、配置文件是啥&#xff1f;config.h 2、怎么处理配置文件&#xff1f;config.c 三、生成界面1、计算每个按钮的Region2、逐个生成按钮画面->生成页面 四、读取输入事件…

记录一次hiveserver2卡死(假死)问题

问题描述 给开发人员开通了个账号&#xff0c;连接hive进行查询&#xff0c;后来发现&#xff0c;hive服务有时候会卡死&#xff0c;查询不了&#xff0c;连不上&#xff08;所有账号/客户端都连不上hive&#xff09;&#xff0c;但在chd里面看监控&#xff0c;服务器资源状态…

物联网之超声波测距模块、arduino、esp32

MENU 原理硬件电路设计软件程序设计 原理 超声波是一种频率高于20000Hz的声波&#xff0c;功率密度为p≥0.3W/cm&#xff0c;它的方向性好&#xff0c;反射能力强&#xff0c;易于获得较集中的声能。超声波用于许多不同的领域&#xff0c;比如检测物体和测量距离&#xff0c;清…

Unity 2d UI 实时跟随场景3d物体

2d UI 实时跟随场景3d物体位置&#xff0c;显示 3d 物体头顶信息&#xff0c;看起来像是场景中的3dUI&#xff0c;实质是2d UIusing System.Collections; using System.Collections.Generic; using UnityEngine; using DG.Tweening; using UnityEngine.UI; /// <summary>…

【JS】无法阻止屏幕滚动

监听滚轮事件&#xff0c;阻止默认行为&#xff0c;但未生效&#xff0c;且控制台报错。 window.addEventListener(wheel, (e) > {e.preventDefault(); })这是因为现代浏览器使用 Passive 事件监听器&#xff0c;默认启用了 passive 模式以确保性能&#xff0c;不会调用 pr…

Cancer Cell|最新发表的单细胞成纤维细胞分析代码,速来学习!!!

简介 成纤维细胞在维持组织稳态、应对炎症和纤维化状况、帮助伤口愈合以及促进癌症进展的复杂舞蹈中起着关键作用。在癌症领域&#xff0c;成纤维细胞已成为肿瘤微环境&#xff08;TME&#xff09;中的核心人物&#xff0c;发挥着多方面的作用。这些作用包括细胞外基质&#xf…

【深度学习实战—12】:基于MediaPipe的手势识别

✨博客主页&#xff1a;王乐予&#x1f388; ✨年轻人要&#xff1a;Living for the moment&#xff08;活在当下&#xff09;&#xff01;&#x1f4aa; &#x1f3c6;推荐专栏&#xff1a;【图像处理】【千锤百炼Python】【深度学习】【排序算法】 目录 &#x1f63a;一、Med…

Java设计模式梳理:行为型模式(策略,观察者等)

行为型模式 行为型模式关注的是各个类之间的相互作用&#xff0c;将职责划分清楚&#xff0c;使得我们的代码更加地清晰。 策略模式 策略模式太常用了&#xff0c;所以把它放到最前面进行介绍。它比较简单&#xff0c;我就不废话&#xff0c;直接用代码说事吧。 下面设计的…

电能表预付费系统-标准传输规范(STS)(16)

6.3.9 MPL: MaximumPowerLimit&#xff08;最大功率限制&#xff09; The maximum power limit field is a 1 6-bit field that indicates the maximum power that the load may draw, in watts. Calculation of this field is identical to that of the TransferAmount field…

【JavaEE】——自定义协议方案、UDP协议

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;自定义协议 1&#xff1a;自定义协议 &#xff08;1&#xff09;交互哪些信息 &…

数据库设计与开发—初识SQLite与DbGate

一、SQLite与DbGate简介 &#xff08;一&#xff09;SQLite[1][3] SQLite 是一个部署最广泛、用 C 语言编写的数据库引擎&#xff0c;属于嵌入式数据库&#xff0c;其作为库被软件开发人员嵌入到应用程序中。 SQLite 的设计允许在不安装数据库管理系统或不需要数据库管理员的情…

C++中的继承(1)

1.继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许我们在保持原有类特性的基础上进行扩展&#xff0c;增加⽅法(成员函数)和属性(成员变量)&#xff0c;这样产生新的类&#xff0c;称派生类&#xff08;也被称为子类&…

Vue 3集成海康Web插件实现视频监控

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;组件封装篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来组件封装篇专栏内容:Vue 3集成海康Web插件实现视频监控 引言 最近在项目中使用了 Vue 3 结合海康Web插件来实…

Django项目创建

安装 pip install django 创建项目 首先打开powershell打开项目与创建到的文件夹 django-admin startproject django_demo01 django-admin startproject 项目名 多出了一个django_demo01的文件夹&#xff0c;这就是我们的项目了 打开项目文件夹&#xff0c;发现一个文件和…

必读推荐:掌握大模型应用的精华书籍,非常详细收藏我这一篇就够了

在这个信息爆炸的时代&#xff0c;人工智能正以前所未有的速度和规模渗透到我们生活的方方面面。其中&#xff0c;大模型应用作为 AI 领域的一大亮点&#xff0c;不仅在学术界引起广泛关注&#xff0c;更在工业界展现出巨大的应用潜力。从自然语言处理到图像识别&#xff0c;从…

最新必应Bing开户条件、流程及注意事项介绍

有效的广告推广对于企业提升品牌影响力和市场占有率至关重要。微软旗下的必应Bing搜索引擎&#xff0c;作为全球知名的搜索平台之一&#xff0c;为企业提供了精准、高效的广告推广服务。那么&#xff0c;如何在必应上开设广告账户呢&#xff1f;下面将详细介绍必应广告开户的条…

springboot 整合 快手 移动应用 授权 发布视频 小黄车

前言&#xff1a; 因快手文档混乱&#xff0c;官方社区技术交流仍有很多未解之谜&#xff0c;下面3种文档的定义先区分。 代码中的JSON相关工具均用hutool工具包 1.快手 移动双端 原生SDK 文档https://mp.kuaishou.com/platformDocs/develop/mobile-app/ios.html 2.快手 Api 开…

探索光耦:光耦——不间断电源(UPS)系统中的安全高效卫士

在现代社会&#xff0c;不间断电源&#xff08;UPS&#xff09;系统已成为保障关键设备和数据安全的关键设施&#xff0c;广泛应用于企业数据中心、家庭电子设备等场景。UPS能在电力中断或波动时提供稳定电力&#xff0c;确保设备持续运行。而在这套系统中&#xff0c;光耦&…

深入理解AQS:并发编程中的利器及其在业务场景中的应用

1. 什么是AQS&#xff08;AbstractQueuedSynchronizer&#xff09;&#xff1f; AQS&#xff0c;全称为AbstractQueuedSynchronizer&#xff0c;是Java并发包中核心的基础框架&#xff0c;用于构建锁和同步器。它是java.util.concurrent.locks包中的基础组件&#xff0c;为多个…

屏幕画面卡住不动声音正常怎么办?电脑屏幕卡住不动解决方法

在数字时代&#xff0c;电脑作为我们日常生活与工作中不可或缺的伙伴&#xff0c;偶尔也会遇到一些小状况。其中&#xff0c;“屏幕画面卡住不动&#xff0c;但是声音依然正常”的情况就是一种常见的问题。本文将探讨这一现象的原因&#xff0c;并提供几种可能的解决方案&#…