鸿蒙双向认证

鸿蒙双向认证

开发环境 基于API12

参考文档

切换到鸿蒙也要用上双向认证。使用的其中的 rcp 功能,详细文档https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/remote-communication-rcp-V5

双向认证包含两个方向,分为客户端验证服务端的证书和服务端验证客户端的证书。

客户端验证服务端的证书

这个就需要把服务端的证书内置在客户端里边,并且在请求的时候获取到服务端的证书,然后自己进行对比证书是否同内置的一样。

服务器证书的校验等级有以下几种:

  1. DefaultTrustEvaluator,使用默认的验证方式,验证证书的有效性,证书信任链那套
  2. RevocationTrustEvaluator,验证证书是否被吊销
  3. PinnedCertificatesTrustEvaluator,验证证书是否同本地的一致,可以是自签证书
  4. PublicKeysTrustEvaluator,验证证书的公钥,可以是自签证书,不过这个有个好处就是不用关心证书的有效期了
  5. CompositeTrustEvaluator,混合模式
  6. DisabledTrustEvaluator,不验证证书

可以按需自己处理。本文以对比公钥为例。

首先获取本地证书及证书的公钥。

javascript 复制代码
let context = getContext(this)
const getRawFileContent = (ctx: Context, file: string) : string => {
  let buffer = ctx.resourceManager.getRawFileContentSync(file).buffer
  return String.fromCharCode(...new Uint8Array(buffer))
}

function stringToUint8Array(str: string): Uint8Array {
  let arr: Array<number> = [];
  for (let i = 0, j = str.length; i < j; i++) {
    arr.push(str.charCodeAt(i));
  }
  return new Uint8Array(arr);
}

const uInt8ToString = (buffer: Uint8Array): string => {
  return String.fromCharCode(...new Uint8Array(buffer))
}

let serverCert: cert.X509Cert | null = null
let serverCertStr: string | null = null

cert.createX509Cert({
  data: stringToUint8Array(getRawFileContent(context, 'server.cer')),
  encodingFormat: cert.EncodingFormat.FORMAT_DER
}).then(x509Cert => {
  serverCert = x509Cert

  serverCertStr = uInt8ToString(serverCert?.getPublicKey().getEncoded().data)
}).catch((error: BusinessError) => {
})

增加自定义验证证书函数。对比公钥是否一致。

javascript 复制代码
{
  security: {
    remoteValidation: (context: rcp.ValidationContext) => {
      let tar = uInt8ToString(context.x509Certs[0].getPublicKey().getEncoded().data)
      if (serverCertStr === tar) {
        return true
      }
      return false
    },
  },
}

这样客户端就验证了服务端证书是否符合要求。

服务端验证客户端的证书

由于接口请求的问题,需要把客户端证书写入到沙盒里边,然后把沙盒地址传进去,就有点麻烦。

首先写入客户端证书到沙盒。

本文需要使用到crt及key两个文件。接口crt文件需要文本形式,但是key又需要沙盒地址。

javascript 复制代码
const getRawFileContent = (ctx: Context, file: string) : string => {
  let buffer = ctx.resourceManager.getRawFileContentSync(file).buffer
  return String.fromCharCode(...new Uint8Array(buffer))
}

let context = getContext(this)
let filesDir = context.filesDir

function saveFile(fn: string) {
  let file = fs.openSync(filesDir + '/' + fn, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
  let clientContent = context.resourceManager.getRawFileContentSync(fn)
  fs.writeSync(file.fd, clientContent.buffer)
  fs.fsyncSync(file.fd)
  fs.closeSync(file)
}

saveFile('client.key')

然后提供客户端证书给服务端进行校验。

javascript 复制代码
{
  security: {
    certificate: {
      content: getRawFileContent(context, 'client.crt'),
      key: filesDir + '/client.key',
      type: 'PEM',
      keyPassword: 'xxx'
    },
  },
}

这样就能把证书提供给服务端进行校验了。

完整实例

javascript 复制代码
import { BusinessError } from '@ohos.base';
import { rcp } from "@kit.RemoteCommunicationKit";
import fs from '@ohos.file.fs';
import { cert } from '@kit.DeviceCertificateKit';
import { cryptoFramework } from '@kit.CryptoArchitectureKit';

const getRawFileContent = (ctx: Context, file: string) : string => {
  let buffer = ctx.resourceManager.getRawFileContentSync(file).buffer
  return String.fromCharCode(...new Uint8Array(buffer))
}

let context = getContext(this)
let filesDir = context.filesDir

function saveFile(fn: string) {
  let file = fs.openSync(filesDir + '/' + fn, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
  let clientContent = context.resourceManager.getRawFileContentSync(fn)
  fs.writeSync(file.fd, clientContent.buffer)
  fs.fsyncSync(file.fd)
  fs.closeSync(file)
}

saveFile('client.key')

function stringToUint8Array(str: string): Uint8Array {
  let arr: Array<number> = [];
  for (let i = 0, j = str.length; i < j; i++) {
    arr.push(str.charCodeAt(i));
  }
  return new Uint8Array(arr);
}

const uInt8ToString = (buffer: Uint8Array): string => {
  return String.fromCharCode(...new Uint8Array(buffer))
}

let serverCert: cert.X509Cert | null = null
let serverCertStr: string | null = null

cert.createX509Cert({
  data: stringToUint8Array(getRawFileContent(context, 'server.cer')),
  encodingFormat: cert.EncodingFormat.FORMAT_DER
}).then(x509Cert => {
  serverCert = x509Cert

  serverCertStr = uInt8ToString(serverCert?.getPublicKey().getEncoded().data)
}).catch((error: BusinessError) => {
  console.error('createX509Cert failed, errCode: ' + error.code + ', errMsg: ' + error.message);
})

const defaultTimeout: number = 60*1000

const createRequestConfiguration = (timeout: number): rcp.Configuration => {
  return {
    security: {
      remoteValidation: (context: rcp.ValidationContext) => {
        let tar = uInt8ToString(context.x509Certs[0].getPublicKey().getEncoded().data)
        if (serverCertStr === tar) {
          return true
        }
        return false
      },
      certificate: {
        content: getRawFileContent(context, 'client.crt'),
        key: filesDir + '/client.key',
        type: 'PEM',
        keyPassword: 'xxx'
      },
    },
    transfer: {
      autoRedirect: true,
      timeout: {
        connectMs: 5000,
        transferMs: timeout,
      }
    }
  }
}

const sessionConfig: rcp.SessionConfiguration = {
  requestConfiguration: createRequestConfiguration(defaultTimeout)
}

const session: rcp.Session = rcp.createSession(sessionConfig)

export const rcpget = (url:string, timeout: number = defaultTimeout):Promise<object> => {
  return new Promise((resolve, reject) => {
    let request = new rcp.Request(url, 'GET', getHeaders())
    if (timeout != defaultTimeout) {
      request.configuration = createRequestConfiguration(timeout)
    }
    session.fetch(request).then((response:rcp.Response) => {
      if (response.statusCode == 200) {
        resolve(data)
      } else {
        reject()
      }
    }).catch((err: BusinessError) => {
      reject()
    })
  })
}

export const rcppost = (url: string, params: Record<string, string | number>, timeout: number = defaultTimeout):Promise<object> => {
  let p: string[] = []
  for(let kv of Object.entries(params)) {
    p.push(`${kv[0]}=${kv[1]}`)
  }
  let pstr: string = p.join('&')
  return new Promise((resolve, reject) => {
    let request: rcp.Request
    if (pstr == '') {
      request = new rcp.Request(url, 'POST', getHeaders())
    } else {
      request = new rcp.Request(url, 'POST', getHeaders(), pstr)
    }
    if (timeout != defaultTimeout) {
      request.configuration = createRequestConfiguration(timeout)
    }
    session.fetch(request).then((response:rcp.Response) => {
      if (response.statusCode == 200) {
        resolve(data)
      } else {
        reject()
      }
    }).catch((err: BusinessError) => {
      reject()
    })
  })
}

如此这般就能实现双向认证。感觉安全级别又上了一个等级。

相关推荐
互联网散修25 分钟前
零基础鸿蒙应用开发第三十二节:JSON核心基础与文件的读写
华为·json·harmonyos
我的世界洛天依30 分钟前
胡桃讲编程:华为鸿蒙系统能用 MT 管理器反编译吗?(安装包 + 系统根目录实战指南)
华为·harmonyos·机器翻译
李李李勃谦1 小时前
Flutter 框架跨平台鸿蒙开发 - 鲜花礼品配送
flutter·华为·harmonyos
2301_822703201 小时前
鸿蒙Flutter三方库适配:Flutter Markdown适配实战-鸿蒙平台的Markdown渲染解决方案
flutter·华为·信息可视化·开源·harmonyos·鸿蒙·三方库
李李李勃谦2 小时前
Flutter 框架跨平台鸿蒙开发 - 蛋糕甜品预订
flutter·华为·harmonyos
HwJack202 小时前
HarmonyOS `hitTestBehavior` 与 `HitTestMode.Block`:揭开事件穿透与拦截的底层暗流
华为·harmonyos
Ww.xh2 小时前
ArkTS重构:Android转HarmonyOS核心要点
华为·harmonyos
_waylau3 小时前
鸿蒙架构师修炼之道-B/S与C/S架构
华为·架构·harmonyos·鸿蒙·鸿蒙系统
Swift社区3 小时前
鸿蒙 vs iOS / Android:谁更适合 AI?
android·ios·harmonyos
雷帝木木3 小时前
Flutter 组件 http_interop 的适配 鸿蒙Harmony 深度进阶 - 驾驭多级拦截器链、实现鸿蒙端标准化通讯审计与流量路由中继方案
flutter·harmonyos·鸿蒙·openharmony·http_interop