证书结构
根据 RFC 3280 定义的证书结构,证书由三个部分组成:
证书主体(TBSCertificate,To Be Signed Certificate,待签名证书)
签名算法
签名值
证书的数据组织格式为 ASN.1 DER 格式(distinguished encoding rules)。这是一种 TLV 编码,其中的每个元素都包含 Tag、Length、Value。通常我们获得的证书是经由 Base64 编码后的 PEM 文件,为了节省不必要的麻烦,后续内容会提前将证书转回 DER 格式。
证书验证
为了验证证书,我们需要获取以下内容:
证书主体。用于通过签名算法得到其 hash 值
证书签名值。
CA 的公钥。用于解密签名,得到证书主体的 hash 值
我们以博客园的证书为例。
证书下载
下载博客园证书
查看博客园的 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
下载 CA 证书
curl -O http://cacerts.digicert.com/EncryptionEverywhereDVTLSCA-G1.crt
这个证书已经是 DER 格式,无需转换。
echo -n | openssl s_client -connect www.cnblogs.com:443 | openssl x509 -outform DER > www.cnblogs.com.crt
证书原文是用 Base64 编码后的 PEM 格式文件,这里将其直接转换为 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 证书的可信。步骤与上述差不多,只是根证书是安装在设备的操作系统上或者浏览器上。