手动验证 TLS 证书

科技资讯 投稿 55500 0 评论

手动验证 TLS 证书

证书结构

根据 RFC 3280 定义的证书结构,证书由三个部分组成:

  1. 证书主体(TBSCertificate,To Be Signed Certificate,待签名证书)

  2. 签名算法

  3. 签名值

证书的数据组织格式为 ASN.1 DER 格式(distinguished encoding rules)。这是一种 TLV 编码,其中的每个元素都包含 Tag、Length、Value。通常我们获得的证书是经由 Base64 编码后的 PEM 文件,为了节省不必要的麻烦,后续内容会提前将证书转回 DER 格式。

证书验证

为了验证证书,我们需要获取以下内容:

  1. 证书主体。用于通过签名算法得到其 hash 值

  2. 证书签名值。

  3. CA 的公钥。用于解密签名,得到证书主体的 hash 值

我们以博客园的证书为例。

证书下载

  1. 下载博客园证书

  2. echo -n | openssl s_client -connect www.cnblogs.com:443 | openssl x509 -outform DER > www.cnblogs.com.crt证书原文是用 Base64 编码后的 PEM 格式文件,这里将其直接转换为 DER 格式。

  3. 查看博客园的 CA 证书地址

    openssl x509 -inform DER -in www.cnblogs.com.crt -noout -text  | grep "CA Issuers"

    得到:

    CA Issuers - URI:http://cacerts.digicert.com/EncryptionEverywhereDVTLSCA-G1.crt
  4. 下载 CA 证书

    curl -O http://cacerts.digicert.com/EncryptionEverywhereDVTLSCA-G1.crt

    这个证书已经是 DER 格式,无需转换。

现在,我们得到了:

  • 博客园证书

  • 给博客园证书签名的 CA 的证书

DER 证书解析

证书由证书主体、签名算法和签名值三个部分组成。DER 格式每个元素以 SEQUENCE 作为开头。由于完整的证书是一个 SEQUENCE,因此证书最外层有个 SEQUENCE。里面三个部分都各自有一个 SEQUENCE。

openssl asn1parse -i -inform DER -in www.cnblogs.com.crt

解析的格式为:开头数字是偏移量,d 表示深度(depth),d 相同的表示是同一个层级。hl 表示头部长度(header length),l 表示不包含头部的数据长度(lenght)。

例如第一行:

再看最后一行,

开头 1277 表示偏移量,d=1 表示顶层的下一层,而这个位置在最后一部分,那么就代表是签名值。头部长度为 4 字节,数据长度为 257 字节,加起来共 261 字节。加上偏移量 1277 + 261 = 1538 字节,与第一行的计算结果吻合。

获取证书主体(待签名证书)

dd if=www.cnblogs.com.crt of=www.cnblogs.com.tbs bs=1 skip=4 count=1258

这里 bs=1 表示每次读取一个字节,count 表示读取 1258 次,两者组合起来就是读取 1258 个字节。

计算证书 hash 值

sha256WithRSAEncryption 表示使用 sha256 hash,并使用 RSA 加密。

得到结果:

签名值

从 ASN.1 解析后的数据,可以知道签名值的偏移量是 1277。

openssl asn1parse  -inform DER -in www.cnblogs.com.crt --strparse 1277 -noout -out www.cnblogs.com.sig

验证这个数据:

xxd -p www.cnblogs.com.sig  | tr -d '\n'

得到:

查看得到的结果是否与以下命令的最后一部分一致:

openssl x509 -inform DER -in www.cnblogs.com.crt -noout -text

这部分直接从完整的 text 中复制然后手动编辑也可以。

CA 证书公钥

openssl asn1parse -i -inform DER -in EncryptionEverywhereDVTLSCA-G1.crt

找到,它在 d=4 的层级,公钥所在位置为下方的 d=3 层级的 BIT STRING:

根据 rfc 3279 的定义,RSA PublickKey 由系数 (modulus 和指数 (exponent 两部分组成。因此 271 字节里面的两部分要分开获取。这两部分也是使用 DER 格式编码的,可以直接使用工具解析:

得到:

其中第一个 INTEGER 是 modulus,第二个 INTEGER 是 exponent。由于可以直接复制,不再使用 xxd 命令。

在  一节里可以看到:

以及: Exponent: 65537 (0x10001。

使用 CA 公钥解密

已知的内容:

  • CA 公钥系数 modulus:

  • CA 公钥指数 exponent:

根据公式 decrypted_value = (modulus^exponent%sig,使用 python 计算:

modulus=0xB3DE3FAC2469BE35772421EA629CA07AADDE3448C56E4C0EF7FD43288E47B55F1702BAE7A7ACD1416221BEF837DA519EDCC5D54818CC31AEDE9A5954C76895BC619BA7564BD38AFE515E84A353D0E608F5AAA4E85F94EDC03A8F1482FA20C13D7C1D178AECDCA472A776909FAA63A69D72AFB201E98E33BFBD847BF3E567FEAB2BA2270BA5A92B49CF54E611EE7F620EE3DED44E08C543011FF4F7DFEDE1CAE1F776F7E089650E5248DDA4C6F2C57F973657B9B84222C81B22E08BDB7130A1F2BBA27C2222E660D7919AE7313F27C1F60257ABFA90375791B80644B2AC478A6E71B26D6CAA889141B1B99236B7BA5F7B02917399D679CBC30597F7FA9D4CA40F sig=0x49aa4a0ff0052f53f27fd1681b11f6247a75b6da247b589a8cec50a7abd3e1ddc9cbbd09a221e529466bdacb34d3bf9555fa4714731b33326cf341d2cd5ac6b5996d22a682dcd8a97ea2245b3318fddc423d17d16318cd925c6d5873a40f16d9b0a6393c18e66d57be6c30688d10faf55b833479cb2735835210c7c6869bc2c33edf7acb531e1e96d3a47796d0f2fe3c3784d3f35e0e7824094bb4b388e8d95861b864a71c50e3b6da9512be585a14196dc7ec2e80f74a06fead44b7340f1a6c3fa1451bd64eadf8ec71d866dee43e44cfcaae3ecc9804e12037400e6548ce77c080d2b09840aa7297aa862679f228a7fac1187a68355946d49a5ae7389d6a38 exponent=0x10001 print("%x" % pow(sig, exponent, modulus

得到:

开头的  是 RSA 算法的填充字节,接下来的  是 sha256 的填充字节。剩下的  是证书 hash 值。

至此,验证完毕。

证书链

上面只验证了第一步。从浏览器中可以看出,上述 CA 证书不是根证书,还需要验证 CA 证书的可信。步骤与上述差不多,只是根证书是安装在设备的操作系统上或者浏览器上。

编程笔记 » 手动验证 TLS 证书

赞同 (63) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽