证书(Certificate)的那些事

项目开发中证书的使用梳理

Posted by pandaychen on July 24, 2019

0x00 前言

在日常的后台开发工作中,会遇到认证的场景,比如 API 接口认证,Cookies 认证,OpenSSH 认证,SSL 认证,SSO 等,认证的方式也是百花齐放,比如 Password、HttpBasicAuth,JWT,证书,OAuth 等等。证书的不可伪造特性(Unforgeability)决定了证书在现有的公钥认证体系中扮演了及其重要的角色。这篇文章,就好好聊聊我在项目中使用到的两种证书,第一类是 X.509 证书,第二类是 OpenSSH 证书。

0x01 X.509 证书体系

一些基础名词

日常遇到的一些概念(单词):

  1. PKI = Public Key Infrastructure,是一整套安全相关标准
  2. CA = Certificate authority, 是 PKI 的”核心”,即数字证书的申请及签发机关,CA 必须具备权威性的特征,它负责管理 PKI 结构下的所有用户 (包括各种应用程序) 的证书,把用户的公钥和用户的其他信息捆绑在一起,在网上验证用户的身份,CA 还要负责用户证书的黑名单登记和黑名单发布 。
  3. X.509,当前使用很广泛的一套证书标准,它规范了公开密钥认证、证书吊销列表、授权证书、证书路径验证算法、证书内容及格式等(如 https 证书)。X509 V3 版本的证书基本语法如下(只列举了 CertificateTBSCertificate 这两个结构),其他的描述见 RFC5280。其中 tbsCertificate 的数据段被拿来做 Digest,并且用上级证书的私钥加密后形成签名置入 https 证书中
    Certificate  ::=  SEQUENCE  {
        tbsCertificate       TBSCertificate,
        signatureAlgorithm   AlgorithmIdentifier,
        signatureValue       BIT STRING  }
        TBSCertificate  ::=  SEQUENCE  {
        version         [0]  EXPLICIT Version DEFAULT v1,
        serialNumber         CertificateSerialNumber,
        signature            AlgorithmIdentifier,
        issuer               Name,
        validity             Validity,
        subject              Name,
        subjectPublicKeyInfo SubjectPublicKeyInfo,
        issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version MUST be v2 or v3
        subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
                             -- If present, version MUST be v2 or v3
        extensions      [3]  EXPLICIT Extensions OPTIONAL
                             -- If present, version MUST be v3
        }
    
  4. CSR = Certificate signing request,用户申请 CA 证书的签名申请

CA 实现了 PKI 体系 中一些非常重要的功能,核心功能就是 “发放” 和 “管理” 数字证书,证书抽象起来就是:CSR+ 签发者公钥 + 签名 + 有效期:

  • 接收验证最终用户数字证书的申请
  • 确定是否接受最终用户数字证书的申请,即证书的审批(签发)
  • 向申请者颁发、拒绝颁发数字证书
  • 接收、处理最终用户的数字证书更新请求
  • 接收最终用户数字证书的查询、撤销
  • 产生和发布证书废止列表
  • 数字证书的归档、密钥归档及历史数据归档

公钥体系与 Diffie-Hellman 协议

说到证书,不得不先引入下 公钥体系 的概念:

  1. 公开密钥基础建设透过信任数字证书认证机构的根证书、及其使用公开密钥加密作数字签名核发的公开密钥认证,形成信任链架构,已在 TLS 实现并在万维网的 HTTP 以 HTTPS、在电子邮件的 SMTP 以 STARTTLS 引入
  2. 公钥解密的特性可以形成数字签名,使数据和文件受到保护并可信赖;如果公钥透过数字证书认证机构签授成为电子证书,更可作为数字身份的认证,这都是对称密钥加密无法实现的
  3. 不过,公钥加密在在计算上相当复杂,性能欠佳、远远不比对称加密;因此,在一般实际情况下,往往通过公钥加密来随机创建临时的对称秘钥,亦即对话键,然后才通过对称加密来传输大量、主体的数据

上面的第 3 点,就引出了,经典的秘钥交换(协商)协议 Diffie-Hellman,解决了不同实体间,由公钥对(Public Key Pair)协商出对称(共享)秘钥的过程。Openssh 的通信过程就是基于此来实现的、https 亦是。 贴一张非常经典的 Diffie-Hellman 原理图(来自 wikipedia) image

证书的基础

证书的作用是用来实现身份认证,举个例子来说,A 和 B 需要认证对方,但 A、B 都信任可信中间人 C,如果 C 分别颁发证书 CERT-A 和 CERT-B 给 A 和 B,那么 A、B 在交换证书时,分别验证证书中 C 的身份,那么 A 和 B 就完成了互相认证。在当今的流行的应用中,证书在系统安全的构建中也是极为广泛,比如 Kubernetes 系统中的使用的证书对。通常,证书就是一个包含如下身份信息的文件:

证书所有组织的信息
颁发者公钥
证书颁发组织的信息
证书颁发组织授予的权限,如证书有效期、适用的主机名、用途等
使用证书颁发组织私钥创建的数字签名
证书申请者提交的 CSR 属性

既然是文件,那么必可不少的就是文件的编码属性:

  • ASN.1(Abstract Syntax Notation One),一种描述数字对象的方法和标准。ASN.1 提供了多种数据编码方法。包括了 BER、DER、PER 和 XER 等。这些编码方法规定了将数字对象转换成应用程序能够处理、保存和网络传输的二进制编码形式的一组规则
  • DER 编码(Distinguished Encoding Rules):属于 ASN.1 下的 BER(Basic Encode Rules)编码派生出来的编码规则,这种编码规则下,一个相同的 ASN.1 对象编码后能得到唯一的编码数据(BER 编码不能保证这一点,即一个对象编码后可能产生多个不同的编码数据)
  • PEM 编码(Privacy Enhanced Mail):是一种保密邮件的编码标准,在 rfc1421 规范中规定。X.509 的证书在 DER 编码的基础上进行 base64 编码,然后添加一些头、尾标志就是 PEM 格式编码了,头尾的标志也是 PEM 的一部分,不要随意改动

在项目中最常用的是 PEM 编码格式,PEM 格式的文件如下,Head 和 Tail 告诉我们这是一个 Certificate:

-----BEGIN CERTIFICATE-----
*************************
-----END CERTIFICATE-----

X.509 证书的结构图如下,从图中可以清晰的看出 V1/V2/V3 版本的区别: image

X.509 证书基本部分如下:

版本:标识证书的版本 (版本 1、2、3),现行通用版本是 V3
序号:标识证书的唯一整数,由证书颁发者分配的本证书的唯一标识符,特别在撤消证书的时候有用
主体:拥有此证书的法人或自然人身份或机器,包括:
> 国家(C,Country)
> 州 / 省(S,State)
> 地域 / 城市(L,Location)
> 组织 / 单位(O,Organization)
> 通用名称(CN,Common Name):在 TLS 应用上,此字段一般是网域
发行者:以数字签名形式签署此证书的数字证书认证机构
有效期开始时间:Not Before,此证书的有效开始时间,在此前该证书并未生效
有效期结束时间:Not After,此证书的有效结束时间,在此后该证书作废
公开密钥用途:指定证书上公钥的用途,例如数字签名、服务器验证、客户端验证等
公开密钥:主体的公钥(以及算法标识符)
公开密钥指纹
数字签名:用于签证书的算法标识,由对象标识符加上相关的参数组成,用于说明本证书所用的数字签名算法。例如,SHA-1 和 RSA 的对象标识符就用来说明该数字签名是利用 RSA > 对 SHA-1 杂凑加密
数字签名算法
主体别名:例如一个网站可能会有多个网域(www.wikipedia.org, zh.wikipedia.org, zh.m.wikipedia.org 都是维基百科)、一个组织可能会有多个网站(*.wikipedia.org, *.wikibooks.org, *.wikidata.org 都是维基媒体基金会旗下的网域),不同的网域可以一并使用同一张证书,方便实现应用及管理

CSR(Certificate Signing Request) 的意义

CSR(Certificate Signing Request),它是向 CA 机构申请数字证书时使用的请求文件。在生成请求文件前,我们需要准备一对对称密钥。私钥信息自己保存,请求中会附上公钥信息以及国家,城市,域名,Email 等信息,CSR 中还会附上签名信息。当我们准备好 CSR 文件后就可以提交给 CA 机构,等待他们给我们签名,签好名后我们会收到 crt 文件,即证书。 注意:CSR 并不是证书。而是向权威证书颁发机构获得签名证书的申请。CSR 的主要作用是 CA 会利用 CSR 文件进行签名使得攻击者无法伪装或篡改原有证书 把 CSR 交给权威证书颁发机构, 权威证书颁发机构对此进行签名, 完成。保留好 CSR, 当权威证书颁发机构颁发的证书过期的时候, 你还可以用同样的 CSR 来申请新的证书, key 保持不变。

在 CSR 中,比较重要的字段是 Common Name(服务器一般会校验这个字段), 另外在 Kubernetes RBAC 体系对证书的鉴权中,用 Common Name 表示用户名,Organization 表示用户组,因为证书一经签发后,所有的字段都是无法被伪造的,这样拿来做权限管控实在太合适不过了。

#生成的 k8s 证书,用户名 ==TEST,用户组 ==admin
USER_NAME=TEST
openssl req -new -key certs/$USER_NAME.key -out certs/$USER_NAME.csr \
-subj "/CN=$USER_NAME/O=admin"

后台服务的双向认证

开发中经常会遇到双向认证的需求,双向认证就是 Client 和 Server 的证书对都是由一个可信任的第三方签发的,客户端认证服务器证书,服务器认证客户端的证书。通俗一点讲,客户端要认证服务器的身份,而服务器也要识别客户端的身份,后面会有一片文章单独介绍项目中 gRPC 和 HTTPS 的双向认证实现。 image

签发 X509 证书

证书签发的过程,如下图,包含了 CA 的生成和 Server 证书的生成两个步骤: image 关于 CA 证书的生成,一般在我们实际内部项目中,大都是使用自签证书,ca.key 由自己生成和保管,使用 ca.key 签发下一层级的证书。下面我们就来操作下证书生成及签发这一过程。

自建证书体系包含了,CA 根证书、客户端证书及服务端证书。

最终的存储目录如下:

conf
├── ca.key  -- caroot 的私钥
├── ca.pem  -- caroot 的根证书
├── ca.srl
├── client
│   ├── client.csr  -- client 请求签名文件
│   ├── client.key  -- client 的私钥
│   └── client.pem  -- client 的证书(由 caroot 签发)
└── server
    ├── server.csr
    ├── server.key
    └── server.pem


1、第一步,创建 CA 私钥和根证书,根证书(Root Certificate)是属于根证书颁发机构(CA)的公钥证书。我们可以通过验证 CA 的签名从而信任 CA ,任何人都可以得到 CA 的证书(含公钥),用以验证它所签发的下一级证书(客户端、服务端)
生成 CA 的私钥 Key

openssl genrsa -out ca.key 2048

生成 CA 自签的证书 Certificate

openssl req -new -x509 -days 7200 -key ca.key -out ca.pem

填写 CSR 信息

Country Name (2 letter code) []:CN
State or Province Name (full name) []:GD
Locality Name (eg, city) []:SZ
Organization Name (eg, company) []:SZ
Organizational Unit Name (eg, section) []:IEG
Common Name (eg, fully qualified host name) []: TEST-CA-ROOT
Email Address []:

上述操作生成了自签的 CA 私钥 + 证书,接下来就可以利用此来签发应用实体的证书对了

2、第二步,基于 CA 来签发客户端 Client 的证书
生成 Client 的私钥 Key(采用 elliptic curves 算法)

openssl ecparam -genkey -name secp384r1 -out client.key

生成 Client 的 CSR,填写相关属性:

openssl req -new -key client.key -out client.csr

填写 CSR 信息

Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:GD
Locality Name (eg, city) []:SZ
Organization Name (eg, company) [Internet Widgits Pty Ltd]:SZ
Organizational Unit Name (eg, section) []:IEG
Common Name (e.g. server FQDN or YOUR name) []:CLIENT-CERT
Email Address []:

使用 CA 的私钥 + CA 自签证书 签发客户端的证书

openssl x509 -req -sha256 -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -in client.csr -out client.pem

最终生成的客户端证书如下,服务端 Server 的证书也可以类似完成。值得注意的是:默认生成的证书版本号是 V1,非 V3 格式。

openssl x509 -in client.pem -noout -text

image

校验 X.509 证书

介绍下 X.509 证书的校验方法(如图),校验方法是基于证书信任链(Certificate Trusted Chain)的结构,由下层自上验证到顶层 Root 根,具体步骤为: image

  1. 取上一级证书的公钥 public key,对下级证书的签名进行解密得出下级证书的摘要 $Digest_1$
  2. 对下级证书计算信息摘要(默认为 TBSCertificate),需要严格遵循 X509 的协议格式,得到 $Digest_2$
  3. 判断 $Digest_1 =? Digest_2$,相等则说明下级证书校验通过
  4. 依次对各个相邻级别证书实施 1--3 步骤,直到根证书(或者可信任锚点 trusted anchor

证书校验的过程其实不难理解,就是证书签发的 “逆” 过程。

在工作中,通常使用 openssl 工具进行校验,如下:
校验证书签名 signature(成功):

[root@VM_0_7_centos x509]# openssl verify -CAfile ca.crt user.pem
user.pem: OK

校验 public 的值(成功):

[root@VM_0_7_centos x509]# diff -eq < (openssl x509 -pubkey -noout -in user.pem) <(openssl rsa -pubout -in server.key)
writing RSA key

0x02 OpenSSH 的证书体系

比起庞大的 X.509 证书体系,OpenSSH 的证书体系就简单的多了。OpenSSH 的证书体系只简单包含了:(双方)信任的关系,以及在 SSH 登录中需要的一些基础属性(如登录用户、证书有效时间及 SSH 的连接属性等)

OpenSSH 证书介绍

使用 OpenSSH 证书认证 这篇文章详细介绍了 OpenSSH 中对证书的支持和使用,做为互联网十分基础和重要的组件,证书的引入对 SSH 的整体安全性提升还是很有必要的。 相比于密码、公私鈅这种静态的票据(认为静态的票据是不安全的),具备动态性(签发 / 吊销)生成、有效期以及关联身份,极大的丰富了 OpenSSH 的认证体系和提升了 OpenSSH 的应用安全性。

OpenSSH 5.4(上古版本)增加了一种新的证书认证方法 (changes).

  • Add support for certificate authentication of users and hosts using a new, minimal OpenSSH certificate format (not X.509). Certificates contain a public key, identity information and some validity constraints and are signed with a standard SSH public key using ssh-keygen(1). CA keys may be marked as trusted in authorized_keys or via a TrustedUserCAKeys option in sshd_config(5) (for user authentication), or in known_hosts (for host authentication).

    Documentation for certificate support may be found in ssh-keygen(1), sshd(8) and ssh(1) and a description of the protocol extensions in PROTOCOL.certkeys.

OpenSSH 认证原理

  1. 传统的 SSH Key 登录认证的过程,必须将客户端的公钥 public key 放在待登录服务器对应登录用户的 ~/.ssh/authorized_keys 文件中 image

  2. 使用了 SSH 证书登录的过程,可以从图中直观看到,SSH 证书登录的 Key 秘钥对,是由可信 CA 签发的 image

OpenSSH 证书签发和校验

image

1、生成 CA 私钥,记为$CA_{pri}$

ssh-keygen -f ca -C "test CA key"
-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxx
-----END RSA PRIVATE KEY-----

2、由 CA 私钥生成 CA 公钥 $CA_{pub}$ ,公钥本身就是可以公开的

ssh-keygen -y -f ca

3、查看公钥 $CA_{pub}$ 的 SHA-256 hash 值

[root@VM_0_7_centos .ssh]# ssh-keygen -E sha256 -lf ca.pub
2048 SHA256:****************************************jzw no comment (RSA)

4、生成临时秘钥对,$Temp_{pri}$、$Temp_{pub}$,查看 $Temp_{pub}$ 的 hash 值:

[root@VM_0_7_centos .ssh]# ssh-keygen -E sha256 -lf id_rsa.pub
2048 SHA256:****************************************G4L4 root@VM_0_7_centos (RSA)

5、签发证书

[root@VM_0_7_centos .ssh]# ssh-keygen -s ca -I "" -n panda,root -V +1d ~/.ssh/id_rsa.pub
Signed user key /root/.ssh/id_rsa-cert.pub: id "" serial 0 for panda,root valid from 2019-11-17T10:58:00 to 2019-11-18T10:59:12

6、查看签发的 OpenSSH 证书 image

7、验证证书 确认下证书中的核心信息是否如预期:

  • 验证证书中的公钥指纹是否相等
\[Public key == sha256(Temp_{pub})\] \[Signing CA == sha256(CA_{pub})\]
  • 有效期限:2019-11-17T10:58:00 to 2019-11-18T10:59:12
  • 登录用户: pandaroot

0x03 参考文档

转载请注明出处,本文采用 CC4.0 协议授权