鸿蒙双向认证

鸿蒙双向认证

开发环境 基于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()
    })
  })
}

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

相关推荐
小诸葛的博客3 小时前
华为ensp实现跨vlan通信
网络·华为·智能路由器
康康这名还挺多5 小时前
鸿蒙HarmonyOS list优化一: list 结合 lazyforeach用法
数据结构·list·harmonyos·lazyforeach
晚秋大魔王8 小时前
OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——nettle库
linux·开源·harmonyos
python算法(魔法师版)12 小时前
.NET 在鸿蒙系统上的适配现状
华为od·华为·华为云·.net·wpf·harmonyos
bestadc13 小时前
鸿蒙 UIAbility组件与UI的数据同步和窗口关闭
harmonyos
枫叶丹414 小时前
【HarmonyOS Next之旅】DevEco Studio使用指南(二十二)
华为·harmonyos·deveco studio·harmonyos next
ax一号街阿楠16 小时前
华为FAT AP配置 真机
网络·华为·智能路由器
吗喽对你问好16 小时前
华为5.7机考第一题充电桩问题Java代码实现
java·华为·排序
乱世刀疤18 小时前
深度 |国产操作系统“破茧而出”:鸿蒙电脑填补自主生态空白
华为·harmonyos
博睿谷IT99_1 天前
华为HCIP-AI认证考试版本更新通知
人工智能·华为