
由于不同软件要求不一样目前常见的自签证书有三种方式。由于不同方法的特殊性要重点强调证书的附加签名部分例如/CCN/STbeijing/Lhaidian/OdevA/OUdevB/CNdevC其中C是国家代码、ST是省或州、L是城市或地区、O是组织类信息、OU是部门类信息、CN是具体的一个主体比如人这些附加消息中CN 不能瞎写它的含义是当前证书的持有主体名称或者通俗的讲就是服务端出示证书时客户端会查看证书中附加域名列表 hosts 内是否有服务端的域名或 IP 如果有则验证通过此时服务端在客户端的认证中名字就是 CN 配置的值。在双向认证的体系下CN 的这种归一化不同域名但持有同一证书的服务端为同一名字的作用也被用来作为构建用户识别的依据 如果各位读者会 K8s就应该明白这一点第一种Java 专用 JKSJKS 是 Java 自身的内置专用格式使用时通常是成对出现一个是密钥库用在服务端对客户端出示证书一个是受信库内部是密钥库的公钥部分用在客户端校验服务端。要特别说明本质上受信库就是密钥库只不过里面只存放了一条公钥数据而已凡是使用这种证书机制的应用特点很明显都是只对自身的 TLS 加密和身份认证不涉及复杂的 CA 链路校验且涉及到集群时必然内置且通常默认服务通信只验证受信公钥是否正确这种方式的好处是不用再困扰如何在每个节点生成 JKS 文件的同时还要处理大量的受信公钥直接一份文件共用即可而且后期换证书文件的成本也小比如 Kafka 、ES 就是这样不过 ES 用的不是 JKS只不过它也默认使用这种认证校验方式但这种方式坏处在于不检查 CN/SAN 可能会应发一些使用上的不便和问题不过多数架构应用已经直接支持了更标准的通用格式比如 Java 的 Springboot 在 2.7 版本开始就不再需要从 jks 转 p12 而是可以直接使用通用标准格式下 CA 签发的 pem 文本格式证书使用时 JKS 生成方式如下# 在需要证书的服务端节点上运行命令CN 要写为当前节点的域名jks下它同时负责 hosts 功能# -keystore 结果路径 -alias 密钥库中默认有一对密钥对设置它的别名 -validity 密钥对有效期默认90天 -genkey 生成新的密钥对# -keyalg RSA 使用的算法 -keysize 密钥长度 -storepass 密钥库的密码 -keypass 密钥对中私钥的密码# -dname 部分除了 CN 要写当前节点的 DNS外其他的自定义keytool-keystorekafka-keystore.jks-aliaslocalhost-validity365-genkey-keyalgRSA-keysize2048-dnameCNlocalhost, OUdev,Odev,Ldev,STdev,CCN-storepass123456-keypass123456# 导出密钥对的公钥部分保存在broker.cer文件中keytool-export-aliaslocalhost-keystorekafka-keystore.jks-filebroker.cer-storepass123456# 用一个导入动作把公钥回写到一个会自动创建的密钥库中就成了受信库后期使用中 broker.cer 这个文件就没用了keytool-import-aliaslocalhost-keystoreclient.truststore.jks-filebroker.cer-storepass123456-noprompt要重点注意千万不要把 JKS 密钥库中生成的公钥当成 CA 根证书去给别人签发JKS 密钥库它从名字上也不难理解可以放多个密钥对只不过在给某个服务使用的场景下密钥库中只有一对密钥且我们说的 CA 证书本质就是 CA 密钥对中的公钥但如果你把 JKS 里的公钥当根证书会发现 OpenSSL 也能正常解析它的 X.509 格式可它本质上只是一张“自签名的叶子证书”没有 CA 的签名能力。它的正确用法就只是直接用于 Kafka、Presto 这类服务自身的 TLS 加密和身份认证对于密钥库来讲Java 提供了转换为 p12 二进制格式 的方法但现在很少这样用了需要 p12 时通常要的都是 X.509 标准CA签发的证书而不是JKS。p12 本质上是一种二进制打包格式它里面有完整的证书关系链# 将 JKS 格式的密钥库转换成 p12 格式keytool-importkeystore-srckeystorekafka-keystore.jks-destkeystorekafka-keystore.p12-deststoretypepkcs12除了上面提到的只校验受信公钥是否正确外还有些任然使用 JKS 格式的应用它们会强制要求走标准 CA 签名那一套逻辑比如 Hadoop 配置 DataNode 加密传输时用的就是这种方式遇到这种情况如果你可以用自签 CA 的方式操作方式如下# 用一个节点做ca生成一个自签的X.509 CA根证书(cert)和根证书的私钥(key)运行后要求输入ca私钥的密码按需设置这里设置123456。# 根证书本质是个单独存放的公钥这点和 JKS 受信库类似相当于这个CA机构的营业执照副本私钥部分单独存放相当于CA机构的公章# 单独存在的私钥本质上是一个很长的字符串用来验证证书的真伪以及作为给别人签名的核心工具# -keyout 根证书的私钥存放路径 -out 根证书存放路径# -days 当前ca根证书有效天数它会影响所签证书请求的有效期超过ca有效期时实际有效期终止到ca根证书过期的时间。但ca根证书切换成本很大因此一般都是3-10年。而私钥本身永不会过期# -newkey 是密钥的算法类型和密钥长度可以不指定默认是 rsa 2048位# -subj 这个是根证书签名中的附加信息就是这个证书CA机构在那里、那个城市、ca自身的名称等等这些自定义信息openssl req-new-x509-keyout/root/hdfs_ca_key-out/root/hdfs_ca_cert-days3650-newkeyrsa:2048-subj/CCN/STbeijing/Lhaidian/OdevA/OUdevB/CNdevC# 随后把根证书和私钥同步给其他节点。# 注意由于现在是生成自签证书发布到各节点为了使用方便正常的CA机构是你把要签名的文件(密钥库请求文件)发给人家给你签名后返回属于你的证书和CA根证书# 相当于你找CA机构签合同CA会返回给你两个东西一个是CA根证书相当于营业执照复印了一份给你另一个就是来源于你的签名请求但已经盖好公章的合同scp/root/hdfs_ca_key /root/hdfs_ca_cert node2:/root/随后每个所需节点用拿到的CA根证书做两件事一是生成受信库二是生成包含完整证书链路的密钥库。注意前面说了图方便下面的操作在各节点本地做正常是CA机构做使用者等结果就行# 把根证书导入到一个会自动生成的密钥库中。第一件事情就完成了# keytool 命令是JavaJDK提供的工具 -keystore 指定结果密钥库路径如果存在则向其中追加导入的内容不存在则会先生成一个密钥库文件# -alias 别名导入操作按需自定义即可 -import 是导入用的动作参数 -file 是指定导入的内容这里指定ca证书 -storepass 生成的密钥库它的密码导入操作不需要被导入密钥库的任何密码只要实际发生使用的时候才需要# -trustcacerts 标记为ca证书 -noprompt 告诉程序使用非交互方式执行并默认受信导入的内容keytool-keystore/root/truststore-aliasca-import-file/root/hdfs_ca_cert-storepass123456-trustcacerts-noprompt# 下面做第二件事每个需要证书的节点生成一个自己的密钥库# -alias 是生成的密钥它的别名 -keyalg 是采用的算法 -genkey是生成新的密钥对# -dname这部分是密钥的签名和上面生成根证书一样是附加信息但是CN一定要是当前执行命令节点它的DNS或者IP且在后期签名证书的扩展hosts范围内通常用DNS因为浏览器访问时一般很少用IP访问服务而不是服务器/etc/hostname文件中的那个主机名这一点非常容易混淆原因通常是大多公司部署服务喜欢用DNS做主机名其他的可以自定义# -storepass 是生成的密钥库密码# -keypass 是生成的密钥库中密钥的密码这一步可能会疑惑生成CA根证书时为什么只输入了一次密码而这里要分开输入根本原因是工具不一样openssl核心是用来控制证书的只需要一个统一的密码就够了而keytool 是用来管理密钥库的一个密钥库可以有多个密钥或者证书且来自于不同的地方所以秘钥的密码会不一样# 要注意命令中不需要指定过期时间因为公钥本来就不是加密的而且默认的公钥会在后期导入CA根证书的时候被覆盖keytool-keystore/root/keystore-aliasnode1-genkey-keyalgRSA-keysize2048-dnameCNnode1, OUdev,Odev,Ldev,STdev,CCN-storepass123456-keypass123456# 用自己生成的密钥库中密钥对的私钥生成一个签名请求文件# -certreq 生成签名请求的动作参数 -alias 别名一定要是密钥库中目标密钥对的别名 -file 是结果存储路径keytool-certreq-keystore/root/keystore-aliasnode1-file/root/cert-storepass123456-keypass123456# 用ca根证书和ca私钥给当前节点签名运行后输入ca私钥的密码# 如果你用脚本生成可以带 -passin pass:123456 这个配置非交互的输入根证书密码# -req 签名的动作参数 -CA ca的根证书文件 -CAkey ca的私钥文件 -in 请求签名文件 -out 签名结果文件存放路径 -days 签名有效天数一定要在ca根证书的剩余有效期内如果超出在不同工具下会出现报错等各种情况# -CAcreateserial -CAserial /root/hdfs_ca_cert.srl 这两个参数是维护ca签名的序列号存储在一个文件中其实就是记录签了多少个你可以不带但标准操作方式有要求openssl x509-req-CA/root/hdfs_ca_cert-CAkey/root/hdfs_ca_key-in/root/cert-out/root/cert_signed-days3650-CAcreateserial-CAserial/root/hdfs_ca_cert.srl# 将ca根证书 和 签名结果导入到节点自己生成的密钥库中第二件事就完成了keytool-keystore/root/keystore-aliasca-import-file/root/hdfs_ca_cert-storepass123456-noprompt# 导入证书的时候别名要和上面生成的密钥对别名一样它本质上是覆盖证书keytool-keystore/root/keystore-aliasnode1-import-file/root/cert_signed-storepass123456-noprompt可以关注一下结果文件的格式# 作为CA机构的主机用openssl生成的x509根证书是一个PEM格式文件ca私钥是一个Base64编码的ASCII文本文件[rootnode1 opt]# file hdfs_ca_certhdfs_ca_cert: PEM certificate[rootnode1 opt]# file hdfs_ca_keyhdfs_ca_key: ASCII text#用 keytool 生成的密钥库和受信库文件是用来给Java应用使用一种较老的Java特有格式jks格式文件[rootnode1 opt]# file keystorekeystore: Java KeyStore[rootnode1 opt]# file truststoretruststore: Java KeyStore#签名请求文件是一个CSR格式过程类的文件[rootnode1 opt]# file certcert: RFC1421 Security Certificate Signing Request, ASCII text, with CRLF, LF line terminators#签名结果文件和CA根证书一样是PEM格式文件[rootnode1 opt]# file cert_signedcert_signed: PEM certificate# ca签名时生成的序列号是一个Base64编码的ASCII文本文件[rootnode1 opt]# file hdfs_ca_cert.srlhdfs_ca_cert.srl: ASCII text 在其他IT领域安卓体系用的证书是BKS格式其他领域大多用的是PKCS12标准通用格式第二种Openssl PEM 格式Openssl 生成 PEM 这种方式属于纯手动通常用来给某个单一服务来生成证书比如 Docker 自建仓库用的 Harbor 为例# 用一个节点做ca生成一个自签的X.509 CA根证书(cert)和根证书的私钥(key)运行后要求输入根证书的密码按需设置这里设置123456。# 根证书本质是个单独存放的公钥这点和 JKS 受信库类似相当于这个CA机构的营业执照副本私钥部分单独存放相当于CA机构的公章# 单独存在的私钥本质上是一个很长的字符串用来验证证书的真伪以及作为给别人签名的核心工具# -keyout 根证书的私钥存放路径 -out 根证书存放路径# -days 当前ca根证书有效天数它会影响所签证书请求的有效期超过ca有效期时实际有效期终止到ca根证书过期的时间。但ca根证书切换成本很大因此一般都是3-10年。而私钥本身永不会过期# -newkey 是密钥的算法类型和密钥长度可以不指定默认是 rsa 2048位# -subj 这个是根证书签名中的附加信息就是这个证书CA机构在那里、那个城市、ca自身的名称等等这些自定义信息openssl req-new-x509-keyout/root/public_ca_key-out/root/public_ca_cert-days3650-newkeyrsa:2048-subj/CCN/STbeijing/Lhaidian/OdevA/OUdevB/CNdevC# 在需要证书的节点上生成一个用来标识当前节点的私钥openssl genrsa-outharbor.key2048# 注意由于 Harbor 比较特殊它没有输入密码的能力因此上面的命令没有设置私钥密码保护在其他场景下需要指定密码openssl genrsa-traditional-aes256-out私钥路径2048# 生成签名请求文件openssl req-new-keyharbor.key-outharbor.csr-subj/CCN/STBeijing/LBeijing/OYourOrg/CNnode1# 同样的如果私钥有密码需要指定openssl req-new-key私钥路径-passinpass:密码-out结果路径-subj/CCN/STBeijing/LBeijing/OYourOrg/CNnode1# 由于 harbor 使用了go语言1.15版本以上的语法库所以在证书签名时需要在扩展文件中写一些必要的东西。注意CN必须填你访问Harbor的域名或IPcatharbor-san.extEOF [v3_req] authorityKeyIdentifierkeyid,issuer basicConstraintsCA:FALSE keyUsage digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment extendedKeyUsage serverAuth subjectAltName alt_names [alt_names] # 关键把你所有的访问方式都列在这里 DNS.1 node1 DNS.2 localhost IP.1 127.0.0.1 IP.2 192.168.239.56 # 如果你还有域名也加上 # DNS.3 harbor.example.com EOF# CA用根证书和CA私钥签名运行后输入根证书密码openssl x509-req-inharbor.csr-CA/root/public_ca_cert-CAkey/root/public_ca_key-CAcreateserial-outharbor.crt-days365-extfileharbor-san.ext# 查看证书内容openssl x509-inharbor.crt-text-noout# 验证证书链输出OKopenssl verify-CAfile/root/public_ca_cert harbor.crt#查看harbor需要的两个结果物类型[rootnode1 opt]# file harbor.crtharbor.crt: PEM certificate[rootnode1 opt]# file harbor.keyharbor.key: PEM RSA private key 或者无密码保护时是个普通的文本文件 harbor.key: ASCII text上面的签名扩展文件是用来签发 barbor 需要的叶子证书如果你签发证书是为了做中间 CA 你要用的文件如下[req] #默认配置对于签名时指定了私钥文件的情况通常无效 default_bits2048 distinguished_namedn req_extensionsv3_req promptno [dn] # 生成私钥时的扩展字段不写也可以 CCN STBeijing LBeijing OYourOrg CNnode1 [v3_req] # 核心配置和前面叶子证书区别在于使用 ca 扩展配置 basicConstraintscritical,CA:TRUE keyUsagecritical,keyCertSign,cRLSign extendedKeyUsageserverAuth subjectAltNamealt_names [alt_names] # 对于一个ca来讲这部分没用然如果你的中间ca也要用来配置给服务做正常的证书用就要配上 DNS.1node1 DNS.2localhost IP.1127.0.0.1 IP.2192.168.239.56第三种cfssl PEM 格式工作中大部分情况证书多数是自签要构造的证书可能会很多且需要物化留痕所以上面 openssl 生成key、生成签名请求、扩展文件、签名这一套下来会很繁琐因此对于 PEM 这种通用格式的证书公司内部通常使用 cfssl 工具批量构造cfssl 需要单独下载# 1. 下载 cfssl 核心工具 (版本 R1.2) 主命令行工具用于证书签发操作curl-L-o/usr/local/bin/cfssl https://pkg.cfssl.org/R1.2/cfssl_linux-amd64# 2. 下载 cfssljson 工具 处理JSON格式输出将证书/密钥/CSR等写入文件curl-L-o/usr/local/bin/cfssljson https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64# 3. 下载 cfssl-certinfo 工具 证书信息查询工具使用频率较低curl-L-o/usr/local/bin/cfssl-certinfo https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64# 4. 赋予执行权限chmodx /usr/local/bin/cfssl*# 验证命令是否正常检查所在路径which cfssl 查看版本cfssl version生成 CA 根证书和私钥。“CN” 是当前 CA 的一个名字可以自定义。“key” 是生成证书用的算法和证书长度。names是这个 CA 的其他附加信息C是国家代码、ST是省或州、L是城市或地区、O是组织类信息、OU是部门类信息、CN是具体的一个主体比如人合理设置即可后期不可随意更改。“ca” 是定义了ca证书的有效期vica-csr.json{CN:ClusterCA,key:{algo:rsa,size:2048},names:[{C:CN,ST:Beijing,L:Beijing,O:cluster,OU:System}],ca:{expiry:876000h}}# 生成命令cfssl gencert-initcaca-csr.json|cfssljson-bareca准备 CA 签名用的证书配置文件它定义了一些 CA 签名时的策略。“default” 部分是签名时默认采用的时效设置在 ca 根证书有效期内或相等即可。profiles是签名策略预设里面包含细化的策略类型 通常 “signing” 、“key encipherment” 这两个是固定要写的。“server auth” 意思是https协议通讯时 服务端 需要向客户端证明身份用的最多。其他地方可能还有一个 “client auth” 就是反过来客户端也要像服务端证明自己的身份一般在 k8s 这种双向认证的场景下使用vica-config.json{signing:{default:{expiry:87600h},profiles:{server:{usages:[signing,key encipherment,server auth],expiry:87600h}}}}准备前后端共用的证书。“CN” 在这里是一个域名指该证书使用者的域名。hosts部分是扩展域名因为 CN 只有一个你要是不给其他人用就够用但现在前后端要共用一个整数就要让证书有多个可被使用的域名 配置时要注意 “*.example.com” 中的 * 只能代表一级域名如果你需要多级需要手动向下写比如 *.api.example.com , 且 * 只能在最左侧 “key” 、“names” 这两个部分和 ca 证书同理。“localhost”、“127.0.0.1” 这两个不要删除固定的不然本地访问会提示不安全viserver.json{CN:example.com,hosts:[localhost,127.0.0.1,example.com,*.example.com],key:{algo:rsa,size:2048},names:[{C:CN,ST:Beijing,L:Beijing,O:hive-auth-platform,OU:all}]}# 生成证书cfssl gencert-caca.pem -ca-keyca-key.pem-configca-config.json-profileserver server.json|cfssljson-bareserver最终在你的 CA 节点上应当存在如下的文件[rootnode1 ca]# lltotal36-rw-r--r--1root root288May2415:27 ca-config.json -rw-r--r--1root root250May2415:15 ca-csr.json -rw-------1root root1675May2415:16 ca-key.pem -rw-r--r--1root root1005May2415:16 ca.csr -rw-r--r--1root root1371May2415:16 ca.pem -rw-------1root root1679May2415:28 server-key.pem -rw-r--r--1root root1094May2415:28 server.csr -rw-r--r--1root root278May2415:26 server.json -rw-r--r--1root root1468May2415:28 server.pem关于密码cfssl 生成的证书中私钥是没有加密的这点和 openssl 不同cfssl 是为了解决自动化部署等场景下批量构造且过程留痕因此它的产出在使用时需要依靠linux文件系统权限 600 来保证。但如果需要你可以将 cfssl 产出的私钥使用 openssl加密[rootcorenode ~]# openssl rsa -in ca-key.pem -aes256 -out ca-key-encrypted.pemwriting RSA key Enter pass phrase: Verifying - Enter pass phrase:[rootcorenode ~]# openssl rsa -in ca-key.pem -check -nooutRSA key ok[rootcorenode ~]# openssl rsa -in ca-key-encrypted.pem -check -nooutEnter pass phraseforca-key-encrypted.pem: RSA key okPEM 格式证书转 P12#-export表示将 PEM 格式导出为 PKCS#12 格式。#-in指定输入的 PEM 证书文件。#-inkey指定输入的私钥文件。#-out指定输出的 .p12 文件名称。openssl pkcs12-export-incert.pem-inkeykey.pem-outcert.p12