OkHttp源码学习之CertificatePinner

在 OkHttp 中,CertificatePinner 是一个安全功能,用于实现 SSL/TLS 证书固定(Certificate Pinning) 。通过将服务器的公钥或证书与客户端预定义的可信值绑定,可以有效防止中间人攻击(MITM),即使攻击者获得了伪造的可信 CA 证书,也无法欺骗客户端。


1. CertificatePinner 的作用

  • 增强 HTTPS 安全性:

    • 即使服务器的证书被替换或签发机构被攻破,客户端依然能够验证预定义的可信证书。
  • 防止中间人攻击:

    • 防止攻击者通过伪造的证书截获和篡改数据。
  • 提升安全透明性:

    • 明确指定客户端信任的证书或公钥,减少对 CA 的完全信任。

2. CertificatePinner 的基本工作原理

  1. 客户端在发起 HTTPS 请求时,会从服务器获取其证书。
  2. CertificatePinner 将服务器证书的公钥或完整证书与客户端本地预定义的值进行对比。
  3. 如果匹配成功,连接继续;否则,连接失败。

3. CertificatePinner 的使用

1. 创建 CertificatePinner

CertificatePinner 是不可变的,通过 CertificatePinner.Builder 配置:

java 复制代码
CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .build();
  • add() 方法参数:

    • hostname:目标主机名(如 example.com)。
    • pin:证书的指纹(支持 sha256 和 sha1 格式,但推荐使用 sha256)。

2. 将 CertificatePinner 应用于 OkHttpClient

将 CertificatePinner 设置到 OkHttpClient:

java 复制代码
OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build();

3. 获取服务器证书指纹

要固定证书,需要先获取服务器证书的指纹:

  • 使用 OpenSSL:

    bash 复制代码
    bash
    复制代码
    echo | openssl s_client -connect example.com:443 | openssl x509 -pubkey | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -binary | base64
  • 使用 Java 程序提取:

    java 复制代码
    OkHttpClient client = new OkHttpClient();
    Request request = new Request.Builder().url("https://example.com").build();
    Response response = client.newCall(request).execute();
    
    Handshake handshake = response.handshake();
    List<Certificate> certificates = handshake.peerCertificates();
    
    for (Certificate certificate : certificates) {
        System.out.println(certificate);
    }

4. 完整示例

java 复制代码
CertificatePinner certificatePinner = new CertificatePinner.Builder()
    .add("example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
    .add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
    .build();

OkHttpClient client = new OkHttpClient.Builder()
    .certificatePinner(certificatePinner)
    .build();

Request request = new Request.Builder()
    .url("https://example.com")
    .build();

try (Response response = client.newCall(request).execute()) {
    System.out.println(response.body().string());
}

5. 注意事项

1. 主机名匹配

  • CertificatePinner 支持通配符:

    java 复制代码
    .add("*.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")

2. 动态证书更新

  • 如果服务器的证书更新,客户端需要同步更新 CertificatePinner,否则会导致连接失败。

3. 多个指纹支持

  • 可以为同一个主机配置多个指纹,以应对证书过渡或多 CA 签发的情况:

    java 复制代码
    .add("example.com", 
         "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
         "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=");

4. 生产环境推荐

  • 固定公钥(pin),而非完整证书,以减少证书更换的频率。

5. 禁用证书固定

  • 在开发或测试环境中,可以禁用 CertificatePinner,例如设置一个空实例:

    java 复制代码
    OkHttpClient client = new OkHttpClient.Builder()
        .certificatePinner(CertificatePinner.DEFAULT)
        .build();

6. 常见问题与解决

问题 1:连接失败,提示证书固定错误

  • 确保固定的指纹与服务器证书的实际指纹一致。
  • 检查主机名是否匹配,确保域名正确。

问题 2:证书更换导致服务中断

  • 提前添加新证书的指纹,支持证书过渡。

7. CertificatePinner 的优势与限制

优势

  1. 增强安全性,防止中间人攻击。
  2. 降低对 CA 的信任依赖。

限制

  1. 配置和维护成本较高,特别是证书频繁更换时。
  2. 不支持动态调整,证书变化需重新部署客户端。
相关推荐
alexhilton5 小时前
模块化Jetpack Compose架构
android·kotlin·android jetpack
小π军5 小时前
操作系统面试题
java·linux·服务器
倔强的石头1066 小时前
应用账号最小权限实践:读写账号、报表账号、运维账号分层
java·运维·开发语言
Shaoshing6 小时前
ThreadLocal
java·jvm·threadlocal
写后端的胖头鱼6 小时前
一文详解CAS
java·jvm·spring·cas·乐观锁·自旋
鱼宵6 小时前
Spring AI 提示词模板:{变量} 参数化 + few-shot,一条提示词反复用
java·人工智能·spring·few-shot·提示词工程·springai
蜗牛互联网7 小时前
Python消费Responses SSE事件:增量文本、超时与取消
java·开发语言·人工智能·后端·python
wanderist.7 小时前
线性筛法详解:从筛质数到欧拉函数、Möbius 函数与约数函数
java·数据结构·算法
厮年断弦7 小时前
AI 辅助开发实录:一周做完一个 Android 本地音乐播放器,这些坑替你踩了
android
hai_android8 小时前
InputStage 责任链核心执行流程
android·android studio·android jetpack