本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2021年02月06日 统计字数: 7094字 阅读时间: 15分钟阅读 本文链接: https://soulteary.com/2021/02/06/how-to-make-and-use-a-self-signed-certificate.html ----- # 如何制作和使用自签名证书 在计算机加密和安全领域中,我们会时常遇到:自签名安全证书。 因为自签名证书签发相对于商业证书流程简单,费用低廉(除了电费几乎不花钱),更新容易。所以在开发领域、甚至一些小众场景下特别常见,比如 K8S / MySQL 集群中的 TLS 认证,一些大的集团、公司的内网服务、网站安全证书、企业路由器设备的管理后台、用于管理企业员工的“安全准入客户端”等不乏使用这个方案。 本篇文章就来聊聊如何快速生成证书,以及如何安装部署到不同的环境中。 ## 写在前面 >(2022.10.22 更新) > 如果你只是需要快速生成证书,可以使用[《只有 3MB 的自签名证书制作 Docker 工具镜像:Certs Maker》](https://soulteary.com/2022/10/22/make-docker-tools-image-with-only-3md-self-signed-certificate-certs-maker.html)一文中提到的新版本的工具,如果你希望全面了解自签名证书,可以继续阅读。 经常有人说,使用自签名证书不安全,会导致中间人攻击。这里需要为自签名证书“正名”,如果你制作生成的证书被妥善保管(即不泄漏并被二次利用),并将其加入你的有限的设备(自用、团队使用)的证书信任列表中,在明确你的设备访问地址(不涉及DNS攻击),你是不会遇到中间人攻击的。 比如当你遇到类似下面的场景,不一定会遇到不安全的事情,有可能只是管理员忘记换掉过期证书、或者你自己生成证书后,使用了一台没有信任证书的设备进行访问、也可能是管理员压根没有想在公网签发证书,想做一个私有的网站: ![常见的网站因为证书问题而产生的警告页面](https://attachment.soulteary.com/2021/02/06/insecure-page-tips.png) 多数时候我们看到的不安全的证书是因为应用错误配置、有心人基于 DNS 地址攻击、证书过期造成、甚至是我们未曾正确配置证书信任白名单造成的。 一旦我们正确生成证书,在妥善保存证书后,进行了有限设备的白名单设置后,我们的证书和商业证书的使用是几乎没有差别的(除了无法使用 OCSP、EV 证书使用上存在一定额外工作外)。 ![信任之后,会看到浏览器提示“安全”](https://attachment.soulteary.com/2021/02/06/after-trust.png) 那么来聊聊如何快速生成证书。 ## 使用命令行脚本生成自签名证书 最常见和通用的做法便是安装配置一个带有 `openssl` 环境的系统,然后使用命令行执行类似下面这样的命令: ```bash openssl req -x509 -newkey rsa:2048 -keyout ssl/${fileName}.key -out ssl/${fileName}.crt -days 3600 -nodes ... ``` 这里如果你选择不使用配置文件的话,得参考 [OpenSSL](https://www.openssl.org/docs/manmaster/man1/req.html) 文档,附带一堆参数,或需要交互式的输入一堆选项,并祈祷在中间每一步没有输入出错,例如下面这样: ```bash enerating a RSA private key ....................................................................................................................................................................................................................................................................++++ .............................................................++++ writing new private key to 'example.key' ----- You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:XX Locality Name (eg, city) []:XXXX Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example, Inc Organizational Unit Name (eg, section) []:IT Common Name (e.g. server FQDN or YOUR name) []:example.com Email Address []:example@soulteary.com ``` 相比之下,使用类似下面的配置生成证书会稍微容易那么一些: ```bash #!/bin/sh OUTPUT_FILENAME="lab.com" printf "[req] prompt = no default_bits = 4096 default_md = sha256 encrypt_key = no string_mask = utf8only distinguished_name = cert_distinguished_name req_extensions = req_x509v3_extensions x509_extensions = req_x509v3_extensions [ cert_distinguished_name ] C = CN ST = BJ L = BJ O = HomeLab OU = HomeLab CN = lab.com [req_x509v3_extensions] basicConstraints = critical,CA:true subjectKeyIdentifier = hash keyUsage = critical,digitalSignature,keyCertSign,cRLSign #,keyEncipherment extendedKeyUsage = critical,serverAuth #, clientAuth subjectAltName = @alt_names [alt_names] DNS.1 = lab.com DNS.2 = *.lab.com DNS.3 = *.page.lab.com ">ssl/${OUTPUT_FILENAME}.conf openssl req -x509 -newkey rsa:2048 -keyout ssl/$OUTPUT_FILENAME.key -out ssl/$OUTPUT_FILENAME.crt -days 3600 -nodes -config ssl/${OUTPUT_FILENAME}.conf ``` 类似的脚本,我曾在 Traefik 示例脚本中提到过: [https://github.com/soulteary/traefik-example/blob/main/scripts/generate-certs.sh](https://github.com/soulteary/traefik-example/blob/main/scripts/generate-certs.sh) 。 还有更简单的方案吗?尤其是在有不断修改 DNS、希望使用脚本自动化签订证书的场景下? ## 快速生成证书 为此我写了一个脚本,并使用容器进行封装,以达到可以使用极其简的命令行来生成证书的目的,并借助容器简化掉了本地需要安装 OpenSSL 依赖的问题,“开箱即用”。相关代码我已经开源,项目地址:[https://github.com/soulteary/certs-maker](https://github.com/soulteary/certs-maker) 比如你想生成一个稍微复杂一些的站点证书,只需要执行下面这行命令就足够了: ```bash docker run --rm -it -e CERT_DNS="domain.com;*.domain.com;*.a.domain.com" -v `pwd`/certs:/ssl soulteary/certs-maker ``` 执行完毕你将会看到类似下面的日志: ```bash User Input: { CERT_DNS: 'domain.com;*.domain.com;*.a.domain.com' } Generating a RSA private key ................................................................................................................................................+++++ .........................................................+++++ writing new private key to 'ssl/domain.com.key' ----- ``` 以及能够在 `ssl` 目录中看到我们生成的证书文件。 至于其他的使用方式,比如生成包含多个域名的混合证书、生成单个证书,只需要调整 `CERT_DNS` 参数的值即可。如果想进一步定制前文提到的证书细节,比如证书签发国家、省份等信息,可以参考开源项目仓库的使用方式,添加其他的参数,这里就不过多赘述了。 ### 使用 docker-compose 生成 如果你希望将命令保存下来,作为代码存储在仓库里,也可以考虑编写一个 compose 文件: ```yaml version: '2' services: certs-maker: image: soulteary/certs-maker environment: - CERT_DNS=a.com;b.com;c.com;*.d.com; volumes: - ./certs:/ssl ``` 将上面的内容保存为 `docker-compose.yml`,然后执行 `docker-compose up` ,你会在 `certs` 目录看到生成的证书文件。 ## 使用证书 生成证书之后,来聊聊如何使用证书。 ### 在各种系统上导入证书 导入证书可以参考下面的文档,过程都很简单,引导证书,然后重启需要使用证书的应用即可。 - Apple 文档:[在 iOS 和 iPadOS 中信任手动安装的证书描述文件](https://support.apple.com/zh-cn/HT204477) - VMware 文档:[在 Windows 主机上导入内部根 CA 证书](https://docs.vmware.com/cn/Horizon-FLEX/1.12/com.vmware.horizon.flex.admin.doc/GUID-AEB47CE4-FECB-49B2-ABA0-AD4BE6D9267A.html) - SSL 文档:[ 生成证书签名请求(CSR)在macOS钥匙串访问中 ](https://www.ssl.com/zh-CN/%E5%A6%82%E4%BD%95/macos%E9%92%A5%E5%8C%99%E4%B8%B2%E8%AE%BF%E9%97%AE%E4%B8%AD%E7%9A%84csr%E7%94%9F%E6%88%90/) - 群晖文档:[使用自我签署证书](https://www.synology.cn/zh-cn/knowledgebase/DSM/help/DSM/AdminCenter/connection_certificate) ### 在 Java 应用中信任自签名证书 如果你使用的是 Java 应用访问自签名的网站,应用访问过程会出现因为证书错误而拒绝连接的错误。 解决这个问题并不复杂,只需要额外做一点点工作,将证书添加到 `keystore` 中,重启 Java 应用即可: ```bash sudo keytool -import -alias charles -file /Path-To-Certs/key.crt -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit ``` 这个操作对于证书过期的情况,也同样有效,早先有一篇文章有描述:[《使用 Docker 和 Traefik v2 搭建 Confluence 7.3 》](https://soulteary.com/2020/03/06/confluence-7-with-docker.html)。 ### 在 Debian / Ubuntu / Alpine 系统中信任证书 对于 Debian / Ubuntu 系统,信任证书相当简单,只需要将证书拷贝到“待安装目录”,然后执行证书更新命令即可: ```bash cp *.crt /usr/local/share/ca-certificates/ update-ca-certificates ``` Alpine 也是一样,考虑我们经常在容器场景中使用它,所以这里直接给出一个完整的Dockerfile 示例: ```bash FROM alpine RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/* ADD ./ssl/*.crt /usr/local/share/ca-certificates/ RUN update-ca-certificates --fresh ``` ## 搭建配合安装证书使用的 Web 服务 上文中如果想在客户端(尤其是手机)上安装证书,一定会遇到跨系统传输文件的问题。为了方便分发和安装,这里可以使用 Nginx 启动一个用于分享证书安装的 Web 服务。 ![使用更“直观”的方式获取证书](https://attachment.soulteary.com/2021/02/06/get-your-certs.png) ### 搭建通用服务 我们可以使用 Nginx 的 **ngx\_http\_sub\_module** 和 **ngx\_http\_autoindex\_module** 模块构建一个能够自动列举证书目录的服务: ```bash server { listen 80; server_name localhost; location = /favicon.ico { empty_gif; } location / { root /public; autoindex on; sub_filter '