SSL与CA

背景

无论是在使用docker过程中还是在接触gRPC的过程中,
如果想自己实现,都需要接触到SSL的问题,
因此想从理论到实践总结一下.

SSL/TLS

作用

  • 加密信息,使无法窃听
  • 校验内容,使篡改可发现
  • 配备身份证书,防止冒充

历史

94年 网景设计了Secure Sockets Layer 1.0
95年 2.0
96年 3.0并推出
99年 ISOC接管兵重命名为TLS 1.0
06年 TLS 1.1 (戏称SSL 3.1)
08年 TLS 1.2 (戏称SSL 3.2)
11年 TLS 1.2+ (戏称SSL 3.3)

基本思路

  1. 客户端索要,并验证服务端的证书,从中取出服务器的公钥
  2. 客户端与服务端用密钥对密谋一个session key
  3. 正常对话使用session key加密以保证速度

注:前两步是握手阶段,客户端对服务器的证书要做验证:

  • 是否可信的机构颁发的
  • 证书中的域名是否与服务端的域名相符
  • 证书是否过期

握手阶段具体过程

C: 随机数1
+自身情况(TLS版本,加密算法,压缩算法)
S: 随机数2
+自己的证书
+加密方法
C: 随机数3(用服务器公钥加密过)
+同意加密方法
+之前发送过的信息(供校验)
S: 同意加密方法
+算好的session key
+之前发送过的信息(供校验)

注:session key的计算中,服务器其实不信任客户端产生随机数的能力,
于是多用几个随机数,通过多几个自由度的方式让数字真的随机(伪随机数可能会被猜到)

CA

全称Certificate Authorities,证书颁发机构.
用于给网站,个人,设备等颁发数字证书以证明其真实可信的在线身份.

如果只有一个CA,那么它万一泄露私钥,波及范围极大.因此出现分级.

最上层的CA,证明一些二级CA,
各个网站和二级CA打交道,如果二级CA发生了私钥泄露,顶层CA及时吊销,可以保证受影响的范围小些.

PKI

PKI(Public Key Infrastructure,公钥基础设施)
主要由三部分构成

  1. 证书
  2. 证书颁发机构
  3. 证书库

就像电力基础设施包括

  1. 电本身
  2. 议价,统计用电量的电力公司
  3. 发电厂,电力线路,用电设施

X.509

证书的格式标准.

  1. 证书应该包含什么内容
  2. 证书使用什么扩展名
  3. 证书能够使用的加密/解密算法名单

证书的签名与验证

服务器证书的内容

  1. 证书版本号(Version)
  2. 证书序列号(Serial Number),当CA计划吊销该证书时,将该序列号加入到黑名单CRL中即可)
  3. 签名使用的算法(Signature algorithm),包含hash算法和非对称加密时的算法
  4. 证书的颁发机构(Issuer),国家,省市,地区,通用名,邮箱…
  5. 证书的有效期(Validity),开始日期,失效日期
  6. 证书的所有者(Subject),证书颁发给谁,国家,省市,地区,通用名,邮箱…
  7. 服务器的公钥(Subject Public key),值以外还有算法.
  8. 扩展信息
  9. 证书签名值
  10. 指纹

CA的签名过程

  1. 准备明文内容P(Issuer, Valid from/to, … Subject)
  2. 将明文hash后得到内容H
  3. 使用CA自己的私钥,利用指纹算法,对H进行RSA加密得到签名S(也叫指纹)
  4. P与S组合成一个文件,即网站的数字证书

验证方式

  1. 浏览器中已经安装证书颁发机构XXXCA的证书
  2. 从网站example.org得到一个证书后,发现颁发机构是XXXCA
  3. XXXCA的证书里有CA的公钥,用CA的公钥解密签名S,得到H1
  4. 自行对明文内容hash一次,得到H2
  5. 两个对比发现相同,则
    要么服务器证书没有篡改,信息可信
    要么有人偷了CA的私钥,用私钥伪造了假的服务器证书.

信任链体系

服务器证书的内容,用二级CA证书中的公钥验证.
二级CA证书的内容,要么用一级CA证书中的公钥验证.
一级CA证书的内容,使用明文中的公钥,能够解密指纹.这就是自签名.

一级CA证书也称根证书.通常会被浏览器默认信任.这就是大厂的地位.

openssl的特殊

理论上非对称加密的两个密钥,一个包含(n,e),一个包含(n,d),哪一个都可以作为公钥.
不过openssl为了一些工程上的考量,有一些明显的不同

  1. 私钥是私钥,包含(n, e, d, p, q, exponent1/2, coefficient,其中exponent1/2, coefficient等是用于加速运算的数据
    使用 openssl rsa -in xxx.key -text -noout 大概可以看到 xxx.key 文件中包含的部分内容.
    对应关系如下

    1
    2
    3
    4
    5
    6
    7
    8
    modulus         - n
    privateExponent - d
    publicExponent - e
    prime1 - p
    prime2 - q
    exponent1 - d mod (p-1)
    exponent2 - d mod (q-1)
    coefficient - (q^-1) mod p

    私钥和公钥的文本中也直接包含 BEGIN RSA PRIVATE KEY, BEGIN PUBLIC KEY 等字样

  2. 看起来一次只生成了"私钥"文件

    1
    openssl genrsa -out ca.key 2048
  3. 公钥可以使用"私钥"计算得出

    1
    openssl rsa -in ca.key -pubout > ca.pub
  4. 但文件的命名方式上却没有给出合理的默认值.甚至公钥和私钥可以用同样的扩展名

  5. 鉴于公钥可以计算得出,许多命令直接在最终产物中包含计算出的公钥,见不到显式计算公钥的命令
    TODO 证据?

颁发自签名证书的大致过程

CA端

  1. 生成一对密钥,私钥 ca.key, 公钥也隐含在这个文件中.

    1
    2
    3
    4
    openssl genrsa -out ca.key 2048
    # genrsa 生成rsa密钥
    # -out 用于指定生成文件名
    # 2048 算法允许的被加密内容的最大长度
  2. 自己使用快捷方式一步到位,申请得出同时直接签发csr证书文件

    1
    2
    3
    4
    5
    6
    openssl req -new -x509 -key ca.key -days 3650 -out ca.crt
    # req 生成签名请求
    # -new 新的请求
    # -x509 直接输出一个X509格式的证书
    # -days 证书的有效时间
    # -key 签名用的私钥
  3. 查看自签名证书的解析后数据

    1
    2
    openssl x509 -in ca.crt -noout -text
    # x509 证书操作,这里用来查看

服务器端

  1. 服务端S生成一对密钥,明面上认为只有私钥文件 example.org.key

    1
    openssl genrsa -out example.org.key 2048
  2. 服务器端S填写申请信息并最终生成申请文件 example.org.csr

    1
    2
    3
    4
    5
    6
    # key中包含的自己的公钥信息,因此用key文件来制作申请文件
    openssl req -new -key example.org.key -days 3650 -out example.org.csr
    # 注意common name一定要和自己的网站名写得相同

    # 查看一下申请时用的文件
    openssl req -in example.org.csr -noout -text
  3. 将文件提交给CA

  4. CA机构B,使用自己的私钥 ca.key 来对 a.pub 施加数字签名并生成一个证书 a.crt

  5. 客户端C,使用CA公开的证书 ca.crt 里的公钥(明文),验证服务端的证书 a.crt.

  6. 验证成功,则C信任 a.crt 中的公钥,并用该公钥加密消息以和S握手通信

再次CA端

  1. 使用自己的 ca.key 文件,对提交来的 example.org.csr 文件中的内容进行签名.

    1. 提取信息

    2. 使用自己的 ca.crt 帮助填写一部分信息

    3. hash

    4. 签名该hash

      1
      2
      3
      4
      5
      6
      7
      openssl x509 -req -in example.org.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out example.org.x1.crt
      # -req 表明输入文件是csr文件
      # -in 输入文件是xxx
      # -CA 用于指定签发该csr请求时所用的CA证书,或许是用来填写服务器证书中CA的国家地区等信息
      # -CAkey 用于签发请求时数组签名的CA私钥
      # -CAcreateserial 如果序列号文件不存在,则创建它.
      # CA本身需要一系列文件夹和文件结果,专门用于保存一些已经发行过的服务器证书的序列号.

    注意

    1. Common Name 一定要与自己的域名完全相同,不要带通配符

    2. openssl命令查看证书时通常 Common Name 简写作 CN

    3. firefox只检查 Common Name, 但Chromium/Chrome会额外验证 Subject Alternative Name.
      此时需要借助配置文件来签名.

      1
      openssl x509 -req -in example.org.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out example.org.crt -extfile example.extensions.cnf

      其中配置文件

      1
      2
      3
      4
      5
      6
      7
      basicConstraints=CA:FALSE
      subjectAltName=@my_subject_alt_names
      subjectKeyIdentifier = hash

      [ my_subject_alt_names ]
      DNS.1 = *.example.org
      DNS.2 = example.org
  2. 查看和验证是否成功

    1
    2
    3
    4
    5
    6
    # 正常查看一下
    openssl x509 -in example.org.x1.crt -noout -text

    # 验证是否成功
    openssl verify -CAfile ca.crt example.org.x1.crt
    # verify 验证动作

nginx配置

1
2
3
4
5
6
7
8
9
10
11
http {
ssl_certificate /opt/etc/nginx/vhost/example.org.bundle.crt;
ssl_certificate_key /opt/etc/nginx/vhost/example.org.key;

ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
}

server {
listen 443 ssl;
}
  1. 如果在server下配置ssl,则有可能在nginx语法检查时报错说还没有配置ssl,需要提前到http中
  2. example.org.bundle.crt靠 cat example.org.crt ca.crt > example.org.bundle.crt 形成
    在浏览器中表现则是,有bundle则证书有2个tab页可以查看(一个网站的,一个CA的),无bundle则只有1个tab(网站的)

浏览器添加证书

需要添加的是CA的证书 ca.crt

firfox

firefox有自己独有的数据库来保存所有可信任的CA证书.

设置->隐私与安全->查看证书->证书颁发机构->导入证书

chromium

在linux系统上,chromium和其他自带软件(比如用来校验发邮件的人)依靠的是名为 nssdb 的数据库.
该数据库由系统拥有,可以使用CLI来交互

1
2
3
4
5
6
# 查看目前系统中的证书列表
certutil -d sql:$HOME/.pki/nssdb -L
# 添加一个CA证书
certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n <nic name> -i /path/to/ca.crt
# 删除一个证书
certutil -d sql:$HOME/.pki/nssdb -D -n blog

当然,chromium等也可以使用GUI配置
设置->隐私设置和安全性->安全->管理证书->授权机构->导入

访问

  • 访问需要完整的URL,比如 https://example.org//
  • 如果还没有host可以在 /etc/hosts 中添加,也可以
    1. /etc/resolv.conf 中设置DNS服务器地址,包含路由器
    2. 更改路由器的 /etc/hosts 文件
    3. 使用 killall dnsmasq; dnsmasq 的方法重启DNS服务
    4. 使用 dig <hostname> 的方式验证DNS是否起效

参考

  1. 阮一峰的日志
  2. 关于CA的介绍
  3. 证书的生成和使用原理
  4. 自己生成网站证书
  5. 各种命令的参数含义
  6. 系统级添加CA证书
  7. openssl的特殊性
  8. 应付chrome更严格的检查