使用 crypto/x509 实现 证书链的 生成 与 校验

2023年 8月 1日 22.3k 0

背景: 数字证书被广泛用于互联网各个领域, 从https访问网站到 发送电子邮件 都少不了它的身影。 现在广泛的证书已经不只是一个证书, 而是一个证书链(根证书, 中间证书, 终端证书)。看完这篇文章, 你将理解什么是数字证书, 什么是证书链, 证书链如何工作, 为什么使用证书链, 最后将使用 golang 的 crypto/x509 操作证书链(查看, 生成与校验)

什么是数字证书?

互联网上的数字证书就好比人的身份证一样, 属于一个身份证明, 其作用就是 证明你就是你。一个网站拥有自己的数字证书, 就好比给自己网站办了张身份证, 大家都认可你的身份。身份证由公安局颁发, 网站证书由CA(Certificate Authority)机构颁发, 其中最常用的就是X509格式的证书。在 chrome 浏览器中, 如果你访问的一个网站它的 的证书无效(比如过期了), chrome 就会把你拦下来, 需要你再次确认才能访问这个网站。这就好比你打了一台网约车, 打车APP上面告诉你上车之前核对好车辆信息才能上车一个道理。如果一个网站没有证书, 那么它的安全性, 有效性就不能保证。比如一个诈骗网站是没有机构愿意给他颁发证书的。

我们可以去WIKI上哪一个证书看看它长什么样。首先打开一个wiki页面, 点击chrome地址栏旁边的锁图标, 选择此链接是安全的, 再选择查看证书, 就可以看到WIKI 的证书信息, 在概要里面可以看到的证书的主要信息:

1690622873240.png

上面这张图的具体内容是什么,我先不做解释, 后面在证书链的校验过程中中我会详细说明。

什么是证书链?

简单来说, 证书链就是 由一个初始的证书的私钥签发出下一级证书,下一级证书的私钥签出下下一级证书, 依次类推, 当前的证书不断的用自己的私钥签发出下一级证书, 形成一条链, 这个链就是证书链。

一般来说这个链不会很长, 网站用的证书链一般是3级, 第一级称作根证书(root cert), 第二级称作 中间证书 intermediate cert, 第三级称作 终端证书(end-user root)。特别的是根证书是用自己的私钥给自己签发证书(称为自签名), 这就要求其拥有绝对的公信力与权威性(这样大家才认可), 所以能够签发根证书的CA机构的数量是最少的。

上面那个WIKI的证书其实是一个终端证书, 下面来看看WIKI完整的=的证书链。
在刚刚那张图片上面的框框上面点击证书详情,就会看到 WIKI 网站的证书链。最顶端的 DigiCert Global Root CA 就是根证书, 第二级的 DigiCer TLS Hybrid ECC SHA384 2020 CA1 就是中间证书, 最后的 *.wikipedia.org 就是中间证书

1690626825442.png

点击 Certificate Fields, 就可以看到证书的详细内容, 比如点击 Issuer, 可以看到 WIKI 的终端证书是由谁签发的。这里可以看到是由 DigiCer TLS Hybrid ECC SHA384 2020 CA1 也就是上面说的中间证书所签发的,中间证书的 CN(common name 中文相当于证书名)为 DigiCer TLS Hybrid ECC SHA384 2020 CA1, O(organization 中文相当于公司名 网页域名) 为 DigiCert Inc, C(country 国家名) 为 US。

1690627013424.png

证书链如何工作?

证书内容

为了说明证书链, 我们先来看看证书的组成内容, 一般证书由这几个部分构成:

  • Issuer 部分: 也就是这个证书是由谁签发的, 里面包含了CA的相关信息(包括common name, organization, country, province 等等)。
  • Subject 部分: 这个证书是签发给谁的, 也就是颁发对象, 里面包括了颁发对象的相关信息(同样也有common name, organization, country, province 等等)
  • Validity: 证书的有效期信息, 通常包含两部分:NotBefore(有效期的最早时间点) 和 NotAfter(有效期的最晚时间点)
  • Subject Public Key Info: 公钥信息包括签名算法和公钥长度, 用于当前证书签发的下一级证书验签。当前的证书私钥签发下一级证书, 下一级用当前证书的公钥进行验签, 所以当前的证书公钥需要在证书里面体现出来, 下面一级的证书才能被验签。
  • Signature: 证书签名, 由上一级CA的私钥对 当前证书的主体信息(主要是Subject和Validity) 和 上一级CA的公钥 进行签名。特别的是上一级CA为了防止自己的公钥被篡改, 所以把自己的公钥也放进去一起签名了。如果是根证书, 那么它没有上级CA, 只能有自己签自己, 这成为自签名。我们可以用上一级证书的公钥对 当前证书的 signature 进行验签。
  • Fingerprints: 证书的哈希值, 也就是当期证书的所有信息(除了当前证书的私钥)取哈希值。这里注意区分 Signature 和 Fingerprint。 Signature是上一级的CA用自己的私钥对下一级证书进行签名, 第三方可以用上一级的CA的公钥对下一级证书验签,其关注点在于确保下一级证书确实是由上一级CA签发出来的。而 Fingerprintsd 的关注点在于确保当前证书的 Subject, Validity 没有被篡改。
  • 证书的 issuer, subejct, validity 被统称为证书的 Meta Info 元数据, 可以理解为元数据就是证书的内容。Signature 和 Fingerprint 都是为了确保 证书内容的真实性和防篡改性的 延申产物。

    证书链 工作流程

    好了,至此为止你知道了证书内容。为了详细解释证书链如何工作, 我画了下面这个示意图:

    cert_chain.drawio.png

    证书链签发证书流程如下:

  • Root CA 使用自己的私钥对 Root Cert 进行自签名
  • Root CA 使用自己的私钥对 Intermediate Cert 的 Meta Info 和 Root CA 自己的公钥进行签名
  • Intermediate CA 使用自己的私钥 对 End-User 的 Meta Info 和 Intermediate CA的公钥进行签名
  • 特别注意,每一级证书私钥并不是放在证书里面的, 而是有一个单独的私钥文件, 当前这一级的私钥用于签发下一级证书, 不对外公开。私钥应该由当前的CA严格保管,不能外泄!!!

    证书链校验流程如下:

  • 对 End-User Cert 的 Validity 和 FingerPrints 进行校验, 分别确保证书位于有效期内和证书信息没有被篡改。使用 Intermediate CA 的 Pub Key 对 证书进行验签。
  • 对 Intermediate Cert 的 Validity 和 FingerPrints 进行校验, 分别确保证书位于有效期内和证书信息没有被篡改。使用 Root CA 的 Pub Key 对 证书进行验签。
  • 对 Root Cert 的 Validity 和 FingerPrints 进行校验, 分别确保证书位于有效期内和证书信息没有被篡改。使用 Root CA 的 Pub Key 对 证书进行验签。
  • 可以看到最后一级证书的公私钥其实是没有上面实际作用的, 它并不参与任何一级证书的签名和验签, 我画在上面只是方便展示。

    如果 Root CA 是被信任的且走完了以上三步, 那么这个 End-User Cert 就是有效的。有人会问 Root CA 被信任的是什么意思。 上面说过,Root CA 是有绝对的权威和公信力的, 其实全世界被广泛使用并信任的 Root CA 就那么几家, 分别是:

    • DigiCert ( WIKI 用的终端证书就是由 DigiCert的根证书间接签发出来的)
    • GlobalSign
    • Comodo CA Limited
    • GoDaddy
    • IdenTrust
    • Entrust Datacard
    • QuoVadis
    • Trustwave
    • Symantec
    • Let's Encrypt

    这几家 Root CA 的 Root Cert 都是公开的, 都是被各个系统,各个网站广泛接受的。只要你的系统(可以是你的操作系统, 你的浏览器)接受 Root CA , 就算是 Root CA 被信任。一般的操作系统和浏览器都会内置一些可信任的 CA 。

    为什么使用证书链

    为什么使用证书链, 单独地使用证书不行吗? 比如直接使用一个私钥对证书进行签名, 公钥验签。可以是可以,但是不够好, 这回带来一下问题

  • 签名的CA会非常繁忙, 所有终端证书都要找它签名, 它要对终端证书的申请者做各种可靠性认证, 这回加重CA的工作量, 如果有中间商CA做代理, 那么根CA将会没有那么繁忙。

  • 安全问题, 如果只用一把私钥对证书进行签名, 那么如果这把私钥被攻破, 那么就可以伪造CA对非法的证书进行签发。如果增加证书的层次, 那么攻破证书密钥的代价将会加大。 根据上面证书链工作流程的介绍, 你需要同时攻破中间CA和根CA的密钥 才有能力签发一个终端证书。

  • crypto/x509 操作证书与证书链

    首先新建一个 cert_demo 文件夹, 命令行输入:

    go mod init cert_demo
    go mod tidy 
    

    新建wiki_cert 和 my_cert, 整个工程目录如下:

    cert_demo
        -- my_cert  用于自己生成一个证书链并进行校验
        -- wiki_cert 校验 Wiki的证书链
            -- _.wikipedia.org.crt
            -- DigiCert Global Root CA.crt
            -- DigiCert TLS Hybrid ECC SHA384 2020 CA1.crt
            -- main.go 
        -- my_cert 自签名根证书生成证书链 与 证书链验签
              -- root_cert.pem
             -- root_keyt.pem
             -- end_cert.pem
             -- end_key.pem
             -- intermediate_cert.pem
             -- intermediate_keyt.pem
             -- main.go
           
        -- go.mod
    
    

    crypto/x509 查看 WIKI 证书

    我们在chrome 浏览器证书框框中将 WIKI 的证书链下载下来 放到 wiki_cert 目录下。 为了方便大家, 我直接把三个证书的内容贴出来:

    终端证书 _.wikipedia.org.crt:

    -----BEGIN CERTIFICATE-----
    MIIIRjCCB82gAwIBAgIQBA+IZ3/wsMPZQoYCYhbs6TAKBggqhkjOPQQDAzBWMQsw
    CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp
    Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjIxMDI3MDAw
    MDAwWhcNMjMxMTE3MjM1OTU5WjB5MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2Fs
    aWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEjMCEGA1UEChMaV2lraW1l
    ZGlhIEZvdW5kYXRpb24sIEluYy4xGDAWBgNVBAMMDyoud2lraXBlZGlhLm9yZzBZ
    MBMGByqGSM49AgEGCCqGSM49AwEHA0IABBunO0XX0ZSDUbkgc67z+3evNIgVrp7b
    5qKdmNXX094RZd17H7QO5TTA+6J97wfN+mSuRVIt3UxDOKFp9GBsrAmjggZYMIIG
    VDAfBgNVHSMEGDAWgBQKvAgpF4ylOW16Ds4zxy6z7fvDejAdBgNVHQ4EFgQUtoeZ
    tLdKugAbAR2Fy6v0N43u9akwggLtBgNVHREEggLkMIIC4IIPKi53aWtpcGVkaWEu
    b3Jngg13aWtpbWVkaWEub3Jngg1tZWRpYXdpa2kub3Jngg13aWtpYm9va3Mub3Jn
    ggx3aWtpZGF0YS5vcmeCDHdpa2luZXdzLm9yZ4INd2lraXF1b3RlLm9yZ4IOd2lr
    aXNvdXJjZS5vcmeCD3dpa2l2ZXJzaXR5Lm9yZ4IOd2lraXZveWFnZS5vcmeCDndp
    a3Rpb25hcnkub3Jnghd3aWtpbWVkaWFmb3VuZGF0aW9uLm9yZ4IGdy53aWtpghJ3
    bWZ1c2VyY29udGVudC5vcmeCESoubS53aWtpcGVkaWEub3Jngg8qLndpa2ltZWRp
    YS5vcmeCESoubS53aWtpbWVkaWEub3JnghYqLnBsYW5ldC53aWtpbWVkaWEub3Jn
    gg8qLm1lZGlhd2lraS5vcmeCESoubS5tZWRpYXdpa2kub3Jngg8qLndpa2lib29r
    cy5vcmeCESoubS53aWtpYm9va3Mub3Jngg4qLndpa2lkYXRhLm9yZ4IQKi5tLndp
    a2lkYXRhLm9yZ4IOKi53aWtpbmV3cy5vcmeCECoubS53aWtpbmV3cy5vcmeCDyou
    d2lraXF1b3RlLm9yZ4IRKi5tLndpa2lxdW90ZS5vcmeCECoud2lraXNvdXJjZS5v
    cmeCEioubS53aWtpc291cmNlLm9yZ4IRKi53aWtpdmVyc2l0eS5vcmeCEyoubS53
    aWtpdmVyc2l0eS5vcmeCECoud2lraXZveWFnZS5vcmeCEioubS53aWtpdm95YWdl
    Lm9yZ4IQKi53aWt0aW9uYXJ5Lm9yZ4ISKi5tLndpa3Rpb25hcnkub3JnghkqLndp
    a2ltZWRpYWZvdW5kYXRpb24ub3JnghQqLndtZnVzZXJjb250ZW50Lm9yZ4INd2lr
    aXBlZGlhLm9yZ4IRd2lraWZ1bmN0aW9ucy5vcmeCEyoud2lraWZ1bmN0aW9ucy5v
    cmcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
    AjCBmwYDVR0fBIGTMIGQMEagRKBChkBodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v
    RGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEzODQyMDIwQ0ExLTEuY3JsMEagRKBChkBo
    dHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRUTFNIeWJyaWRFQ0NTSEEz
    ODQyMDIwQ0ExLTEuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQICMCkwJwYIKwYBBQUH
    AgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBhQYIKwYBBQUHAQEEeTB3
    MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTwYIKwYBBQUH
    MAKGQ2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5YnJp
    ZEVDQ1NIQTM4NDIwMjBDQTEtMS5jcnQwCQYDVR0TBAIwADCCAX8GCisGAQQB1nkC
    BAIEggFvBIIBawFpAHcA6D7Q2j71BjUy51covIlryQPTy9ERa+zraeF3fW0GvW4A
    AAGEG2zFZQAABAMASDBGAiEA5L5Jpsq64lCWlQbq7mKJ/4asldTMzrON2Hv5rlmq
    aZ0CIQDkpVM7uC4AGnshm8S0HCIr33mDJRyUZqvfTXJHc8Ei6gB2ALNzdwfhhFD4
    Y4bWBancEQlKeS2xZwwLh9zwAw55NqWaAAABhBtsxVAAAAQDAEcwRQIhAO+qMSht
    9Pcc26DFhE3XiU6N9PiM5NG3I3efGzw1+KzCAiBUpzu15eB9rl/FultM7vWaUtp6
    woWx60qUJ+e98jrryQB2ALc++yTfnE26dfI5xbpY9Gxd/ELPep81xJ4dCYEl7bSZ
    AAABhBtsxScAAAQDAEcwRQIhAOOQIeH37/+ByHcSzyXB5/4mAYv99yQNfktX6CJc
    Srg+AiA0wAo3EPL2bklC7O1/N47u0MjrwpPoBowfkLPrTkz4hjAKBggqhkjOPQQD
    AwNnADBkAjAZ/9EwjPo68aBw5JSuySDx1VgraBMKGStXFQnQ3d2LS5YHs3yOp+GZ
    D+hjGaKpeGUCMHa7GIyN3PYkcUJi2LLpAVU6LpRHbiv3vaiYAtvJL2wX4NXowGil
    JFmSn17Sru13GQ==
    -----END CERTIFICATE-----
    

    中间证书 DigiCert TLS Hybrid ECC SHA384 2020 CA1.crt:

    -----BEGIN CERTIFICATE-----
    MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh
    MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
    d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
    QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT
    MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI
    eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA
    BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ
    qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v
    c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5
    bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G
    A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI
    KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j
    b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp
    Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny
    bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE
    NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG
    BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr
    6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY
    kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/
    BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos
    Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh
    xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA==
    -----END CERTIFICATE-----
    

    根证书 DigiCert Global Root CA.crt:

    -----BEGIN CERTIFICATE-----
    MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
    MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
    d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
    QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
    MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
    b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
    9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
    CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
    nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
    43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
    T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
    gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
    BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
    TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
    DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
    hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
    06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
    PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
    YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
    CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
    -----END CERTIFICATE-----
    

    然后新建一个 main.go 用于查看证书内容并校验证书链的有效性。

    package main
    
    import (
       "crypto/x509"
       "encoding/pem"
       "fmt"
       "os"
    )
    
    func main() {
    
       // Load the root CA certificate
       rootCertBytes, err := os.ReadFile("DigiCert Global Root CA.crt")
       if err != nil {
          panic(err)
       }
    
       rootBlocks, _ := pem.Decode(rootCertBytes)
       if rootBlocks == nil {
          panic("failed to parse certificate PEM")
       }
    
       rootCert, err := x509.ParseCertificate(rootBlocks.Bytes)
       if err != nil {
          panic(err)
       }
    
       fmt.Println("=============== start of root cert ===============")
       fmt.Println("rootCert.Issuer.CommonName = ", rootCert.Issuer.CommonName)
       fmt.Println("rootCert.Subject.CommonName = ", rootCert.Subject.CommonName)
       fmt.Println("rootCert.NotBefore = ", rootCert.NotBefore.Format("2006-01-02 15:04:05"))
       fmt.Println("rootCert.NotAfter = ", rootCert.NotAfter.Format("2006-01-02 15:04:05"))
       fmt.Println("rootCert.PublicKeyAlgorithm = ", rootCert.PublicKeyAlgorithm)
       fmt.Println("rootCert.PublicKey = ", rootCert.PublicKey)
       fmt.Println("rootCert.Signature = ", rootCert.Signature)
    
       // Load the intermediate CA certificate
       intermediateCertBytes, err := os.ReadFile("DigiCert TLS Hybrid ECC SHA384 2020 CA1.crt")
       if err != nil {
          panic(err)
       }
    
       intermediateBlocks, _ := pem.Decode(intermediateCertBytes)
       if intermediateBlocks == nil {
          panic("failed to parse certificate PEM")
       }
    
       intermediateCert, err := x509.ParseCertificate(intermediateBlocks.Bytes)
       if err != nil {
          panic(err)
       }
    
       fmt.Println("n=============== start of intermediate cert ===============")
       fmt.Println("intermediateCert.Issuer.CommonName = ", intermediateCert.Issuer.CommonName)
       fmt.Println("intermediateCert.Subject.CommonName = ", intermediateCert.Subject.CommonName)
       fmt.Println("intermediateCert.NotBefore = ", intermediateCert.NotBefore.Format("2006-01-02 15:04:05"))
       fmt.Println("intermediateCert.NotAfter = ", intermediateCert.NotAfter.Format("2006-01-02 15:04:05"))
       fmt.Println("intermediateCert.PublicKeyAlgorithm = ", intermediateCert.PublicKeyAlgorithm)
       fmt.Println("intermediateCert.PublicKey = ", intermediateCert.PublicKey)
       fmt.Println("intermediateCert.Signature = ", intermediateCert.Signature)
    
       // Load the end-user certificate
       endCertBytes, err := os.ReadFile("_.wikipedia.org.crt")
       if err != nil {
          panic(err)
       }
    
       endBlocks, _ := pem.Decode(endCertBytes)
       if endBlocks == nil {
          panic("failed to parse certificate PEM")
       }
    
       endCert, err := x509.ParseCertificate(endBlocks.Bytes)
       if err != nil {
          panic(err)
       }
    
       fmt.Println("n=============== start of end cert ===============")
       fmt.Println("endCert.Issuer.CommonName = ", endCert.Issuer.CommonName)
       fmt.Println("endCert.Subject.CommonName = ", endCert.Subject.CommonName)
       fmt.Println("endCert.NotBefore = ", endCert.NotBefore.Format("2006-01-02 15:04:05"))
       fmt.Println("endCert.NotAfter = ", endCert.NotAfter.Format("2006-01-02 15:04:05"))
       fmt.Println("endCert.PublicKeyAlgorithm = ", endCert.PublicKeyAlgorithm)
       fmt.Println("endCert.PublicKey = ", endCert.PublicKey)
       fmt.Println("endCert.PublicKey = ", endCert.PublicKey)
       fmt.Println("endCert.Signature = ", endCert.Signature)
    
       // Construct the certificate chain
       certPool := x509.NewCertPool()
       certPool.AddCert(rootCert)
       certPool.AddCert(intermediateCert)
    
    
       // Verify the end-user certificate chain
       _, err = endCert.Verify(x509.VerifyOptions{
          Roots:         certPool,
       })
    
       if err != nil {
          panic(err)
       }
    
       fmt.Println("nWIKI's certificate chain is valid")
    }
    

    我们使用 os.ReadFile 读取证书文件, 再使用 pem.Decode 对证书文件进行解码(X509有两种格式编码, .pem 与 .der格式), 随后使用x509.ParseCertificate 对证书内容进行解析, 然后使用 x509.NewCertPool 构建一个证书链, 最后使用 Certificate.verity 函数校验证书链有效性。
    运行结果如下:

    =============== start of root cert ===============
    rootCert.Issuer.CommonName =  DigiCert Global Root CA
    rootCert.Subject.CommonName =  DigiCert Global Root CA
    rootCert.NotBefore =  2006-11-10 00:00:00
    rootCert.NotAfter =  2031-11-10 00:00:00
    rootCert.PublicKeyAlgorithm =  RSA
    rootCert.PublicKey =  &{285593844427928762732802743986205789797337868177841749601124001697190659063014719123402043911640757309877712552814791918585
    039123799744433633192060132859229329691430821141089959035073026073721641078463955261699288495469303527786129468113353499174244691889175009962536194
    383842187217442787871642746252437819172374442022293396722341133509359482645761803424926911179603760237386273491504411524871201973330424488341547799
    668012770940705281669189684124330788799396640530447971169162600950556415835061700452415491050223238193141636257988345135444201652354121056946816165
    78431019525684868803389424296613694298865514217451303 65537}
    rootCert.Signature =  [203 156 55 170 72 19 18 10 250 221 68 156 79 82 176 244 223 174 4 245 121 121 8 163 36 24 252 75 43 132 192 45 185 213 199 2
    54 244 193 31 88 203 184 109 156 122 116 231 152 41 171 17 181 227 112 160 161 205 76 136 153 147 140 145 112 226 171 15 28 190 147 169 255 99 213 
    228 7 96 211 163 191 157 91 9 241 213 142 227 83 244 142 99 250 63 167 219 180 102 223 98 102 214 209 110 65 141 242 45 181 234 119 74 159 157 88 2
    26 43 89 192 64 35 237 45 40 130 69 62 121 84 146 38 152 224 128 72 168 55 239 240 214 121 96 22 222 172 232 14 205 110 172 68 23 56 47 73 218 225 
    69 62 42 185 54 83 207 58 80 6 247 46 232 196 87 73 108 97 33 24 213 4 173 120 60 44 58 128 107 167 235 175 21 20 233 216 137 193 185 56 108 226 14
    5 108 138 255 100 185 119 37 87 48 192 27 36 163 225 220 233 223 71 124 181 180 36 8 5 48 236 45 189 11 191 69 191 80 185 169 243 235 152 1 18 173 
    200 136 198 152 52 95 141 10 60 198 233 213 149 149 109 222]
    
    =============== start of intermediate cert ===============
    intermediateCert.Issuer.CommonName =  DigiCert Global Root CA
    intermediateCert.Subject.CommonName =  DigiCert TLS Hybrid ECC SHA384 2020 CA1
    intermediateCert.NotBefore =  2021-04-14 00:00:00
    intermediateCert.NotAfter =  2031-04-13 23:59:59
    intermediateCert.PublicKeyAlgorithm =  ECDSA
    intermediateCert.PublicKey =  &{0x1106ff0 297221182878416596764291921304050291726196947274046366828144692949351603683433721507553709277508963909470
    26983438760 27783767732617047952280214471820522996239123706494474477997286866364877480990705578538474377391077176892030697025395}
    intermediateCert.Signature =  [71 89 129 127 212 27 31 176 113 246 152 93 24 186 152 71 152 176 126 118 43 234 255 26 139 172 38 179 66 141 49 230 
    74 232 25 208 239 218 20 231 215 20 146 161 146 242 167 46 45 175 251 29 246 251 83 176 138 63 252 216 22 10 233 176 46 182 165 11 24 144 53 38 162
     218 246 168 183 50 252 149 35 75 198 69 185 196 207 228 124 238 230 201 248 144 189 114 227 153 195 29 11 5 124 106 151 109 178 171 2 54 216 194 1
    88 44 1 146 63 4 163 139 117 17 199 185 41 188 17 208 134 186 146 188 38 249 101 200 55 205 38 246 134 19 12 4 170 137 229 120 177 193 78 121 188 1
    18 163 11 81 228 197 208 158 106 254 26 44 86 174 6 54 39 163 115 28 8 125 147 50 208 194 68 25 218 141 244 14 123 29 40 3 43 9 138 118 202 119 220
     135 122 172 123 82 38 85 167 114 15 157 210 136 79 254 177 33 197 26 161 170 57 245 86 219 194 132 196 53 31 112 218 187 70 240 134 191 100 0 196 
    62 247 159 70 27 157 35 5 185 125 179 79 15 169 69 58 227 116 48 152]
    
    =============== start of end cert ===============
    endCert.Issuer.CommonName =  DigiCert TLS Hybrid ECC SHA384 2020 CA1
    endCert.Subject.CommonName =  *.wikipedia.org
    endCert.NotBefore =  2022-10-27 00:00:00
    endCert.NotAfter =  2023-11-17 23:59:59
    endCert.PublicKeyAlgorithm =  ECDSA
    endCert.PublicKey =  &{0x1106fe0 12507919457565322318772319154696329376339391912668318301946860368094221819409 460749206777472701698036085329604752
    13529674547223847927482644208817122618377}
    endCert.PublicKey =  &{0x1106fe0 12507919457565322318772319154696329376339391912668318301946860368094221819409 460749206777472701698036085329604752
    13529674547223847927482644208817122618377}
    endCert.Signature =  [48 100 2 48 25 255 209 48 140 250 58 241 160 112 228 148 174 201 32 241 213 88 43 104 19 10 25 43 87 21 9 208 221 221 139 75 
    150 7 179 124 142 167 225 153 15 232 99 25 162 169 120 101 2 48 118 187 24 140 141 220 246 36 113 66 98 216 178 233 1 85 58 46 148 71 110 43 247 18
    9 168 152 2 219 201 47 108 23 224 213 232 192 104 165 36 89 146 159 94 210 174 237 119 25]
    
    WIKI's certificate chain is valid
    

    crypto/x509 生成证书链并校验

    根据示意图的分析, 我们已经知道了如何构造一个证书链, 那么现在我们自己来生成一个证书链, 然后还是用同样的方法来校验一下证书链。

    main.go

    package main
    
    import (
       "crypto/rand"
       "crypto/rsa"
       "crypto/x509"
       "crypto/x509/pkix"
       "encoding/pem"
       "fmt"
       "math/big"
       "os"
       "time"
    )
    
    func main() {
    
       // Generate root cert
       rootTemplate := &x509.Certificate{
          SerialNumber: big.NewInt(1),
          Subject: pkix.Name{
             Organization: []string{"My Organization"},
             CommonName:   "Root CA",
          },
          NotBefore:             time.Now(),
          NotAfter:              time.Now().AddDate(10, 0, 0),
          BasicConstraintsValid: true,
          IsCA:                  true,
          KeyUsage:              x509.KeyUsageCertSign,
       }
       rootPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
       rootCertificate, _ := x509.CreateCertificate(rand.Reader, rootTemplate, rootTemplate, &rootPrivateKey.PublicKey, rootPrivateKey)
    
       // Ggenerate intermediate cert
       intermediateTemplate := &x509.Certificate{
          SerialNumber: big.NewInt(2),
          Subject: pkix.Name{
             Organization: []string{"My Organization"},
             CommonName:   "Intermediate CA",
          },
          NotBefore:             time.Now(),
          NotAfter:              time.Now().AddDate(5, 0, 0),
          BasicConstraintsValid: true,
          IsCA:                  true,
          KeyUsage:              x509.KeyUsageCertSign,
       }
       intermediatePrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
       intermediateCertificate, _ := x509.CreateCertificate(rand.Reader, intermediateTemplate, rootTemplate, &intermediatePrivateKey.PublicKey, rootPrivateKey) // 使用根证书的私钥进行签名
    
       // Generate end-user cert
       endTemplate := &x509.Certificate{
          SerialNumber: big.NewInt(3),
          Subject: pkix.Name{
             Organization: []string{"My Organization"},
             CommonName:   "end-user company",
          },
          NotBefore:             time.Now(),
          NotAfter:              time.Now().AddDate(1, 0, 0),
          BasicConstraintsValid: false,
          IsCA:                  false,
          KeyUsage:              x509.KeyUsageDigitalSignature,
       }
       endPrivateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
       endCertificate, _ := x509.CreateCertificate(rand.Reader, endTemplate, intermediateTemplate, &endPrivateKey.PublicKey, intermediatePrivateKey)
    
       // Write certs and keys into files
       rootCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: rootCertificate})
       intermediateCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: intermediateCertificate})
       endCertPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: endCertificate})
       rootKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(rootPrivateKey)})
       intermediateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(intermediatePrivateKey)})
       endKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(endPrivateKey)})
    
       rootCertFile, _ := os.Create("root_cert.pem")
       defer rootCertFile.Close()
       rootCertFile.Write(rootCertPEM)
    
       intermediatetCertFile, _ := os.Create("intermediate_cert.pem")
       defer intermediatetCertFile.Close()
       intermediatetCertFile.Write(intermediateCertPEM)
    
       endCertFile, _ := os.Create("end_cert.pem")
       defer endCertFile.Close()
       endCertFile.Write(endCertPEM)
    
       rootKeyFile, _ := os.Create("root_key.pem")
       defer rootKeyFile.Close()
       rootKeyFile.Write(rootKeyPEM)
    
       intermediateKeyFile, _ := os.Create("intermediate_key.pem")
       defer intermediateKeyFile.Close()
       intermediateKeyFile.Write(intermediateKeyPEM)
    
       endKeyFile, _ := os.Create("end_key.pem")
       defer endKeyFile.Close()
       endKeyFile.Write(endKeyPEM)
    
       // Load root CA cert
       rootCertBytes, err := os.ReadFile("root_cert.pem")
       if err != nil {
          panic(err)
       }
    
       rootBlocks, _ := pem.Decode(rootCertBytes)
       if rootBlocks == nil {
          panic("failed to parse certificate PEM")
       }
    
       rootCert, err := x509.ParseCertificate(rootBlocks.Bytes)
       if err != nil {
          panic(err)
       }
    
       // Load  intermediate CA cert
       intermediateCertBytes, err := os.ReadFile("intermediate_cert.pem")
       if err != nil {
          panic(err)
       }
    
       intermediateBlocks, _ := pem.Decode(intermediateCertBytes)
       if rootBlocks == nil {
          panic("failed to parse certificate PEM")
       }
    
       intermediateCert, err := x509.ParseCertificate(intermediateBlocks.Bytes)
       if err != nil {
          panic(err)
       }
    
       // Load end-user cert
       endCertBytes, err := os.ReadFile("end_cert.pem")
       if err != nil {
          panic(err)
       }
    
       endBlocks, _ := pem.Decode(endCertBytes)
       if rootBlocks == nil {
          panic("failed to parse certificate PEM")
       }
    
       endCert, err := x509.ParseCertificate(endBlocks.Bytes)
       if err != nil {
          panic(err)
       }
    
       // Construct the cert chain
       certPool := x509.NewCertPool()
       certPool.AddCert(rootCert)
       certPool.AddCert(intermediateCert)
    
       // Verify the cert chain
       _, err = endCert.Verify(x509.VerifyOptions{
          Roots:         certPool,
          Intermediates: x509.NewCertPool(),
       })
    
       if err != nil {
          panic(err)
       }
    
       fmt.Println("My certificate chain is valid")
    
    }
    

    运行结果:

    Certificate chain is valid
    

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论