Section outline

  • 在此任务中,我们将使用程序手动验证 X.509 证书。X.509 包含有关公钥的数据以及颁发者对该数据的签名。我们将从 Web 服务器上下载真实的 X.509 证书,获取其颁发者的公钥,然后使用该公钥来验证证书上的签名。

    第 1 步: 从真实的 Web 服务器上下载一张证书
     
    在这份文档中,我们使用 www.example.org 服务器。学生应当选择一个其他的 Web 服务器,获取与文档中不同的证书(注意 www.example.com 和 www.example.org 的证书是一样的)。我们可以用浏览器或者使用下面的命令下载证书:
    $ openssl s_client -connect www.example.org:443 -showcerts
    
    Certificate chain
     0 s:/C=US/ST=California/L=Los Angeles/O=Internet Corporation for Assigned
         Names and Numbers/OU=Technology/CN=www.example.org
       i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High Assurance
         Server CA
       -----BEGIN CERTIFICATE-----
       MIIF8jCCBNqgAwIBAgIQDmTF+8I2reFLFyrrQceMsDANBgkqhkiG9w0BAQsFADBw
       MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
       ......
       wDSiIIWIWJiJGbEeIO0TIFwEVWTOnbNl/faPXpk5IRXicapqiII=
       -----END CERTIFICATE-----
     1 s:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert SHA2 High
         Assurance Server CA
       i:/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High Assurance
         EV Root CA
       -----BEGIN CERTIFICATE-----
       MIIEsTCCA5mgAwIBAgIQBOHnpNxc8vNtwCtCuF0VnzANBgkqhkiG9w0BAQsFADBs
       MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
       ......
       cPUeybQ=
       -----END CERTIFICATE-----

    命令的结果包含两张证书。证书的 subject(s: 开头的一项)是 www.example.org,也就是说这张证书是 www.example.org 的证书。颁发者 issuer(i: 开头)提供了颁发者的信息。第二张证书的 subject 与第一张证书的颁发者相同,也就是说第二章证书是一个中间 CA 的。在本任务中,我们使用 CA 的证书验证服务器的证书。

    如果上面的命令只返回了一张证书,就说明你获得的证书是根 CA 签发的。根 CA 的证书可以从我们的虚拟机中安装的 Firefox 浏览器里获得。点击 Edit → Preferences → Privacy  然后点击 Security → View Certificates。搜索颁发者的名字,然后下载它的证书。

    复制每张证书(在 "Begin CERTIFICATE" 和 "END CERTIFICATE" 的两行中间的文本,包括这两行)到一个文件中。第一个文件称为 c0.pem,第二个称为 c1.pem。


    第 2 步: 从颁发者的证书中 (e, n) 提取公钥 
     
    Openssl 提供了从 X.509 证书中提取特定属性的命令。我们可以使用 -modulus 参数来提取 n 的值。没有特定参数的指令可以用来提取 e,但是我们可以输出所有的域,从中很容易就能找到 e 的值.
    For modulus (n):
    $ openssl x509 -in c1.pem -noout -modulus
    
    打印所有的域, 从中找到 e:
    $ openssl x509 -in c1.pem -text -noout


    第 3 步: 从服务器的证书中提取签名
     
    Openssl 没有具体命令来提取签名,但是我们可以输出所有的域,然后复制签名到一个文件中(注意:如果证书使用的签名算法不是基于 RSA 的,你可以找一张其他的证书)。
    $ openssl x509 -in c0.pem -text -noout
    ...
    Signature Algorithm: sha256WithRSAEncryption
      84:a8:9a:11:a7:d8:bd:0b:26:7e:52:24:7b:b2:55:9d:ea:30:
      89:51:08:87:6f:a9:ed:10:ea:5b:3e:0b:c7:2d:47:04:4e:dd:
      ......
      5c:04:55:64:ce:9d:b3:65:fd:f6:8f:5e:99:39:21:15:e2:71:
      aa:6a:88:82

    我们需要从数据中删除空格和冒号,这样就能获得可以输入到我们的程序的十六进制字符串。下面的命令可以实现这个目的。tr 命令是一个 Linux 用来处理字符串的工具。在本例中, -d 选项用来从数据中删除 ":" 和 "space" (空格)。
    $ cat signature | tr -d '[:space:]:'
    84a89a11a7d8bd0b267e52247bb2559dea30895108876fa9ed10ea5b3e0bc7
    ......
    5c045564ce9db365fdf68f5e99392115e271aa6a8882

    第 4 步: 提取服务器的证书主体
     
    证书颁发机构 (CA) 为一张服务器证书生成签名时,首先需要计算证书的 Hash ,然后对 Hash 签名。为了验证证书,我们也需要从证书中生成一个 Hash。由于 Hash 是在计算签名之前生成的,我们需要排除证书的签名块,然后再计算 Hash。如果没有对证书格式很好的理解,找到证书中用于生成 Hash 的部分是很有挑战性的。

    X.509 证书使用 ASN.1 (Abstract Syntax Notation One) 标准编码,所以如果我们能解析 ASN.1 结构,我们就能很容易从整数中提取任何一个域。Openssl 有一个用于解析 ASN.1 结构的 asn1parse 命令,这里我们用来解析 X.509 证书。
    $ openssl asn1parse -i -in c0.pem
        0:d=0  hl=4 l=1522 cons: SEQUENCE
        4:d=1  hl=4 l=1242 cons:  SEQUENCE           🅰
        8:d=2  hl=2 l=   3 cons:   cont [ 0 ]
       10:d=3  hl=2 l=   1 prim:    INTEGER           :02
       13:d=2  hl=2 l=  16 prim:   INTEGER           :0E64C5FBC236ADE14B172AEB41C78CB0
       ... ...
     1236:d=4  hl=2 l=  12 cons:     SEQUENCE
     1238:d=5  hl=2 l=   3 prim:      OBJECT            :X509v3 Basic Constraints
     1243:d=5  hl=2 l=   1 prim:      BOOLEAN           :255
     1246:d=5  hl=2 l=   2 prim:      OCTET STRING      [HEX DUMP]:3000
     1250:d=1  hl=2 l=  13 cons:  SEQUENCE           🅱
     1252:d=2  hl=2 l=   9 prim:   OBJECT            :sha256WithRSAEncryption
     1263:d=2  hl=2 l=   0 prim:   NULL
     1265:d=1  hl=4 l= 257 prim:  BIT STRING

    从 🅰 开始的部分是用于生成 Hash 的证书主体,从 🅱 开始的部分是签名块。每行开头的数字是它们的偏移 (Offset)。在本例中,证书主体的偏移是 4 到 1249,而签名块是从 1250 到文件的末尾。对于 X.509 证书,开头的偏移总是一样的(也就是 4 ),但结尾的位置依赖于证书内容的长度。我们可以使用 -strparse} 选项获取起始偏移为 4 的域,也就是证书不带签名的主体部分。
    $ openssl asn1parse -i -in c0.pem -strparse 4 -out c0_body.bin -noout

    获得了证书的主体之后,我们就可以用下面的命令计算它的 Hash :
    $ sha256sum c0_body.bin

    第 5 步: 验证签名
     
    现在我们有了全部的信息,包括 CA 的公钥、 CA 的签名以及服务器证书的主体。我们可以执行自己的程序来验证签名是否有效。Openssl 提供了一个命令来为我们验证证书,但是这里要求学生使用他们自己的程序来实现。否则,这个任务给0分。