一文搞懂自签名tls证书

最近工作上遇到了一些关于tls证书相关的问题,网上教程很多但是大部分都只是说怎么做,并且大部分教程互相也对不上,各种查资料研究了两三天,写一个相对清晰,且解释详细的文档来帮助大家避坑哈!

前置知识

名词解释

  • ca:Certification Authority,翻译为证书机构,负责给各个网站颁发证书。(每个电脑都会内置13个根证书机构的证书,并无条件相信这13个证书机构)
  • csr:Certificate Signing Request,即证书签名请求。用户向ca申请证书的时候,需要提交的信息。一般会包含网站域名、用户邮箱、所在国家地区、组织名称等等信息
  • 申请证书,或者签发证书,即ca首先使用特定的摘要算法计算出csr的摘要,再用自己的私钥给摘要进行加密后得到的签名,最后将以上所有信息打包为一个特定格式的文件(也就是证书)
  • cert:Certificate,即证书。一个特定格式的文件,里面包含了csr中部分信息、摘要生成算法、tls拓展支持、ca给csr的指纹信息(也翻译为签名)等。
  • key:一般指openssl生成的密钥文件,其中包含了公钥和私钥
  • 验证证书合法:指客户端首先从cert中读取该证书的签发ca(这里会涉及到证书链的问题,暂时可简单理解为,所有证书都是根证书机构签发),然后使用该ca的公钥去解密证书的签名(非对称加密,私钥加密,公钥解密),获得摘要信息A;然后使用证书中指定的摘要算法计算证书的摘要B;第三步判摘要A是否和摘要B相等
  • tls单向认证:一般指客户端会验证服务端所提供的证书是否合法(访问https的网页就是用的这个,用的很广泛)
  • tls双向认证:在单项认证基础上,服务端也会验证客户端的证书是否合法(相对少见,目前只有docker daemon开启tls后才会使用)

tls单向认证大致流程

和证书相关的只有图中的第2、3步。

在普通的https页面访问中(也就是tls单项认证中)如何实现自签名证书

自签名证书都需要什么文件

从前置知识中可以知道服务端需要提供证书,客户端需要验证证书。因此服务端需要有证书文件,又因为申请证书需要csr以及公钥、私钥,因此服务端至少需要3个文件也就是cert、csr、key。那么客户端呢?客户端至少需要ca的公钥,其实ca的公钥一般存放在ca的cert中,因此客户端需要ca的cert,也就是一个ca的证书文件。

tls单项认证自签名证书小结

tls单项认证所需要文件如下

ca相关:

  • key.pem (包含ca的私钥,切记不可暴露)
  • cert.pem (ca的证书,需要给到客户端)

服务端相关的:

  • key.pem (包含服务端的私钥,不可外露,和ca的key.pem不是同一个)
  • cert.pem (服务端证书)
  • csr.pem (签名证书的中间文件可以删除)
  • openssl.cnf (签名证书时的额外配置)

客户端相关的:

  • ca.pem(和ca的cert.pem是同一个)

talk is cheap,show me the code

  1. 生成ca相关文件
bash 复制代码
mkdir ca
#4096 为私钥比特数,一般取2048或4096即可
#生成key文件
openssl genrsa -out "ca/key.pem" 4096

#生成ca的证书文件cert.pem
# subj '/CN=docker:dind CA' 为证书机构的名称,-days "1800 为证书有限期,可适当调整
# -x509为密码体系标准(不太准确)记住即可
openssl req -new -key "ca/key.pem" \
  -out "ca/cert.pem" \
  -subj '/CN=docker:dind CA' -x509 -days "1800"
  1. 生成服务端文件
bash 复制代码
mkdir server
# 生成key文件
openssl genrsa -out "server/key.pem" 4096

#生成csr文件
# subj '/CN=docker:dind server' 里面填写的是你的组织、组织单位、公用名的信息。
# 至少需要CN这个字端,其他可以选填,其他字端请自行百度
openssl req -new -key "server/key.pem" \
  -out "server/csr.pem" \
  -subj '/CN=docker:dind server'
# 申请证书时额外的配置
# subjectAltName = DNS:ubuntu-linux,IP:10.211.55.5,这里要把dns后面的改为你的域名,ip后面改你你服务所在的ip(如果只用域名访问,ip可以不要)
# subjectAltName 也是X509的一个拓展,指的是san(多域名)类型的证书,其中DNS:ubuntu-linux可以多次重复,下面给出了bing证书的例子,可以看到,一个证书就支持了非常多的域名。
cat > "server/openssl.cnf" <<-EOF
  [ x509_exts ]
  subjectAltName = DNS:ubuntu-linux,IP:10.211.55.5
EOF

# 模拟ca签发服务端证书
openssl x509 -req \
   -in "server/csr.pem" \
   -CA "ca/cert.pem" \
   -CAkey "ca/key.pem" \
   -CAcreateserial \
   -out "server/cert.pem" \
   -days "1800" \
   -extfile "server/openssl.cnf" \
   -extensions x509_exts
# 查看证书内容
openssl x509 -in server/cert.pem -noout -text
# 验证证书
# 使用ca证书验证你的证书,
openssl verify -CAfile "ca/cert.pem" "server/cert.pem"
  1. nginx演示
nginx 复制代码
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    server {
        listen 443 ssl;
        server_name ubuntu-linux
        # SSL 协议版本
        ssl_protocols TLSv1.2;
        # 服务端证书目录
        ssl_certificate /root/ca/server/cert.pem;
        # 服务端私钥目录
        ssl_certificate_key /root/ca/server/key.pem;
        # ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_ciphers AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256;

        # 与False Start没关系,默认此项开启,此处减少抓包的干扰而关闭
        ssl_session_tickets off;
        default_type text/html;
        return 200 "https ok \n";
    }

    include /etc/nginx/conf.d/*.conf;
}

使用docker启动进行演示

docker run --rm -v $PWD/nginx.conf:/etc/nginx/nginx.conf -p 9999:443 -v /root/ca:/root/ca nginx

此时访问浏览器会报如下错误:

出现的原因是我们的ca不是权威的ca,所以浏览器不信任这个ca 4. 给客户端添加额外的ca证书,将刚才生成的ca的cert.pem拷贝到客户端电脑上,每个人命令不一样,我的命令为scp ubuntu-linux:/root/ca/ca/cert.pem .

mac上打开钥匙串访问,操作如下(windows请自行百度):

导入后,双击证书,并信任

此时重新刷新浏览器页面发现已经没有报错了,查看证书的信息也都正常,可查看一下颁发给、颁发者的信息,和前面填写的信息都能对得上

选看内容

创建双向认证证书

所谓双向认证就是服务端也会向客户端索要证书,因此只需要在单项认证的基础上,再生成一套客户端证书即可

生成客户端证书

bash 复制代码
mkdir client
# 生成key文件
openssl genrsa -out "client/key.pem" 4096

#生成csr文件
openssl req -new -key "client/key.pem" \
  -out "client/csr.pem" \
  -subj '/CN=docker:dind client'
# 申请证书时额外的配置
# client和server这里不一样
# extendedKeyUsage = clientAuth 这是x509的一个扩展用来支持客户端认证的
cat > "client/openssl.cnf" <<-'EOF'
  [ x509_exts ]
  extendedKeyUsage = clientAuth
EOF

# 模拟ca签发服务端证书
openssl x509 -req \
   -in "client/csr.pem" \
   -CA "ca/cert.pem" \
   -CAkey "ca/key.pem" \
   -CAcreateserial \
   -out "client/cert.pem" \
   -days "1800" \
   -extfile "client/openssl.cnf" \
   -extensions x509_exts
# 查看证书内容
openssl x509 -in client/cert.pem -noout -text
# 验证证书
# 使用ca证书验证你的证书,
openssl verify -CAfile "ca/cert.pem" "client/cert.pem"

最终目录结构如下:

nginx配置并验证

http 复制代码
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;
    server {
        listen 443 ssl;
        server_name ubuntu-linux
        # SSL 协议版本
        ssl_protocols TLSv1.2;
        # 服务端证书目录
        ssl_certificate /root/ca/server/cert.pem;
        # 服务端私钥目录
        ssl_certificate_key /root/ca/server/key.pem;
        # ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
        ssl_ciphers AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256;
        #客户端ca的证书
        ssl_client_certificate /root/ca/ca/cert.pem;
        ssl_verify_client on; #开启客户端证书验证

        # 与False Start没关系,默认此项开启,此处减少抓包的干扰而关闭
        ssl_session_tickets off;
        default_type text/html;
        return 200 "https ok \n";
    }

    include /etc/nginx/conf.d/*.conf;
}

启动服务端nginx docker run --rm -v $PWD/nginx.conf:/etc/nginx/nginx.conf -p 9999:443 -v /root/ca:/root/ca nginx

客户端使用curl验证

--cert client/cert.pem --key client/key.pem指定了客户端的证书和key文文件,-k参数不校验服务端的证书(因为自签名的证书,curl也不认)

curl --cert client/cert.pem --key client/key.pem https://ubuntu-linux:9999 -v -k 验证结果如图,可以看到返回了200,并且客户端也发送了证书给服务端TLSv1.3 (OUT), TLS handshake, Certificate (11):

docker daemon开启tls访问

docker daemon tls模式默认就是使用tls双向认证来进行安全通信的,只需要按双向认证步骤生成相应的证书文件即可,下面是docker官方生成双向证书的脚本。具体如何配置,可自行百度,这里不再赘述

bash 复制代码
#!/bin/sh
set -eu

_tls_ensure_private() {
  local f="$1"; shift
  [ -s "$f" ] || openssl genrsa -out "$f" 4096
}
_tls_san() {
  {
   ip -oneline address | awk '{ gsub(//.+$/, "", $4); print "IP:" $4 }'
   {
    cat /etc/hostname
    echo 'docker'
    echo 'localhost'
    echo 'ubuntu-linux'
    hostname -f
    hostname -s
   } | sed 's/^/DNS:/'
   [ -z "${DOCKER_TLS_SAN:-}" ] || echo "$DOCKER_TLS_SAN"
  } | sort -u | xargs printf '%s,' | sed "s/,$//"
}
_tls_generate_certs() {
  local dir="$1"; shift

  # if server/{ca,key,cert}.pem && !ca/key.pem, do NOTHING except verify (user likely managing CA themselves)
  # if ca/key.pem || !ca/cert.pem, generate CA public if necessary
  # if ca/key.pem, generate server public
  # if ca/key.pem, generate client public
  # (regenerating public certs every startup to account for SAN/IP changes and/or expiration)

  if [ -s "$dir/server/ca.pem" ] && [ -s "$dir/server/cert.pem" ] && [ -s "$dir/server/key.pem" ] && [ ! -s "$dir/ca/key.pem" ]; then
   openssl verify -CAfile "$dir/server/ca.pem" "$dir/server/cert.pem"
   return 0
  fi

  # https://github.com/FiloSottile/mkcert/issues/174
  local certValidDays='825'

  if [ -s "$dir/ca/key.pem" ] || [ ! -s "$dir/ca/cert.pem" ]; then
   # if we either have a CA private key or do *not* have a CA public key, then we should create/manage the CA
   mkdir -p "$dir/ca"
   _tls_ensure_private "$dir/ca/key.pem"
   openssl req -new -key "$dir/ca/key.pem" \
    -out "$dir/ca/cert.pem" \
    -subj '/CN=docker:dind CA' -x509 -days "$certValidDays"
  fi

  if [ -s "$dir/ca/key.pem" ]; then
   # if we have a CA private key, we should create/manage a server key
   mkdir -p "$dir/server"
   _tls_ensure_private "$dir/server/key.pem"
   openssl req -new -key "$dir/server/key.pem" \
    -out "$dir/server/csr.pem" \
    -subj '/CN=docker:dind server'
   cat > "$dir/server/openssl.cnf" <<-EOF
    [ x509_exts ]
    subjectAltName = $(_tls_san)
   EOF
   openssl x509 -req \
     -in "$dir/server/csr.pem" \
     -CA "$dir/ca/cert.pem" \
     -CAkey "$dir/ca/key.pem" \
     -CAcreateserial \
     -out "$dir/server/cert.pem" \
     -days "$certValidDays" \
     -extfile "$dir/server/openssl.cnf" \
     -extensions x509_exts
   cp "$dir/ca/cert.pem" "$dir/server/ca.pem"
   openssl verify -CAfile "$dir/server/ca.pem" "$dir/server/cert.pem"
  fi

  if [ -s "$dir/ca/key.pem" ]; then
   # if we have a CA private key, we should create/manage a client key
   mkdir -p "$dir/client"
   _tls_ensure_private "$dir/client/key.pem"
   chmod 0644 "$dir/client/key.pem" # openssl defaults to 0600 for the private key, but this one needs to be shared with arbitrary client contexts
   openssl req -new \
     -key "$dir/client/key.pem" \
     -out "$dir/client/csr.pem" \
     -subj '/CN=docker:dind client'
   cat > "$dir/client/openssl.cnf" <<-'EOF'
    [ x509_exts ]
    extendedKeyUsage = clientAuth
   EOF
   openssl x509 -req \
     -in "$dir/client/csr.pem" \
     -CA "$dir/ca/cert.pem" \
     -CAkey "$dir/ca/key.pem" \
     -CAcreateserial \
     -out "$dir/client/cert.pem" \
     -days "$certValidDays" \
     -extfile "$dir/client/openssl.cnf" \
     -extensions x509_exts
   cp "$dir/ca/cert.pem" "$dir/client/ca.pem"
   openssl verify -CAfile "$dir/client/ca.pem" "$dir/client/cert.pem"
  fi
}

_tls_generate_certs "$(pwd)"

总结

自签名tls证书一共分为三步

  1. 生成ca相关文件:
  • key.pem (包含ca的私钥,切记不可暴露)
  • cert.pem (ca的证书,需要给到客户端)
  1. 生成服务端相关文件:
  • key.pem (包含服务端的私钥,不可外露,和ca的key.pem不是同一个)
  • cert.pem (服务端证书)
  • csr.pem (签名证书的中间文件可以删除)
  • openssl.cnf (签名证书时的额外配置)
  1. 配置客户端使其信任自签名的ca证书
相关推荐
我是谁??24 分钟前
ubuntu22.04 通过docker部署vLLM(Qwen3-0.6B)大模型+New API+OpenWebUI
docker·容器·vllm
运维瓦工1 小时前
DevOps 生态介绍(十):Docker Compose 核心 YAML 配置详解与常用命令大全
spring cloud·docker·容器
云烟成雨TD1 小时前
Spring AI 1.x 系列【59】容器化开发支持:Docker Compose 与 Testcontainers
人工智能·spring·docker
Plastic garden1 小时前
K8s(10)NFS 的动态 PV 创建数据库给k8s的mysql和redis
docker·容器·kubernetes
与海boy2 小时前
docker compose minio
docker·容器·eureka
吠品2 小时前
一次 Nginx 报错 unexpected end of file 的排查记录
网络协议·https·ssl
JimCarter2 小时前
使用Azure Devops Pipeline将Docker应用部署到你的Raspberry Pi上
docker·azure·树莓派·devops·orangepi·香橙派·raspberrypi
武子康2 小时前
调查研究-167 Docker Compose 详解:从单容器到多服务编排的工程化入口
运维·docker·云原生·容器·kubernetes·k8s·docker-compose
旅僧3 小时前
Ubantu docker环境配置(前置)
运维·docker·容器