安卓SSL证书绑定(pinning)

5
我知道有很多关于Android上固定证书的问题,但是我找不到我要找的东西...
我子类化了SSLSocketFactory并重写了checkServerTrusted()方法。在这个方法中,我进行以下操作:
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate ca = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(PUB_KEY.getBytes("UTF-8")));
for (X509Certificate cert : chain) {
    // Verifing by public key
    cert.verify(ca.getPublicKey());                      
}

这个链条中的一个项目验证了,而另一个没有(导致了一个 "Exception" 异常)。我想我不太理解证书链是如何工作的。

同一个公共证书是否应该与链条中的所有证书进行验证?


请查看OWASP网站上的示例代码,以在Android中使用。 - Steffen Ullrich
你提到了“异常”。有Logcat吗? - shkschneider
你是否面临着 SSLHandshakeException 问题? - Pravin Divraniya
5个回答

2

您的问题和疑虑

同一个公共证书是否应该验证所有证书链中的证书?

不应该,因为证书链中的每个证书(根证书、中间证书和叶子证书)都是使用不同的私钥/公钥对签名的。

证书链中的某些项可以验证,而其他项则无法验证(导致异常)。我想我无法理解证书链的工作原理。

这是因为您的证书是叶子证书,因此您只能将您的公钥与之验证,而不能将其与根证书和中间证书进行验证。

代码方法

我子类化了SSLSocketFactory并覆盖了checkServerTrusted()方法。

如果您真的想自己编写代码,我建议您使用内置的OkHttp证书固定器,您可以像这样构建它:

import okhttp3.CertificatePinner;

public class OkHttpPinnerService {

    // true if the Approov SDK initialized okay
    private boolean initialized;

    // cached OkHttpClient to use or null if not set
    private OkHttpClient okHttpClient;

    public synchronized OkHttpClient getOkHttpClient() {
        if (okHttpClient == null) {
            // build a new OkHttpClient on demand
            if (initialized) {
                // build the pinning configuration
                CertificatePinner.Builder pinBuilder = new CertificatePinner.Builder();
                Map<String, List<String>> pins = YourConfig.getPins("public-key-sha256");
                for (Map.Entry<String, List<String>> entry : pins.entrySet()) {
                    for (String pin : entry.getValue())
                        pinBuilder = pinBuilder.add(entry.getKey(), "sha256/" + pin);
                }

                // build the OkHttpClient with the correct pins preset and ApproovTokenInterceptor
                Log.i(TAG, "Building new Approov OkHttpClient");
                okHttpClient = okHttpBuilder.certificatePinner(pinBuilder.build()).build();
            } else {
                // if the Approov SDK could not be initialized then we can't pin or add Approov tokens
                Log.e(TAG, "Cannot build Approov OkHttpClient due to initialization failure");
                okHttpClient = okHttpBuilder.build();
            }
        }

        return okHttpClient;
    }    
}

该代码未经过语法错误或逻辑正确性的测试。我只是从this repo复制了它,并稍作修改。

无代码方法

自 Android API 24 以来,可以通过内置的安全配置文件实现对公钥哈希的证书固定,而不需要编写任何代码,只需将正确配置的network_security_config.xml文件添加到您的项目中即可。

为避免在构建network_security_config.xml文件时出错,我建议您使用Mobile Certificate Pinning Generator提取您想要针对的域正在使用的实时固定码,并为您构建正确的配置。例如:

From to add the domains to pin

Android network security configuration

现在只需将生成的配置 network_security_config.xml 文件复制并粘贴到您的项目中,然后将此文件添加到 AndroifManifest.xml 中。只需按照页面上的说明进行操作即可。

1
我发现在Android上实现证书固定的最简单方法是使用OkHttp库。
以下是文档摘录
默认情况下,OkHttp信任主机平台的证书颁发机构。这种策略最大程度地提高了连接性,但它容易受到证书颁发机构攻击,例如2011年DigiNotar攻击。它还假设您的HTTPS服务器证书由证书颁发机构签名。
使用CertificatePinner来限制可信任的证书颁发机构。证书固定增加了安全性,但限制了服务器团队更新其TLS证书的能力。在未经过服务器的TLS管理员允许的情况下,请勿使用证书固定!
  public CertificatePinning() {
    client = new OkHttpClient();
    client.setCertificatePinner(
        new CertificatePinner.Builder()
            .add("publicobject.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=")
            .add("publicobject.com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=")
            .add("publicobject.com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=")
            .add("publicobject.com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=")
            .build());
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/robots.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    for (Certificate certificate : response.handshake().peerCertificates()) {
      System.out.println(CertificatePinner.pin(certificate));
    }
  }

如果您需要支持自签名证书,OkHttp是否支持接受自签名SSL证书?的答案会指导您。


0
同一个公共证书是否应该验证所有证书链中的证书?
答案:不应该。
大多数公共CA不直接签署服务器证书。相反,他们使用其主要CA证书(称为根CA)来签署中间CA。他们这样做是为了将根CA存储在离线状态以减少被攻击的风险。然而,像Android这样的操作系统通常只信任根CA,这在服务器证书(由中间CA签署)和证书验证器之间留下了一个短暂的信任差距,后者知道根CA。
为了解决这个问题,服务器在SSL握手期间不仅向客户端发送自己的证书,还会发送一系列证书,从服务器CA通过任何必要的中间证书到达受信任的根CA。
请查看this link获取更多信息。希望这能帮助用户。

0
证书和公钥固定(又称证书固定)简介:
通常情况下,应用程序信任所有预安装的CA。如果其中任何一个CA发出虚假证书,则该应用程序将面临中间人攻击(又称窃听)的风险。一些应用程序选择通过限制它们信任的CA集合或通过证书固定来限制接受的证书集合。证书固定是通过提供公钥哈希的证书集合来完成的。证书固定是一种依赖于客户端对服务器证书验证的方法。
以下是在Android上实现证书固定的3种方法:
特别针对您的问题,您可以使用<pin-set>标签在NSC中按公钥哈希配置证书。 请注意,当使用证书固定时,您应始终包含备用密钥,以便在强制切换到新密钥或更改CA(当固定到CA证书或该CA的中间件时)时,您的应用程序的连通性不受影响。否则,您必须推送更新以恢复连接。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <pin-set expiration="2018-01-01">
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
            <!-- backup pin -->
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

0

要在 Kotlin Android 应用中添加 SSL pinning,请按照以下逐步说明进行操作: 第 1 步:将 OkHttp 库添加到您的项目中。 在应用级别的 build.gradle 文件中添加 OkHttp 依赖项:

implementation 'com.squareup.okhttp3:okhttp:4.9.1'

第二步:创建一个证书固定助手类。 在您的项目中创建一个新的 Kotlin 类,例如 CertificatePinningHelper。这个类将包含 SSL 固定的逻辑。以下是一个基本实现:
import okhttp3.CertificatePinner
object CertificatePinningHelper {
    private const val HOSTNAME = "your-api-hostname.com"
private const val PIN = "sha256/YourPublicKeyHash"

fun getCertificatePinner(): CertificatePinner {
    return CertificatePinner.Builder()
        .add(HOSTNAME, PIN)
        .build()
}
}

用实际的 API 主机名替换 your-api-hostname.com。用服务器证书的实际公钥哈希替换 YourPublicKeyHash。您可以使用 OpenSSL 或其他类似工具获取公钥哈希。
步骤 3:使用 CertificatePinner 初始化 OkHttpClient。 在您的网络代码中,创建一个 OkHttpClient 实例,并配置它以使用在前一步创建的 CertificatePinner。以下是一个示例:
import okhttp3.OkHttpClient
val client = OkHttpClient.Builder()
.certificatePinner(CertificatePinningHelper.getCertificatePinner())
.build()

第四步:使用OkHttpClient进行网络请求。 使用配置好的OkHttpClient实例进行网络请求。例如:
import okhttp3.Request
val request = Request.Builder()
.url("https://your-api-hostname.com/api/endpoint")
.build()

client.newCall(request).enqueue(object : Callback {
  override fun onFailure(call: Call, e: IOException) {
    // Handle network request failure
}
    override fun onResponse(call: Call, response: Response) {
   // Handle network response

   }
})

请确保将https://your-api-hostname.com/api/endpoint替换为您要访问的实际API端点。

就这样!通过这些步骤,您已经在Kotlin Android应用程序中使用OkHttp添加了SSL固定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接