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:证书的指纹(支持 sha256sha1 格式,但推荐使用 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. 不支持动态调整,证书变化需重新部署客户端。
相关推荐
wu_android8 分钟前
Android 线性布局中常见的冲突属性总结
android
pan_junbiao13 分钟前
Spring框架的设计模式
java·spring·设计模式
远方160914 分钟前
0x-2-Oracle Linux 9上安装JDK配置环境变量
java·linux·oracle
北执南念20 分钟前
CompletableFuture+线程池使用案列
java
恋猫de小郭31 分钟前
为什么跨平台框架可以适配鸿蒙,它们的技术原理是什么?
android·前端·flutter
黄交大彭于晏1 小时前
发送文件脚本源码版本
java·linux·windows
钮钴禄·爱因斯晨1 小时前
Java 面向对象进阶之多态:从概念到实践的深度解析
java·开发语言·数据结构
鸽子炖汤1 小时前
Java中==和equals的区别
java·开发语言·jvm
hstar95271 小时前
二、即时通讯系统设计经验
java·架构
张风捷特烈2 小时前
每日一题 Flutter#5,6 | 两道 Widget 选择题
android·flutter