鸿蒙双向认证

鸿蒙双向认证

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

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

相关推荐
2501_919749033 小时前
配置flutter鸿蒙的环境和创建并运行第一个flutter鸿蒙项目【精心制作】
flutter·华为·harmonyos
Fanmeang5 小时前
华为交换机VLAN技术详解:从基础到高级应用
运维·网络·华为·vlan·交换机·mux vlan
Fanmeang5 小时前
华为路由器核心技术详解:数据包的智能导航系统
运维·网络·华为·路由器·路由表·路由协议
赵得C9 小时前
智能体的范式革命:华为全栈技术链驱动下一代AI Agent
人工智能·华为·ai·ai编程
Fanmeang11 小时前
华为防火墙基础功能详解:构建网络安全的基石
运维·网络·安全·华为·防火墙·策略·安全域
爱笑的眼睛1112 小时前
深入解析ArkTS类型系统:构建安全高效的HarmonyOS应用
华为·harmonyos
Android疑难杂症12 小时前
鸿蒙Media Kit媒体服务开发快速指南
android·harmonyos·音视频开发
国霄13 小时前
(3)Kotlin/Js For Harmony——解决官方库序列化卡顿
harmonyos
光芒Shine13 小时前
【HarmonyOS-北向开发(软件)】
harmonyos
猫林老师15 小时前
Flutter for HarmonyOS开发指南(四):国际化与本地化深度实践
flutter·华为·harmonyos