Ubuntu上默认证书库是怎么回事
Tags: openssl, certificate, chrome, certutil
Published: 2024-06-01 10:58:06

## 背景
最近整`HTTP3`客户端时候，需要验证服务端证书，中间出了点小插曲，老是报错见上篇[文章](https://dev.to/shouhua_57/http3zhi-quictlsbao-cuo-unable-to-get-local-issuer-certificate-14ic)，问题解决了但是想到另外个问题，浏览器和客户端都是怎么验证证书的，我们都知道服务端给出证书，客户端根据本地的或者用户提供的根证书校验，但是系统根证书在哪儿，为什么我没有设置，他们就自动找到了？本文旨在根据这个问题，梳理Ubuntu系统自带证书库和浏览器的证书库。本文主要聚焦Ubuntu系统，但是其他系统的流程大致相似。

## 系统证书库
Ubuntu系统的证书目录位于`/etc/ssl/certs`, 根证书是`/etc/ssl/certs/ca-certificates.crt`。 其他平台的默认证书可以查看[golang源码文件](https://go.dev/src/crypto/x509/root_linux.go)。

查看`/etc/ssl/certs`发现里面有很多`pem`格式证书，这里就是系统根证书目录，我的机器上面有部分软链接到`/usr/share/ca-certificates/mozilla`（这个是系统读取`/etc/ca-certificates.conf`配置加入的）。文件`/etc/ssl/certs/ca-certificates.crt`是这些证书的合集，可以使用如下命令查看所有证书的`subject`
```bash
awk -v cmd='openssl x509 -noout -subject' '/BEGIN/ {close(cmd)}; {print | cmd}' < /etc/ssl/certs/ca-certificates.crt
```

### 系统证书更新
系统不建议我们自己手动证书到这个目录和文件中，那我们如何添加证书到系统呢？系统提供了命令`update-ca-certificates`
```bash
man 8 update-ca-certificates
```
根据manpage介绍，`update-ca-certificates`更新`etc/ssl/certs`和`ca-certificates.crt`。我们将`pem`格式证书以`.crt`后缀放到`/usr/local/share/ca-certificates`，一个证书一个文件，然后执行命令就会自动添加到根目录证书中。

### 寻找证书
刚才不建议咱们自己添加证书，因为其中有些步骤是看不见的，比如[c_rehash](https://www.openssl.org/docs/manmaster/man1/openssl-rehash.html)过程。寻找证书通过证书的`subject`，如果通过打开文件找到`subject`比对，那这样效率太低了，所以通过将证书的`subject`的hash值作为文件名，就可以提高寻找效率了，`rehash`就是干这个事。比如，我的系统中有个`Go_Daddy_Class_2_CA.pem`证书
```bash
ll /etc/ssl/certs | grep 'Go_Daddy_Class_2_CA'
# f081611a.0 -> Go_Daddy_Class_2_CA.pem 这个是rehash生成文件
# Go_Daddy_Class_2_CA.pem -> /usr/share/ca-certificates/mozilla/Go_Daddy_Class_2_CA.crt
```
可以通过以下命令看下hash值是否正确(当然是正确的)
```bash
openssl x509 -hash -fingerprint -noout -in $(readlink -f /etc/ssl/certs/Go_Daddy_Class_2_CA.pem)
# f081611a
# SHA1 Fingerprint=27:96:BA:E6:3F:18:01:E2:77:26:1B:A0:D7:77:70:02:8F:20:EE:E4
```
最后的`.0`是为了防止hash碰撞产生一样的值。

## OpenSSL
OpenSSL默认情况使用系统证书库
```bash
openssl version -d # 打印OpenSSL目录
ls -l $(openssl version -d | tr -d '"' | awk '{print $2}') # 查看OpenSSL目录内容
```
通过查看OpenSSL目录，里面的`certs`文件夹默认是软链接到`/etc/ssl/certs`。因此OpenSSL编写代码使用默认证书库就是系统证书库
```c
SSL_CTX_set_default_verify_paths(ssl_ctx);
```

## 浏览器
浏览器一般使用图形界面设置，但是我就觉得纳闷了，我在浏览器中设置了，但是根目录证书中没有，猜想肯定使用自己证书库，查找后确定是这样的。  
`Firefox`和`Chrome`都使用`sqlite`存储用户导入证书，具体放在文件`cert9.db`中，早期放在`cert8.db`，这里咱们不溯源历史，有兴趣可以查看相关[文档](https://wiki.mozilla.org/NSS_Shared_DB)。同样，这个数据库不建议直接操作，浏览器都使用了[NSS_Shared_DB](https://wiki.mozilla.org/NSS_Shared_DB)管理数据库，提供了命令`certutil`操作，Ubuntu需要安装`libnss3-tools`
```bash
sudo apt install libnss3-tools
```
[Chromium源码文档](https://chromium.googlesource.com/chromium/src/+/master/docs/linux/cert_management.md#add-a-certificate)中有提供如何操作。

### 查找浏览器证书库地址
命令操作没啥问题，但是关键是制定证书的数据库地址。Ubuntu一般使用`snap`(尽管不想使用)按照`Chrome`，会放在snap目录`$HOME/snap/[chromium|firefox]`，如果没有snap目录，可以查看个人目录隐藏文件夹`$HOME/.pki/nssdb`，可能会不一样，可以通过查找方式查询：
```bash
find $HOME -type f -name 'cert9.db'
```
注意浏览器都是以用户(profile)形式生成`cert9.db`的，Chromium系列明显亲民点，可以使用比如`$HOME/snap/chromium/current`软链接到当前用户目录。

## JKS(Java Key Store)
在前面查看`/etc/ssl/certs`目录时，看到有个文件夹`java`，翻看资料后发现，`JAVA`平台可以使用自己的证书库，支持多种存储格式，JKS的store位于`$JAVA_HOME/lib/security/cacerts`或者`$JAVA_HOME/jre/lib/security/cacerts`，JKS使用JDK提供的工具`keytool`管理。JKS的store默认密码是`changeit`。
```bash
# find JAVA_HOME
export JAVA_HOME=readlink -f $(which java) | sed 's/\/bin\/java//'

# List
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | head -5
# OR
keytool -list -cacerts -storepass changeit | head -5

# Add
keytook -import -alias testCert -keystore $JAVA_HOME/lib/security/cacerts -file ca_cert.crt
```


## 有用知识
工具[mkcert](https://github.com/FiloSottile/mkcert)是cert操作相关的库，里面有上面的相关信息，有兴趣可以仔细查看阅读。