鸿蒙next开发实战系列:TLS安全数据传输双向认证规范

654 篇文章 4 订阅
648 篇文章 6 订阅

目录

 1.TLS双向认证

 2.TLS双向认证通讯示例

其他鸿蒙开发文章:

最后


 1.TLS双向认证

在上一篇文章鸿蒙开发实战系列-TLS安全数据传输单向认证示例中,演示了单向认证的方式,也就是客户端需要验证服务端的数字证书,而服务端不需要验证客户端的数字证书,这能满足大部分业务场景,但是,在一些高安全性需求的场景下,服务端也需要确保客户端是可信的,这就要求双向认证,也就是除了客户端验证服务端证书外,服务端也需要验证客户端的数字证书。

要实现双向的认证,就需要服务端在握手时提出客户端的数字证书认证需求,以ECDHE算法的握手过程为例,在第二次握手时,需要服务端发送Certificate Request消息给客户端,表明是双向认证的,在第三次握手时,客户端发送Certificate消息给服务端,其中就包含证书信息。

 2.TLS双向认证通讯示例

本文将实现一个双向认证的示例,应用运行后的界面如图所示:

在这个应用里,用户可以选择服务端的CA文件,作为验证服务端证书有效性的依据;还需要选择客户端证书文件和客户端私钥文件,用来执行服务端对客户端的认证。这三个文件都选择并加载后,就可以单击“连接”按钮连接服务端了,连接成功后可以发送消息给服务端。

下面详细介绍创建该应用的步骤。

步骤1:创建Empty Ability项目。

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

"requestPermissions": [

      {

        "name": "ohos.permission.INTERNET"

      },

      {

        "name": "ohos.permission.GET_WIFI_INFO"

      }

    ]

这里分别添加了访问互联网和访问WIFI信息的权限。

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

import socket from '@ohos.net.socket';
import wifiManager from '@ohos.wifiManager';
import systemDateTime from '@ohos.systemDateTime';
import util from '@ohos.util';
import picker from '@ohos.file.picker';
import fs from '@ohos.file.fs';
import common from '@ohos.app.ability.common';

//执行TLS通讯的对象
let tlsSocket = socket.constructTLSSocketInstance()

//说明:本地的IP地址不是必须知道的,绑定时绑定到IP:0.0.0.0即可,显示本地IP地址的目的是方便对方发送信息过来
//本地IP的数值形式
let ipNum = wifiManager.getIpInfo().ipAddress
//本地IP的字符串形式
let localIp = (ipNum >>> 24) + '.' + (ipNum >> 16 & 0xFF) + '.' + (ipNum >> 8 & 0xFF) + '.' + (ipNum & 0xFF);

//服务端ca证书文件地址
let caFileUri = ''

//客户端证书文件地址
let certFileUri = ''

//客户端私钥文件地址
let keyFileUri = ''

@Entry
@Component
struct Index {
  //连接、通讯历史记录
  @State msgHistory: string = ''
  //要发送的信息
  @State sendMsg: string = ''
  //服务端IP地址
  @State serverIp: string = "0.0.0.0"
  //服务端端口
  @State serverPort: number = 9999
  //是否可以加载CA
  @State caCanLoad: boolean = false
  //是否已加载CA
  @State caLoaded: boolean = false
  //是否可以加载证书
  @State certCanLoad: boolean = false
  //是否已加载证书
  @State certLoaded: boolean = false
  //是否可以加载私钥
  @State keyCanLoad: boolean = false
  //是否已加载私钥
  @State keyLoaded: boolean = false
  //是否可以发送消息
  @State canSend: boolean = false
  //服务端证书
  @State ca: string = ``
  //客户端证书
  @State cert: string = ``
  //客户端私钥
  @State privateKey: string = ``
  scroller: Scroller = new Scroller()

  build() {
    Row() {
      Column() {
        Text("TLS通讯示例")
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .width('100%')
          .textAlign(TextAlign.Center)
          .padding(10)

        Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          Text("本地IP地址:")
            .width(100)
            .fontSize(14)
            .flexGrow(0)
          Text(localIp)
            .width(110)
            .fontSize(12)
            .flexGrow(1)

        }.width('100%')
        .padding(10)

        Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          Text("服务端地址:")
            .fontSize(14)
            .width(90)
            .flexGrow(1)

          TextInput({ text: this.serverIp })
            .onChange((value) => {
              this.serverIp = value
            })
            .width(110)
            .fontSize(12)
            .flexGrow(4)

          Text(":")
            .width(5)
            .flexGrow(0)

          TextInput({ text: this.serverPort.toString() })
            .type(InputType.Number)
            .onChange((value) => {
              this.serverPort = parseInt(value)
            })
            .fontSize(12)
            .flexGrow(2)
            .width(50)
        }
        .width('100%')
        .padding(10)

        Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          Text("服务端CA:")
            .fontSize(14)
            .width(90)
            .flexGrow(1)

          Button("选择")
            .onClick(() => {
              this.selectCA()
            })
            .width(70)
            .fontSize(14)

          Button("加载")
            .onClick(() => {
              this.loadCA()
            })
            .enabled(this.caCanLoad)
            .width(70)
            .fontSize(14)
        }
        .width('100%')
        .padding(10)

        Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          Text("客户端证书:")
            .fontSize(14)
            .width(90)
            .flexGrow(1)

          Button("选择")
            .onClick(() => {
              this.selectCert()
            })
            .width(70)
            .fontSize(14)

          Button("加载")
            .onClick(() => {
              this.loadCert()
            })
            .enabled(this.caCanLoad)
            .width(70)
            .fontSize(14)
        }
        .width('100%')
        .padding(10)

        Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          Text("客户端私钥:")
            .fontSize(14)
            .width(90)
            .flexGrow(1)

          Button("选择")
            .onClick(() => {
              this.selectKey()
            })
            .width(70)
            .fontSize(14)

          Button("加载")
            .onClick(() => {
              this.loadKey()
            })
            .enabled(this.caCanLoad)
            .width(70)
            .fontSize(14)

          Button("连接")
            .onClick(() => {
              this.connect2Server()
            })
            .enabled(this.caLoaded && this.keyLoaded && this.certLoaded)
            .width(70)
            .fontSize(14)
        }
        .width('100%')
        .padding(10)

        Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
          TextInput({ placeholder: "输入要发送的消息" }).onChange((value) => {
            this.sendMsg = value
          })
            .width(200)
            .flexGrow(1)

          Button("发送")
            .enabled(this.canSend)
            .width(70)
            .fontSize(14)
            .flexGrow(0)
            .onClick(() => {
              this.sendMsg2Server()
            })
        }
        .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%')
  }

  //发送消息到服务端
  sendMsg2Server() {
    tlsSocket.send(this.sendMsg + "\r\n")
      .then(async () => {
        this.msgHistory += "我:" + this.sendMsg + await getCurrentTimeString() + "\r\n"
      })
      .catch((e) => {
        this.msgHistory += '发送失败' + e.message + "\r\n";
      })
  }

  //绑定本地地址
  async bind2LocalAddress() {
    //本地地址
    let localAddress = { address: "0.0.0.0", family: 1 }

    await tlsSocket.bind(localAddress)
      .then(() => {
        this.msgHistory += 'bind success' + "\r\n";
      })
      .catch((e) => {
        this.msgHistory += 'bind fail ' + e.message + "\r\n";
      })

    //收到消息时的处理
    tlsSocket.on("message", async (value) => {
      let msg = buf2String(value.message)
      let time = await getCurrentTimeString()
      this.msgHistory += "服务端:" + msg + time + "\r\n"
      this.scroller.scrollEdge(Edge.Bottom)
    })
  }

  //选择CA证书文件
  selectCA() {
    let documentPicker = new picker.DocumentViewPicker();
    documentPicker.select().then((result) => {
      if (result.length > 0) {
        caFileUri = result[0]
        this.msgHistory += "select file: " + caFileUri + "\r\n";
        this.caCanLoad = true
      }
    }).catch((e) => {
      this.msgHistory += 'DocumentViewPicker.select failed ' + e.message + "\r\n";
    });
  }

  //选择客户端证书文件
  selectCert() {
    let documentPicker = new picker.DocumentViewPicker();
    documentPicker.select().then((result) => {
      if (result.length > 0) {
        certFileUri = result[0]
        this.msgHistory += "select file: " + certFileUri + "\r\n";
        this.certCanLoad = true
      }
    }).catch((e) => {
      this.msgHistory += 'DocumentViewPicker.select failed ' + e.message + "\r\n";
    });
  }

  //选择私钥文件
  selectKey() {
    let documentPicker = new picker.DocumentViewPicker();
    documentPicker.select().then((result) => {
      if (result.length > 0) {
        keyFileUri = result[0]
        this.msgHistory += "select file: " + keyFileUri + "\r\n";
        this.keyCanLoad = true
      }
    }).catch((e) => {
      this.msgHistory += 'DocumentViewPicker.select failed ' + e.message + "\r\n";
    });
  }

  //加载CA文件内容
  loadCA() {
    try {
      this.ca = this.readStringFromFile(caFileUri)
      this.caLoaded = true
    }
    catch (e) {
      this.msgHistory += 'readText failed ' + e.message + "\r\n";
    }
  }

  //加载私钥文件内容
  loadKey() {
    try {
      this.privateKey = this.readStringFromFile(keyFileUri)
      this.keyLoaded = true
    }
    catch (e) {
      this.msgHistory += 'readText failed ' + e.message + "\r\n";
    }
  }

  //加载客户端证书文件内容
  loadCert() {
    try {
      this.cert = this.readStringFromFile(certFileUri)
      this.certLoaded = true
    }
    catch (e) {
      this.msgHistory += 'readText failed ' + e.message + "\r\n";
    }
  }

  //从文件读取字符串内容
  readStringFromFile(fileUri: string): string {
    let buf = new ArrayBuffer(1024 * 4);
    let file = fs.openSync(fileUri, fs.OpenMode.READ_ONLY);
    let readLen = fs.readSync(file.fd, buf, { offset: 0 });
    let result = buf2String(buf.slice(0, readLen))
    fs.closeSync(file);
    return result
  }

  //连接服务端
  async connect2Server() {
    //绑定本地地址
    await this.bind2LocalAddress()

    //服务端地址
    let serverAddress = { address: this.serverIp, port: this.serverPort, family: 1 }
    //tls选项
    let opt: socket.TLSSecureOptions = {
      ca: [this.ca],
      cert: this.cert,
      key: this.privateKey
    }

    await tlsSocket.connect({ address: serverAddress, secureOptions: opt })
      .then(() => {
        this.msgHistory = 'connect success ' + "\r\n";
        this.canSend = true
      })
      .catch((e) => {
        this.msgHistory = 'connect fail ' + e.message + "\r\n";
      })
  }
}

//同步获取当前时间的字符串形式
async function getCurrentTimeString() {
  let time = ""
  await systemDateTime.getDate().then(
    (date) => {
      time = date.getHours().toString() + ":" + date.getMinutes().toString()
        + ":" + date.getSeconds().toString()
    }
  )
  return "[" + time + "]"
}

//ArrayBuffer转utf8字符串
function buf2String(buf: ArrayBuffer) {
  let msgArray = new Uint8Array(buf);
  let textDecoder = util.TextDecoder.create("utf-8");
  return textDecoder.decodeWithStream(msgArray)
}

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

步骤5:加载服务端CA、客户端证书、客户端私钥文件,当然需要确保服务端已经启动了,然后配置服务端地址,再单击“连接”按钮执行连接,如图所示:

连接成功后,如果在服务端监听,可以看到如下的连接过程信息:

其中,使用框线标出的即是服务端发送给客户端的认证请求信息以及客户端发送给服务端的证书信息。

步骤6:输入要发送的信息,单击“发送”按钮即可发送信息到服务端,如下图所示:

其他鸿蒙开发文章:

HarmonyOS开发实战:TLS安全数据传输单向规范-CSDN博客

HarmonyOS开发实战:邮件传输协议客户端-Smtp-CSDN博客

HarmonyOS开发实战:邮件传输协议客户端-Smtp-CSDN博客


最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

点击领取→纯血版全套鸿蒙HarmonyOS学习资料希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取~

鸿蒙(HarmonyOS NEXT)最新学习路线

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

HarmonyOS Next 最新全套视频教程

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

大厂面试必问面试题

鸿蒙南向开发技术

鸿蒙APP开发必备


请点击→纯血版全套鸿蒙HarmonyOS学习资料

总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。 

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值