常见HarmonyOS开发中自签名证书处理:企业内网HTTPS

常见HarmonyOS开发中自签名证书处理:企业内网HTTPS

当正规军的CA证书无法覆盖你的内网服务,自签名证书就是你的民兵武装

一、背景与动机:为什么需要自签名证书?

正规HTTPS证书由权威CA机构(如DigiCert、Let's Encrypt)签发,浏览器和操作系统默认信任这些CA。但企业内网场景下,这套机制遇到了问题:

内网域名无法验证 :CA只签发公网域名证书,192.168.1.100internal.company.local这种内网地址,CA无法验证所有权。

成本考虑:企业可能有几十上百个内网服务,每个都买证书成本太高。Let's Encrypt虽然免费,但不支持内网域名。

离线环境:某些安全级别高的企业,内网完全隔离,无法连接外部CA进行验证。

开发测试 :本地开发环境需要HTTPS测试,但不可能为localhost申请证书。

自签名证书就是解决这些问题的方案------自己给自己签发证书,不需要CA参与。但问题来了:既然不是CA签发的,系统默认不信任,如何让鸿蒙应用信任自签名证书呢?

二、核心原理:自签名证书的信任链

2.1 正规证书 vs 自签名证书

#mermaid-svg-gm2WnYKzJ1Y6ZsCd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .error-icon{fill:#552222;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .marker.cross{stroke:#333333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd p{margin:0;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .cluster-label text{fill:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .cluster-label span{color:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .cluster-label span p{background-color:transparent;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .label text,#mermaid-svg-gm2WnYKzJ1Y6ZsCd span{fill:#333;color:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node rect,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node circle,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node ellipse,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node polygon,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .rough-node .label text,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node .label text,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .image-shape .label,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .icon-shape .label{text-anchor:middle;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .rough-node .label,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node .label,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .image-shape .label,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .icon-shape .label{text-align:center;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node.clickable{cursor:pointer;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .arrowheadPath{fill:#333333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .cluster text{fill:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .cluster span{color:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd rect.text{fill:none;stroke-width:0;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .icon-shape,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .icon-shape p,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .icon-shape .label rect,#mermaid-svg-gm2WnYKzJ1Y6ZsCd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .primary>*{fill:#4A90E2!important;stroke:#2E5C8A!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .primary span{fill:#4A90E2!important;stroke:#2E5C8A!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .primary tspan{fill:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .warning>*{fill:#F5A623!important;stroke:#C17D10!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .warning span{fill:#F5A623!important;stroke:#C17D10!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .warning tspan{fill:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .error>*{fill:#D0021B!important;stroke:#8B0012!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .error span{fill:#D0021B!important;stroke:#8B0012!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .error tspan{fill:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .info>*{fill:#7ED321!important;stroke:#5BA318!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .info span{fill:#7ED321!important;stroke:#5BA318!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-gm2WnYKzJ1Y6ZsCd .info tspan{fill:#fff!important;} 自签名证书链
系统不信任
手动导入信任库
自签名证书

internal.company.local
❌ 无CA签发
❌ 校验失败
✅ 手动信任
正规证书链
系统内置信任
根证书

DigiCert Root CA
中间证书

DigiCert Intermediate
服务器证书

api.example.com
✅ 自动信任

2.2 信任自签名证书的方式

方式 适用场景 安全性 实现难度
系统信任库导入 企业设备统一管理 低(运维操作)
应用内证书固定 特定应用信任 中(代码实现)
自定义校验逻辑 灵活控制 低-高 高(需谨慎)
忽略证书错误(不推荐) 仅开发测试 极低

2.3 自签名证书生成

在讨论鸿蒙如何处理之前,先了解如何生成自签名证书。

bash 复制代码
# 使用OpenSSL生成自签名证书
# 1. 生成私钥
openssl genrsa -out server.key 2048

# 2. 生成证书签名请求(CSR)
openssl req -new -key server.key -out server.csr \
  -subj "/CN=internal.company.local/O=MyCompany/C=CN"

# 3. 生成自签名证书(有效期365天)
openssl x509 -req -days 365 -in server.csr \
  -signkey server.key -out server.crt

# 4. 查看证书信息
openssl x509 -in server.crt -text -noout

# 5. 导出为PKCS12格式(某些服务器需要)
openssl pkcs12 -export -out server.p12 \
  -inkey server.key -in server.crt \
  -password pass:yourpassword

三、代码实战:三种处理方案

场景一:应用内嵌入证书文件

将自签名证书打包到应用中,在请求时使用。

typescript 复制代码
import http from '@ohos.net.http';
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';

// 证书管理器
class SelfSignedCertManager {
  private certificates: Map<string, Uint8Array> = new Map();
  
  // 从应用资源加载证书
  async loadCertificate(hostname: string, certPath: string): Promise<boolean> {
    try {
      // 读取证书文件
      let file = fs.openSync(certPath, fs.OpenMode.READ_ONLY);
      let stat = fs.statSync(certPath);
      let buffer = new ArrayBuffer(stat.size);
      fs.readSync(file.fd, buffer);
      fs.closeSync(file);
    
      // 转换为Uint8Array并存储
      let certData = new Uint8Array(buffer);
      this.certificates.set(hostname, certData);
    
      console.info(`证书加载成功: ${hostname}`);
      return true;
    
    } catch (error) {
      console.error(`证书加载失败: ${hostname}`, error);
      return false;
    }
  }
  
  // 获取指定主机的证书
  getCertificate(hostname: string): Uint8Array | undefined {
    return this.certificates.get(hostname);
  }
  
  // 获取所有已加载证书的主机
  getTrustedHosts(): string[] {
    return Array.from(this.certificates.keys());
  }
}

// 自签名证书HTTP客户端
class SelfSignedHttpClient {
  private certManager: SelfSignedCertManager;
  private defaultTimeout: number = 30000;
  
  constructor(certManager: SelfSignedCertManager) {
    this.certManager = certManager;
  }
  
  // 发起HTTPS请求(使用自签名证书)
  async request(
    url: string,
    options: http.HttpRequestOptions
  ): Promise<http.HttpResponse | null> {
  
    let httpRequest = http.createHttp();
  
    try {
      // 解析URL获取主机名
      let hostname = this.extractHostname(url);
    
      // 检查是否有对应的自签名证书
      let certData = this.certManager.getCertificate(hostname);
    
      if (certData) {
        console.info(`使用自签名证书访问: ${hostname}`);
      
        // HarmonyOS 6: 配置自定义证书
        // 注:实际API需要参考官方文档
        let response = await httpRequest.request(url, {
          ...options,
          connectTimeout: this.defaultTimeout,
          readTimeout: this.defaultTimeout
          // 自定义证书配置(示意)
          // customCertificates: [certData]
        });
      
        return response;
      
      } else {
        // 无自签名证书,使用默认HTTPS
        console.info(`使用默认HTTPS访问: ${hostname}`);
      
        return await httpRequest.request(url, {
          ...options,
          connectTimeout: this.defaultTimeout,
          readTimeout: this.defaultTimeout
        });
      }
    
    } catch (error) {
      let e = error as BusinessError;
      console.error(`请求失败: ${e.message}`);
      return null;
    
    } finally {
      httpRequest.destroy();
    }
  }
  
  // 提取主机名
  private extractHostname(url: string): string {
    try {
      let urlObj = new URL(url);
      return urlObj.hostname;
    } catch {
      return '';
    }
  }
}

// 初始化和使用示例
async function initSelfSignedClient(): Promise<SelfSignedHttpClient> {
  let certManager = new SelfSignedCertManager();
  
  // 加载内网服务器的自签名证书
  await certManager.loadCertificate(
    'internal.company.local',
    '/resources/certs/internal.crt'
  );
  
  await certManager.loadCertificate(
    '192.168.1.100',
    '/resources/certs/server100.crt'
  );
  
  console.info('已信任的自签名证书主机:', certManager.getTrustedHosts());
  
  return new SelfSignedHttpClient(certManager);
}

// UI组件中使用
@Entry
@Component
struct InternalApiPage {
  @State responseData: string = '';
  @State client: SelfSignedHttpClient | null = null;
  
  async aboutToAppear() {
    this.client = await initSelfSignedClient();
  }
  
  async fetchInternalData() {
    if (!this.client) return;
  
    let response = await this.client.request(
      'https://internal.company.local/api/data',
      {
        method: http.RequestMethod.GET,
        header: { 'Content-Type': 'application/json' }
      }
    );
  
    if (response && response.responseCode === 200) {
      this.responseData = response.result as string;
    }
  }
  
  build() {
    Column() {
      Button('获取内网数据')
        .onClick(() => this.fetchInternalData())
    
      Text(this.responseData || '暂无数据')
        .margin({ top: 20 })
    }
    .padding(20)
  }
}

场景二:动态下载和缓存证书

对于证书可能更新的场景,从服务器动态获取证书并缓存。

typescript 复制代码
import http from '@ohos.net.http';
import fs from '@ohos.file.fs';
import preferences from '@ohos.data.preferences';

// 证书缓存管理
class CertificateCache {
  private cacheDir: string;
  private preferences: preferences.Preferences | null = null;
  
  constructor(cacheDir: string) {
    this.cacheDir = cacheDir;
  }
  
  // 初始化
  async init(context: Context): Promise<void> {
    try {
      this.preferences = await preferences.getPreferences(context, 'cert_cache');
    } catch (error) {
      console.error('初始化证书缓存失败:', error);
    }
  }
  
  // 获取缓存的证书路径
  async getCachedCertPath(hostname: string): Promise<string | null> {
    if (!this.preferences) return null;
  
    try {
      let certInfo = await this.preferences.get(hostname, '') as string;
      if (certInfo) {
        let info = JSON.parse(certInfo);
        // 检查证书是否过期
        if (new Date(info.expiry) > new Date()) {
          return info.path;
        }
      }
    } catch (error) {
      console.error('获取缓存证书失败:', error);
    }
  
    return null;
  }
  
  // 缓存证书
  async cacheCertificate(
    hostname: string,
    certData: Uint8Array,
    expiry: Date
  ): Promise<string> {
    // 保存证书文件
    let certPath = `${this.cacheDir}/${hostname}.crt`;
    let file = fs.openSync(certPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
    fs.writeSync(file.fd, certData.buffer);
    fs.closeSync(file);
  
    // 保存元数据
    if (this.preferences) {
      await this.preferences.put(hostname, JSON.stringify({
        path: certPath,
        expiry: expiry.toISOString(),
        cachedAt: new Date().toISOString()
      }));
      await this.preferences.flush();
    }
  
    return certPath;
  }
  
  // 清除过期证书
  async cleanExpired(): Promise<void> {
    if (!this.preferences) return;
  
    let allKeys = await this.preferences.getAll();
  
    for (let [hostname, info] of Object.entries(allKeys)) {
      try {
        let certInfo = JSON.parse(info as string);
        if (new Date(certInfo.expiry) < new Date()) {
          // 删除过期证书文件
          fs.unlinkSync(certInfo.path);
          // 删除缓存记录
          await this.preferences.delete(hostname);
        }
      } catch (error) {
        // 忽略解析错误
      }
    }
  
    await this.preferences.flush();
  }
}

// 动态证书获取器
class DynamicCertificateFetcher {
  private cache: CertificateCache;
  private certManager: SelfSignedCertManager;
  
  constructor(cache: CertificateCache, certManager: SelfSignedCertManager) {
    this.cache = cache;
    this.certManager = certManager;
  }
  
  // 获取证书(优先使用缓存)
  async getCertificate(hostname: string): Promise<boolean> {
    // 1. 检查缓存
    let cachedPath = await this.cache.getCachedCertPath(hostname);
  
    if (cachedPath) {
      console.info(`使用缓存证书: ${hostname}`);
      return await this.certManager.loadCertificate(hostname, cachedPath);
    }
  
    // 2. 从服务器下载证书
    console.info(`下载证书: ${hostname}`);
  
    let certData = await this.downloadCertificate(hostname);
  
    if (certData) {
      // 解析证书有效期
      let expiry = await this.parseCertificateExpiry(certData);
    
      // 缓存证书
      let certPath = await this.cache.cacheCertificate(hostname, certData, expiry);
    
      // 加载到内存
      return await this.certManager.loadCertificate(hostname, certPath);
    }
  
    return false;
  }
  
  // 从证书服务器下载证书
  private async downloadCertificate(hostname: string): Promise<Uint8Array | null> {
    let httpRequest = http.createHttp();
  
    try {
      // 假设有一个专门的证书分发服务
      let certServer = 'https://cert-server.company.com';
      let response = await httpRequest.request(
        `${certServer}/api/certs/${hostname}`,
        {
          method: http.RequestMethod.GET,
          header: { 'Accept': 'application/x-x509-ca-cert' }
        }
      );
    
      if (response.responseCode === 200) {
        // 假设响应是Base64编码的证书
        let base64Cert = response.result as string;
        return this.base64ToUint8Array(base64Cert);
      }
    
      return null;
    
    } catch (error) {
      console.error('下载证书失败:', error);
      return null;
    
    } finally {
      httpRequest.destroy();
    }
  }
  
  // 解析证书有效期
  private async parseCertificateExpiry(certData: Uint8Array): Promise<Date> {
    // 实际需要使用证书解析库
    // 这里返回一个默认值(1年后)
    let expiry = new Date();
    expiry.setFullYear(expiry.getFullYear() + 1);
    return expiry;
  }
  
  // Base64转Uint8Array
  private base64ToUint8Array(base64: string): Uint8Array {
    // 实际实现需要使用Base64解码API
    return new Uint8Array(0);
  }
}

// 使用示例
async function setupDynamicCerts(context: Context): Promise<SelfSignedHttpClient> {
  let certManager = new SelfSignedCertManager();
  let cache = new CertificateCache('/data/local/tmp/cert_cache');
  
  await cache.init(context);
  
  // 清理过期证书
  await cache.cleanExpired();
  
  let fetcher = new DynamicCertificateFetcher(cache, certManager);
  
  // 获取内网服务证书
  await fetcher.getCertificate('internal.company.local');
  await fetcher.getCertificate('api.internal.local');
  
  return new SelfSignedHttpClient(certManager);
}

场景三:企业CA证书链处理

企业通常有自己的CA服务器,需要处理完整的证书链。

typescript 复制代码
// 企业CA证书配置
interface EnterpriseCAConfig {
  // 根CA证书
  rootCA: Uint8Array;
  
  // 中间CA证书(可选)
  intermediateCAs: Uint8Array[];
  
  // 证书吊销列表(CRL)
  crlUrls: string[];
  
  // OCSP服务器地址
  ocspServer?: string;
}

// 企业CA信任管理器
class EnterpriseCATrustManager {
  private config: EnterpriseCAConfig;
  private trustedCerts: Set<string> = new Set();
  
  constructor(config: EnterpriseCAConfig) {
    this.config = config;
  }
  
  // 初始化信任链
  async initialize(): Promise<void> {
    // 1. 加载根CA
    let rootFingerprint = await this.calculateFingerprint(this.config.rootCA);
    this.trustedCerts.add(rootFingerprint);
    console.info('根CA已信任:', rootFingerprint.substring(0, 16) + '...');
  
    // 2. 加载中间CA
    for (let intermediate of this.config.intermediateCAs) {
      let fingerprint = await this.calculateFingerprint(intermediate);
      this.trustedCerts.add(fingerprint);
      console.info('中间CA已信任:', fingerprint.substring(0, 16) + '...');
    }
  
    // 3. 下载CRL
    await this.downloadCRLs();
  }
  
  // 校验服务器证书
  async verifyServerCertificate(certChain: Uint8Array[]): Promise<boolean> {
    if (certChain.length === 0) {
      return false;
    }
  
    // 检查证书链是否以受信任的CA结尾
    let rootCert = certChain[certChain.length - 1];
    let rootFingerprint = await this.calculateFingerprint(rootCert);
  
    if (!this.trustedCerts.has(rootFingerprint)) {
      console.error('证书链不以受信任的CA结尾');
      return false;
    }
  
    // 验证证书链完整性
    for (let i = 0; i < certChain.length - 1; i++) {
      let isValid = await this.verifyCertificateSignature(
        certChain[i],
        certChain[i + 1]
      );
    
      if (!isValid) {
        console.error(`证书链第${i}层签名验证失败`);
        return false;
      }
    }
  
    // 检查服务器证书是否被吊销
    let serverCert = certChain[0];
    if (await this.isRevoked(serverCert)) {
      console.error('服务器证书已被吊销');
      return false;
    }
  
    return true;
  }
  
  // 计算证书指纹
  private async calculateFingerprint(cert: Uint8Array): Promise<string> {
    // 实际需要使用SHA-256计算
    return 'simulated_fingerprint';
  }
  
  // 验证证书签名
  private async verifyCertificateSignature(
    cert: Uint8Array,
    issuerCert: Uint8Array
  ): Promise<boolean> {
    // 实际需要使用加密API验证签名
    return true;
  }
  
  // 下载CRL
  private async downloadCRLs(): Promise<void> {
    for (let crlUrl of this.config.crlUrls) {
      try {
        let httpRequest = http.createHttp();
        let response = await httpRequest.request(crlUrl, {
          method: http.RequestMethod.GET
        });
      
        if (response.responseCode === 200) {
          console.info('CRL下载成功:', crlUrl);
          // 解析并存储CRL
        }
      
        httpRequest.destroy();
      } catch (error) {
        console.error('CRL下载失败:', crlUrl, error);
      }
    }
  }
  
  // 检查证书是否被吊销
  private async isRevoked(cert: Uint8Array): Promise<boolean> {
    // 检查CRL或OCSP
    return false;
  }
}

// 企业内网HTTP客户端
class EnterpriseHttpClient {
  private trustManager: EnterpriseCATrustManager;
  
  constructor(trustManager: EnterpriseCATrustManager) {
    this.trustManager = trustManager;
  }
  
  async request(url: string, options: http.HttpRequestOptions): Promise<http.HttpResponse | null> {
    let httpRequest = http.createHttp();
  
    try {
      // 配置企业CA信任
      let response = await httpRequest.request(url, {
        ...options,
        // HarmonyOS 6: 配置自定义信任管理器
        // 注:实际API需要参考官方文档
      });
    
      return response;
    
    } catch (error) {
      console.error('请求失败:', error);
      return null;
    
    } finally {
      httpRequest.destroy();
    }
  }
}

// 初始化企业CA
async function initEnterpriseCA(): Promise<EnterpriseHttpClient> {
  // 加载企业CA证书(从应用资源)
  let rootCA = await loadCertFromResource('/resources/certs/enterprise-root-ca.crt');
  let intermediateCA = await loadCertFromResource('/resources/certs/enterprise-intermediate.crt');
  
  let config: EnterpriseCAConfig = {
    rootCA: rootCA,
    intermediateCAs: [intermediateCA],
    crlUrls: ['https://ca.company.com/crl/enterprise.crl'],
    ocspServer: 'https://ca.company.com/ocsp'
  };
  
  let trustManager = new EnterpriseCATrustManager(config);
  await trustManager.initialize();
  
  return new EnterpriseHttpClient(trustManager);
}

// 辅助函数:从资源加载证书
async function loadCertFromResource(path: string): Promise<Uint8Array> {
  let file = fs.openSync(path, fs.OpenMode.READ_ONLY);
  let stat = fs.statSync(path);
  let buffer = new ArrayBuffer(stat.size);
  fs.readSync(file.fd, buffer);
  fs.closeSync(file);
  return new Uint8Array(buffer);
}

四、踩坑与注意事项

坑点一:证书格式不兼容

不同服务器可能使用不同证书格式,需要正确转换。

typescript 复制代码
// 常见证书格式
enum CertFormat {
  PEM = 'PEM',      // 最常见,Base64编码
  DER = 'DER',      // 二进制格式
  PKCS12 = 'P12',   // 包含私钥和证书
  PKCS7 = 'P7B'     // 证书链格式
}

// 证书格式转换器
class CertFormatConverter {
  // PEM转DER
  pemToDer(pem: string): Uint8Array {
    // 移除PEM头尾
    let lines = pem.split('\n');
    let base64 = lines
      .filter(line => !line.startsWith('-----'))
      .join('');
  
    // Base64解码
    return this.base64Decode(base64);
  }
  
  // DER转PEM
  derToPem(der: Uint8Array, label: string = 'CERTIFICATE'): string {
    let base64 = this.base64Encode(der);
    let lines = [];
  
    lines.push(`-----BEGIN ${label}-----`);
  
    // 每64字符一行
    for (let i = 0; i < base64.length; i += 64) {
      lines.push(base64.substring(i, i + 64));
    }
  
    lines.push(`-----END ${label}-----`);
  
    return lines.join('\n');
  }
  
  private base64Decode(base64: string): Uint8Array {
    // 实际需要使用Base64解码API
    return new Uint8Array(0);
  }
  
  private base64Encode(data: Uint8Array): string {
    // 实际需要使用Base64编码API
    return '';
  }
}

坑点二:证书更新未同步

服务器证书更新后,客户端仍使用旧证书导致连接失败。

typescript 复制代码
// 证书版本管理
class CertificateVersionManager {
  private versions: Map<string, number> = new Map();
  
  // 记录证书版本
  recordVersion(hostname: string, version: number): void {
    this.versions.set(hostname, version);
  }
  
  // 检查证书是否需要更新
  async checkForUpdate(hostname: string, currentCert: Uint8Array): Promise<boolean> {
    try {
      // 从服务器获取最新证书版本
      let latestVersion = await this.fetchLatestVersion(hostname);
      let currentVersion = this.versions.get(hostname) || 0;
    
      if (latestVersion > currentVersion) {
        console.info(`证书需要更新: ${hostname} (${currentVersion} -> ${latestVersion})`);
        return true;
      }
    
      return false;
    
    } catch (error) {
      console.error('检查证书更新失败:', error);
      return false;
    }
  }
  
  private async fetchLatestVersion(hostname: string): Promise<number> {
    // 实际需要调用版本查询API
    return 1;
  }
}

坑点三:多环境证书混淆

开发、测试、生产环境使用不同证书,容易混淆。

typescript 复制代码
// 环境感知的证书管理
class EnvironmentAwareCertManager {
  private environment: 'dev' | 'test' | 'prod';
  private certPaths: Map<string, Map<string, string>> = new Map();
  
  constructor() {
    // 初始化不同环境的证书路径
    this.certPaths.set('dev', new Map([
      ['api.internal.local', '/resources/certs/dev/api.crt']
    ]));
  
    this.certPaths.set('test', new Map([
      ['api.test.local', '/resources/certs/test/api.crt']
    ]));
  
    this.certPaths.set('prod', new Map([
      ['api.company.local', '/resources/certs/prod/api.crt']
    ]));
  
    // 检测当前环境
    this.environment = this.detectEnvironment();
  }
  
  // 获取当前环境的证书路径
  getCertPath(hostname: string): string | null {
    let envCerts = this.certPaths.get(this.environment);
    return envCerts?.get(hostname) || null;
  }
  
  // 环境检测
  private detectEnvironment(): 'dev' | 'test' | 'prod' {
    // 可以通过构建配置、环境变量等方式检测
    // 这里简单返回dev
    return 'dev';
  }
}

五、HarmonyOS 6适配指南

5.1 自定义信任管理器

HarmonyOS 6提供了更灵活的证书信任配置。

typescript 复制代码
import http from '@ohos.net.http';

// HarmonyOS 6: 自定义信任配置
let trustOptions: http.TrustManagerOptions = {
  // 自定义证书来源
  customCertificates: [
    {
      hostname: 'internal.company.local',
      certData: certData1  // Uint8Array
    },
    {
      hostname: 'api.internal.local',
      certData: certData2
    }
  ],
  
  // 是否信任系统CA(默认true)
  trustSystemCA: true,
  
  // 证书校验回调
  onVerify: (certChain: Uint8Array[], hostname: string) => {
    // 自定义校验逻辑
    console.info(`校验证书: ${hostname}`);
    return true;  // 返回true表示信任
  }
};

// 应用信任配置
let httpRequest = http.createHttp();

let response = await httpRequest.request(
  'https://internal.company.local/api/data',
  {
    method: http.RequestMethod.GET,
    trustOptions: trustOptions  // HarmonyOS 6新增
  }
);

5.2 证书热更新

HarmonyOS 6支持运行时更新信任证书,无需重启应用。

typescript 复制代码
// 证书热更新管理
class HotUpdateCertManager {
  private trustManager: http.TrustManager;
  
  constructor() {
    this.trustManager = http.createTrustManager();
  }
  
  // 添加新证书
  async addCertificate(hostname: string, certData: Uint8Array): Promise<void> {
    await this.trustManager.addTrustedCertificate(hostname, certData);
    console.info(`证书已添加: ${hostname}`);
  }
  
  // 移除证书
  async removeCertificate(hostname: string): Promise<void> {
    await this.trustManager.removeTrustedCertificate(hostname);
    console.info(`证书已移除: ${hostname}`);
  }
  
  // 获取当前信任的所有证书
  async getTrustedCertificates(): Promise<string[]> {
    return await this.trustManager.listTrustedCertificates();
  }
}

// 使用示例
async function updateCerts() {
  let manager = new HotUpdateCertManager();
  
  // 动态添加新服务器证书
  let newCert = await fetchCertificate('new-server.company.local');
  await manager.addCertificate('new-server.company.local', newCert);
  
  // 查看当前信任列表
  let trusted = await manager.getTrustedCertificates();
  console.info('当前信任的证书:', trusted);
}

六、总结一下下

自签名证书是企业内网HTTPS的常见需求,正确处理既能保证安全又能满足业务需求。本文从三个场景展开:

应用内嵌入证书:将证书文件打包到应用,启动时加载。适合证书相对固定的场景,实现简单。

动态下载缓存:从证书服务器动态获取,支持证书更新。适合证书可能变化的场景,需要处理缓存和过期。

企业CA证书链:处理完整证书链,包括根CA、中间CA、CRL/OCSP。适合企业级部署,安全性最高。

三个常见坑点:证书格式不兼容、证书更新未同步、多环境证书混淆。遇到证书问题时,先排查这三个方面。

HarmonyOS 6提供了自定义信任管理器和证书热更新能力,让自签名证书的处理更加灵活。但切记:自签名证书虽然方便,安全责任也完全在自己,务必做好证书管理和更新机制。

下一篇文章,我们将进入WebSocket实时通信的世界,探讨长连接的最佳实践!