一文搞懂自签名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证书
相关推荐
小小的木头人4 小时前
Docker vs. containerd 深度剖析容器运行时
运维·docker·容器
weixin_443290695 小时前
【Docker】安装及使用
docker·容器·eureka
it技术分享just_free5 小时前
基于 K8S kubernetes 的常见日志收集方案
linux·运维·docker·云原生·容器·kubernetes·k8s
aidroid6 小时前
git github仓库管理
linux·运维·docker
三朝看客6 小时前
k8s自动清理pod脚本分享
linux·docker
it技术分享just_free6 小时前
基于 K8S kubernetes 搭建 安装 EFK日志收集平台
运维·docker·云原生·容器·kubernetes·k8s
lj9077226446 小时前
Dockerfile部署xxljob
java·docker
lizhou8287 小时前
win10下使用docker、k8s部署java应用
java·docker·kubernetes
wydydgh9 小时前
docker 升级步骤
运维·docker·容器
小小工匠9 小时前
加密与安全_HTTPS TLS 1.2 连接(RSA 握手)的整个过程解读
安全·https·tls