OpenSSL:CA

CA(Certificate Authority)就是颁发证书的机构,你可以把他类比成西朝鲜的公安局,前者给你发数字证书,后者给你发身份证,二者的目的相同,都是为了证明你就是你,不是其他人。一句话,CA 就是能这样说话的机构“这个 jasey 就是你要的那个 jasey,不是别人伪造的;我们 CA 以自身的信誉能够证明这一点!”。

既然是数字证书,肯定不是那种纸质签名的了,这里又要涉及到我们先前谈到的核心思想,公钥密钥体系。发件人 jasey 给收件人 kiki 发送信息,那么 kiki 凭什么相信她收到的信息就一定是 jasey 发送的了?谁说的那个公钥就一定是 jasey 的公钥了?这把公钥难道不可以在传输的过程中被人做手脚吗?针对这些问题,就有了数字证书以及 CA。

数字证书就是由 CA 这个第三方公正权威的机构颁发的。他跟普通公钥的区别在于,数字证书除了包含用户的公钥,还附加了用户信息及 CA 这个权威机构的签名以表示对这张证书的信任。

看到这儿还不是很清楚的请继续往下看:

Bob 要使用网上银行,于是他打开了 www.chinasb.com 这个网页,他除了获取到了他要的数据之外,还接收到了一把公钥。当他要向 chinasb 这个网站提交一些敏感的数据比如金钱时,这把公钥就会将他要提交的数据加密通过浏览器再发给 chinasb。而私钥只有 chinasb 有,所以只有 chinasb 可以解密看到那写敏感的内容。

过了几天,Bob 重新在浏览器中输入 www.chinasb.com 这个网址,很不幸,这个网站被劫持了,一个跟他一模一样的网页出现在他面前(假设 Bob 没有这个辨别能力)。这个假的网页同样给了他一把公钥,他屁颠屁颠的把数据提交了上去,结果可想而知。

这时候就该 CA 出场了,当 Bob 的浏览器接收到了 www.chinasb.com 的公钥后,浏览器就会向 CA 确认这把公钥确实是属于 chinasb 的,这样就能有效的避免上面悲剧的发生了。

再假设一个情景,黑客 Eve 想法设法让 CA 给他颁发了一个证书,声称他就是 Alice,也就是说 CA 公开承认了 Eve 就代表 Alice。Eve 就用这个假的证书给 Bob 发邮件,让 Bob 相信这封信件是来自 Alice 的,Bob 这次又屁颠屁颠的相信了,于是重要的数据全被 Eve 的私钥给解开了。
所以说如果一个 CA 被攻击了,那么整个系统将会紊乱!2001 年的时候就发生了这样一起事故,当时 VeriSign 颁发了两张证书给了一个声称代表 MS 的人。还好 MS 和 VeriSign 发现的早,Versign 后来将这两张证书给撤销了,放进了 CRL(Certificate Revocation List) 中。

接下来可能有同学会问:为什么要搞个第三方的 CA 出来?我用我自己的私钥签名,再发给你,你用我的公钥验证不就行了么。这个问题如果只是我们俩的话,完全可以这样做。但是想象一下,我们每一个访问 https://www.alipay.com 要是都这样做,alipay 估计早就撑不下去了。所以出现了第三方 CA 这个机构。更多描述请参见这里。再者一般都是客户端验证服务端,而服务端不验证客户端,所以证书都是安装在客户端的。

下面我们看看浏览器是如何跟服务器交流的:

1.浏览器发起一个通常带有 https:// 的请求
2.服务器将证书连同它的公钥个给浏览器
3.浏览器检测证书是由 CA 颁发的,有效的,并且就是这个网站的
4.浏览器接着使用这个公钥加密对称密钥,将加密的 URL 连同数据发给服务器
5.服务器用私钥解密这个对称密钥,使用这个对称密钥解密 URL 和 http 数据
6.服务器使用对称密钥加密浏览器请求的 html 文件和 http 数据
7.浏览器使用对称密钥解密 http 数据和 html 文件

如何使用 OpenSSL 来生成证书了?上面都是铺垫,下面才是我们这篇文章要说的重点,我们这里讨论的将包括证书的生成,自签名以及认证签名。

自签名的一个最主要优点就是可以任意使用,前提是在一个相对封闭的环境中,也就是说不会流入英特网。要制作一个自签名证书,首先要建立一个 CA。

注意:证书本身没什么用,只有被 CA 签名以后才有用。你个小屁孩自己写个“我叫 jasey”证书,然后随便找个人签名说“这个确实 jasey”,你把这张证书拿到社会上去使用,会有人承认么?
你可以自己给自己签名,这个叫做自签名证书,所有的 root CA 证书都是自签名证书。

证书操作过程中最重要的一个指令就是:

req

req 不仅可以生成 RSA,DSA 密钥,以及将他们封装成证书请求,还可以对现有的证书请求进行签名验证,格式转换以及信息修改等。req 这个命令是跟 OpenSSL 的默认配置文件 openssl.cnf 有很大的联系。

下面是  req 后面接的选项:

inform:in 选项指定的输入证书请求文件的编码格式,支持 PEM 和 DER 编码。in 选项指定的文件输入内容是一个已经存在的符合 PKCS#10 标准的证书请求

outform:输出选项 out 输出的证书请求或自签名证书的编码格式,支持的编码方式跟 inform 一样

keyform:指定密钥输入选项 key 的密钥编码格式,包括 PEM,DER,PKCS#12 编码格式

in:指定了保存证书请求的文件名。如果要根据现有的证书请求生成自签名根证书(使用 x509 选项),要求同时使用 key 选项或者 newkey 选项,那么 in 选项指定的证书请求文件将被忽略

out:指定了输出文件名,输出的文件可以包括证书请求,自签名证书,编码公钥,其中证书请求和自签名证书的输出的编码格式可以为 DER 或者 PEM 格式,由 outform 决定;公钥的编码格式固定为 PEM 格式

key:输入的私钥文件,该文件的私钥编码格式由 keyform 决定

keyout: 指定生成的新私钥的输出保存文件, 若无,默认从 openssl.cnf 的 default_keyfile 读取,仅在使用了 newkey 或 new 选项导致生成新密钥对的时候才有效。若使用了已有的私钥(key 选项),该项会被忽略。输出到 keyout 选项指定文件的私钥一般会使用 DES3 加密

passin:读取 key 选项指定的私钥所需要的解密口令

passout:使用 keyout 选项输出私钥时的加密口令

new:执行生成新的证书请求操作,此时 in 选项指定的输入文件会被忽略。使用 new 选项后,如果没有使用 key 选项指定私钥用于生成证书请求,那么就会根据 newkey 的参数生成新的密钥对。如果既没有 key 也没有 newkey,默认会生成一个 RSA 密钥,由 openssl.cnf 中的 default_bits 选项决定

newkey:生成一个新的密钥对,该选项只有在没有使用 key 选项时候才有效。有如下两种形式:rsa:bits 或者 dsa:file。 前者 rsa 后面接密钥的位数,后者接生成 DSA 密钥的参数文件

nodes:不对新生成的私钥进行加密处理,passout 被忽略

pubkey:输出公钥到 out 指定的文件中,默认只输出私钥到 keyout 文件中,而不输出公钥

verify:对证书请求的数字签名进行认证,验证过程中会从证书请求中提取公钥,然后进行验证

x509: 生成一个自签名根证书而不是输出一个证书请求。所谓自签名根证书就是使用一对密钥的私钥对自己的的公钥生成的证书进行签名而颁发的证书,也就是说证书申请 人和签发者都是同一个人。在 req 下,既可以直接生成一个新的自签名根证书,也可以根据现有的证书请求和其相应的私钥生成自签名根证书。如果是后者,需要使用 key 选项提供相应的私钥才能执行成功

上面的选项看的可能有点头晕,总结一下:

生成新的证书请求操作:req 后面接 new 选项,若使用 key 指定私钥用于生成证书的请求,这个跟 in/keyout 选项是有冲突的,所以默认会忽略 in/keyout 选项;或者使用 newkey 生成新的密钥对,此时使用 keyout 有效,如果加了 nodes 选项则代表不要对新生成的私钥进行加密处理。
生成一个新的自签名根证书: req 后面接 x509 选项,需要有 key 选项加上私钥的支持。

生成一个新的证书请求文件,证书请求输出到 req.pem 中,私钥输出到 privkey.pem 中,私钥加密:

# openssl req -new -newkey rsa:1024 -keyout privkey.pem -out req.pem [-passout pass:123456]

生成一个自签名的根证书,非证书请求:

#  openssl req -x509 -newkey rsa:1024 -keyout privkey.pem -out selfsign.cer [passout pass:123456]

对一个已经签发的证书请求签名进行验证:

#  openssl req -in req.pem -verify [-noout]

证书请求是包含用私钥签名的证书的,所以可以用证书中的公钥来进行验证。

RSA 是目前最通用的加密方式,既支持密钥交换(加密),又支持签名。在使用 req 生成证书请求时,我们可以使用两种方式生成 RSA 密钥对,其一使用 genrsa 生成,其二直接使用 req 指令,但是前者的灵活性更大。

对 RSA 进行了加密,且只能使用 DES3 模式,此外私钥的输出格式也只能使用 PEM 格式:

# openssl req -new -newkey rsa:1024 -keyout privkey.pem -out req.pem

使用的 AES 对私钥进行了加密:

# openssl genrsa -aes256 -out rsakey.pem 1024
# openssl req -new -key rsakey.pem -out req.pem

同样的来看 DSA 方式

原始方式:

# openssl dsaparam -out dsaparam.pem 1024
# openssl req -new -newkey dsa:dsaparam.pem -keyout privkey.pem -out req.pem

以更灵活的方式,使用 AES256 加密 DSA 的私钥,应该更加安全:

# openssl gendsa -out dsakey.pem -aes256 dsaparam.pem
# openssl req -new -key dsakey.pem -out req.pem

大家有没有发现一个问题,上述的输出都只有私钥文件,公钥了?其实私钥文件的结构中已经包含了相应的公钥参数了,req 从中提取公钥,将其封装在 PKCS#10 的证书请求中,然后私钥完成对证书的签名。

接下来,我们来看看一个标准 CA 服务器的文件结构。

新建一个 myCA 目录,里面存放所有的跟 CA 相关的文件:

# mkdir ~/myCA
# cd ~/myCA
# mkdir newcerts private
# touch cacert.pem index.txt serial

其中 newcerts 存放新签发的证书文件,证书文件名为序列号,后缀为 pem;privare 存放的 CA 证书对应的私钥,默认为 cakey.pem;cacert.pem 是 PEM 编码的 CA 证书文件;index.txt 可以理解成一个数据库,保存了已经签发的证书信息;serial 是决定签发证书的序列号。

在 CentOS 下,默认的 OpenSSL 配置文件 openssl.cnf 在 /etc/pki/tls/ 下( ArchLinux 默认是 /etc/ssl/ ),我们可以复制一份到 ~/myCA 中:

# cp /etc/pki/tls/openssl.cnf ~/myCA/myssl.cnf

然后根据上面新建的目录以及文件名,将 myssl.cnf 改成与之符合的配置。如果你怕麻烦,可以直接在 /etc/pki/tls/ 下根据 openssl.cnf 默认配置来生成相应的证书请求或者自签名根证书。

除了 req 这个命令外,下面还有个几个比较重要的,直接以应用来理解,更具体的含义可以 man 一下,建议还是结合实例来分析,不然会比较难以理解。

ca

该命令模拟了一个完整的 CA 服务器,不但可以签发证书,还可以吊销证书,产生 CRL,更新证书数据库等操作。

签发证书,不输出明文信息到证书文件上,这样 win 能够正确解码签发的证书:

# openssl ca -in req.pem -out cert.cer -notext

使用 infiles 同时签发两个证书请求,限定证书的有有效期从 11 年 1 月 1 号到 12 年 1 月 1 号,默认的证书输出到 /etc/pki/tls/certs 中:

# openssl ca -notext -startdate 110101000000Z -enddate 120101000000Z -infiles req1.pem req2.pem

由于证书私钥泄露吊销证书:

# openssl ca -revoke cert.pem -crl_resion keyCompromise

x509

它可以以各种方式显示证书的信息,对证书的格式进行转换,甚至签发证书等。

将证书的信息直接现实在屏幕上:

# openssl x509 -in cert.pem -noout -text

显示证书的序列号/哈希值/MD5 指纹:

# openssl x509 -in cert.pem -noout -serial/-hash/-fingerprint

将 PEM 格式转换成 DER 格式:

# openssl x509 -in cert.pem -inform PEM -out cert.der -outform DER

pkcs12

可将普通的的 X509 证书和私钥封装成 PKCS#12 证书;反过来可以将 PKCS#12 转换成 X509 证书,并提取相应的私钥。

将 X509 的证书及私钥封装成 PKCS#12 格式的证书,并起一个比较容易的记的名字:

# openssl pkcs12 -export -in mycert.pem -inkey mykey.pem -out file.p12 -name "name"

verify

验证证书。

验证 cert.pem,他的 CA 是 cacert.pem:

# openssl verify -CApath ~/cacaert.pem -verbose cert.pem

接下来我将在 CentOS 5.5 x86_64 上,以默认的方式演示自签名根证书;在 ubuntu 10.04 i686 上演示给另一张证书签名。以下全部以 root 执行。另外上述的几台主机均配有 apache。

CentOS

# cd /etc/pki/tls;ls -lF
total 44
lrwxrwxrwx 1 root root   19 Feb 21 09:19 cert.pem -> certs/ca-bundle.crt
drwxr-xr-x 2 root root 4096 Apr  8 01:43 certs/
drwxr-xr-x 2 root root 4096 Feb 21 09:19 misc/
-rw-r–r– 1 root root 9828 Dec 15 10:30 openssl.cnf
drwxr-xr-x 2 root root 4096 Apr  8 01:43 private/

生成自签名根证书:

# openssl req -x509 -newkey rsa:1024 -keyout ./private/cakey.pem -out ./certs/cacert.pem

在执行的过程中会让你填写国家,地区等信息,其中有一项“Common Name”,填写你的 hostname,最后要输入密码来保护私钥。上述命令不再解释,看不懂的请往上翻。

安装 apache 上 ssl 模块:

# yum install mod_ssl

修改配置:

# vi /etc/httpd/conf/httpd.conf

在最后加上:

NameVirtualHost *.443
<VirtualHost *.443>
       SSLEngine on
       SSLCertificateFile /etc/pki/tls/certs/cacert.pem
       SSLCertificateKeyFile /etc/pki/tls/private/cakey.pem
       <Directory /var/www/html/>
       AllowOverride All
       </Directory>
</VirtualHost>

# echo "hello world" > /var/www/index.html

看一下 iptables,放行 443 端口:

# iptables -A INPUT -p tcp –dport 443 -j ACCEPT
# service iptables save

打开浏览器,输入 https://202.**.**.** ,firefox 会弹出如下的警告。由于只是自签名的,没有得到权威 CA 的认可才会弹出这样的会话框,选择“I Understand the Risks”,接着点击"Add Exception",进去后,点击“Get Certificate”,获取到证书后,"Confirm Security Exception" 就可以访问网页了。


 

Ubuntu

# mkdir ~/myCA
# cd myCA
# mkdir private signedcerts
# echo 01 > serial ; touch index.txt
# cat myCA/caconfig.cnf

# My sample caconfig.cnf file.
#
# Default configuration to use when one is not provided on the command line.
#
[ ca ]
default_ca      = local_ca
#
#
# Default location of directories and files needed to generate certificates.
#
[ local_ca ]
dir             = /root/myCA
certificate     = #dir/cacert.pem
database        = #dir/index.txt
new_certs_dir   = #dir/signedcerts
private_key     = #dir/private/cakey.pem
serial          = #dir/serial
#
#
# Default expiration and encryption policies for certificates.
#
default_crl_days        = 365
default_days            = 1825
default_md              = md5

#
policy          = local_ca_policy
x509_extensions = local_ca_extensions
#
#
# Default policy to use when generating server certificates.  The following
# fields must be defined in the server certificate.
#
[ local_ca_policy ]
commonName              = supplied
stateOrProvinceName     = supplied
countryName             = supplied
emailAddress            = supplied
organizationName        = supplied
organizationalUnitName  = supplied
#
#
# x509 extensions to use when generating server certificates.
#
[ local_ca_extensions ]
subjectAltName          = DNS:alt.tradeshowhell.com
basicConstraints        = CA:false
nsCertType              = server
#
#
# The default root certificate generation policy.
#
[ req ]
default_bits    = 2048
default_keyfile = /root/myCA/private/cakey.pem
default_md      = md5
#
prompt                  = no
distinguished_name      = root_ca_distinguished_name
x509_extensions         = root_ca_extensions
#
#
# Root Certificate Authority distinguished name.  Change these fields to match
# your local environment!
#

[ root_ca_distinguished_name ]
commonName              = MyOwn Root Certificate Authority
stateOrProvinceName     = JS
countryName             = CN
emailAddress            = jaseywang@gmail.com
organizationName        = NJUPT
organizationalUnitName  = CS
#
[ root_ca_extensions ]
basicConstraints        = CA:true

上面的文件就是修改自原来的默认配置文件 openssl.cnf,在这个文件中具体化了我们的需求,比如先前生成证书要填写的国家,省市等都在这个文件中定义好了。

加载 OPENSSL_CONF 变量:

# export OPENSSL_CONF=~/myCA/caconfig.cnf

生成 CA 自签名根证书:

# openssl req -x509 -newkey rsa:2048 -out cacert.pem -outform PEM -days 1825

# cat ~/myCA/exampleserver.cnf

# exampleserver.cnf
#

[ req ]
prompt                  = no
distinguished_name      = server_distinguished_name

[ server_distinguished_name ]
commonName              = jaseywang.info
stateOrProvinceName     = JS
countryName             = CN
emailAddress            = jaseywang@gmail.com
organizationName        = NJUPT
organizationalUnitName  = Subunit of My Large Organization

加载变量:

# export OPENSSL_CONF=~/myCA/exampleserver.cnf

生成证书请求:

# openssl req -newkey rsa:1024 -keyout tempkey.pem -keyform PEM -out tempreq.pem -outform PEM

为了方便,去除私钥的加密功能:

# openssl rsa < tempkey.pem > server_key.pem

加载变量,使用 CA 的密钥给证书签名:

# export OPENSSL_CONF=~/myCA/caconfig.cnf
# openssl ca -in tempreq.pem -out server_crt.pem

有些 IMAP 的邮件系统需要将没有经过对称密钥加密的私钥放在证书的前面,可以这样操作:

# car server_key.pem server_crt.pem > hold.pem
# mv hold.pem server_crt.pem
# chmod 400 server_crt.pem

大家有没有发现上面两张图的区别,CentOS 采用的是自签名根证书,所以在 Forefox 的 Technical Details 上显示的是“This certificate is not trusted because it is self-signed”,而 ubuntu 那台采用的是使用自签名根证书 CA 来给接下来的证书签名,所以它会显示"This certificate is not trusted because the issuer certificate is unknown",也就是表明这个 CA 不是权威的,没有得到社会的认可。

如果你希望从你根 CA 的 X.509 格式的证书生成 PKCS#12 格式的证书方便浏览器使用,可以这样:

# openssl req -x509 -nodes -days 3650 -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
# openssl pkcs12 -export -out mycert.pfx -in mycert.pem -name "Certificate for whatever"

或者像下面那样,前提是你已经有了 X.509 及其私钥:

# openssl pkcs12 -export -in server_crt.pem -inkey server_key.pem -out file.pfx

接下来加载 apache2 的 SSL 模块:

# a2enmod ssl
# cp /etc/apache2/sites-available/default /etc/apache2/site-available/mysite

NameVirtualHost *.443
<VirtualHost *:443>
   ServerAdmin jaseywang@gmail.com
   SSLEngine on
   SSLCertificateFile /root/myCA/cacert.pem
   SSLCertificateKeyFile /root/myCA/private/cakey.pem
   DocumentRoot /var/www
。。。。。。。。。。。。。

修改成如上的语法,其他的默认即可。

使新增的网站生效:

# a2ensite mysite

让所有的 http 流量全部重定向到 https,实现全站 https:

# vi /etc/apache2/sites-available/default

RewriteEngine   on
RewriteCond     %{SERVER_PORT} ^80#
RewriteRule     ^(.*)# https://%{SERVER_NAME}#1 [L,R]

重启:

# /etc/init.d/apache2 restart

下面两段命令同样是生成证书包括签名,方式不一样,有兴趣的可以看一下:

# openssl genrsa -des3 -out server.key 1024
# openssl req -new -key server.key -out server.csr
# openssl x509 -req -day 365 -in server.csr -signkey server.key -out server.crt

# openssl req -new -x509 -keyout cacert.pem -out cakey.pem -config ~/openssl.cnf -days 3650
# openssl genrsa -des3 -out test.key 1024
# openssl req -new -key test.key -out testcert.csr -config ~/openssl.cnf -days 3650
# openssl ca -in testcert.cst -out testcert.crt -cert cakey.pem  -keyfile cacert.pem

建立 CA 是一套比较复杂的流程,从技术层面来说,实现 CA 比较好的方式就是使用 OpenSSL 这套开源的框架。这篇文章只是简单的叙述了一下 CA 的一些基本操作,因为从个人的角度来看,应用场合不是很多。网上这方面的参考资料不少,但是大多都停在了怎样使用自签名,而没有说清楚为什么, 所以我将他们整理一下,写了 OpenSSL:BasicOpenSSL:CA 两篇文章,跟大家分享一下我对 CA 的“看法” 。有兴趣的同学可以看看《OpenSSL 与网络信息安全-基础,结构,之类》这本书,很详细的解释了 OpenSSL 这一套体系及其操作。

 

扩展阅读:
一篇关于 OpenSSL 的 FAQ:
【1】OpenSSL Command-Line HOWTO
下面的几篇主要讲的是 CNNIC 这个土匪做的些见不得人的事以及如何抵御这个土匪:
【2】CNNIC干过的那些破事
【3】CNNIC CA:最最最严重安全警告!

参考:
http://en.wikipedia.org/wiki/Certificate_authority
https://help.ubuntu.com/community/OpenSSL
http://wiki.centos.org/zh/HowTos/Https?highlight=(openssl)
https://wiki.archlinux.org/index.php/OpenSSL
http://www.linode.com/wiki/index.php/Apache2_SSL_in_Ubuntu
http://www.openssl.org/docs/HOWTO/certificates.txt
http://www.gtlib.cc.gatech.edu/pub/linux/docs/HOWTO/other-formats/html_single/SSL-Certificates-HOWTO.html
http://www.madboa.com/geek/openssl