最近工作上遇到了一些关于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
- 生成ca相关文件
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"
- 生成服务端文件
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"
- 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请自行百度):
导入后,双击证书,并信赖
此刻从头刷新浏览器页面发现现已没有报错了,检查证书的信息也都正常,可检查一下颁布给、颁布者的信息,和前面填写的信息都能对得上
选看内容
创立双向认证证书
所谓双向认证便是服务端也会向客户端索要证书,因而只需求在单项认证的基础上,再生成一套客户端证书即可
生成客户端证书
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装备并验证
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官方生成双向证书的脚本。具体怎么装备,可自行百度,这儿不再赘述
#!/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证书总共分为三步
- 生成ca相关文件:
- key.pem (包括ca的私钥,牢记不行露出)
- cert.pem (ca的证书,需求给到客户端)
- 生成服务端相关文件:
- key.pem (包括服务端的私钥,不行外露,和ca的key.pem不是同一个)
- cert.pem (服务端证书)
- csr.pem (签名证书的中间文件能够删去)
- openssl.cnf (签名证书时的额定装备)
- 装备客户端使其信赖自签名的ca证书