背景

在工作中,遇到一个要写agent的场景,因为agent具有一定的执行能,且由控制端发起,因此就需要考虑安全问题。因为代码需要部署到客户机器上,简单使用对称加密不安全,主要是担心密钥不安全。因此选用了 SSL/TLS 协议。 使用的python有库可以选用,因此,最大的工作量还是 证书的生成。

找到 一个比较全的资料如何使用OpenSSL工具生成根证书与应用证书,在此留档,以供参考。

步骤简记

以生成一个二级证书链为例,将会用到以下命令:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 生成顶级CA的公钥证书和私钥文件,有效期10年(RSA 1024bits,默认)  
openssl req -new -x509 -days 3650 -keyout CARoot.key -out CARoot.crt     
  
# 生成顶级CA的公钥证书和私钥文件,有效期15年(RSA 2048bits,指定)  
openssl req -newkey rsa:2048 -x509 -days 5480 -keyout CARoot.key -out CARoot.crt  
# 为顶级CA的私钥文件去除保护口令  
openssl rsa -in CARoot.key -out CARoot.key  
  
# 为应用证书/中级证书生成私钥文件  
openssl genrsa -out app.key 2048  
# 根据私钥文件,为应用证书/中级证书生成 csr 文件(证书请求文件)  
openssl req -new -key app.key -out app.csr  
# 使用CA的公私钥文件给 csr 文件签名,生成应用证书,有效期5年  
openssl ca -in app.csr -out app.crt -cert CARoot.crt -keyfile CARoot.key -days 1826 -policy policy_anything  
# 使用CA的公私钥文件给 csr 文件签名,生成中级证书,有效期5年  
openssl ca -extensions v3_ca -in app.csr -out app.crt -cert CARoot.crt -keyfile CARoot.key -days 1826 -policy policy_anything

简要说明:

1024bit的RSA在2009年因大数成功因数分解而被威胁,因此建议普通用户尽快升级到2048bit或以上。

各步详解

生成自签名根证书(即顶级CA)

典型示例: openssl req -new -x509 -days 5480 -keyout CA.key -out CA.crt

命令选项和参数解读

示例中,各选项(及参数)的意义如下:

req //使用openssl的req子命令
-new //生成新的证书请求
-x509 //生成自签名证书
-days 5480 //自签名证书的有效期5480天(15年)【仅当使用了 -x509 选项后有效】
-keyout CA.key //私钥文件名指定为CA.key【此选项的一般作用是新生成文件命名;但若同时使用了-key选项,则此选项用于原私钥文件的更名】
-out CA.crt //指定输出所生成自签名证书的信息到文件,且文件名为CA.crt【建议不要省略】

关于私钥文件加密口令的指定

运行中会提示输入加密口令,如下:

1
2
3
writing new private key to 'CA.key'  
Enter PEM pass phrase:  
Verifying - Enter PEM pass phrase:

此口令用于加密私钥文件 CA.key 中的私钥信息,如果不想在运行过程中还要输入加密口令,则可以使用选项 -passout 在命令中直接指定。选项 -passout 的使用形式为: -passout arg。 其中,arg是选项 -passout 的参数,其格式有多种,详参《OpenSSL官方文档》中关于"PASS_PHRASE_ARGUMENTS"的介绍。

本小节典型示例中的命令,可以用选项 -passout 改造如下: openssl req -new -x509 -days 5480 -keyout CA.key -out CA.crt -passout pass:1314 由于Linux系统中可以使用history命令查看历史指令记录,所以出于安全方面的考量,一般如非必要,不建议在命令中直接指定口令。这与mysql登录的时候不在 -p 选项里直接指定登录口令的原因是一致的。

关于证书请求文件中的DN字段

运行中会提示输入一些 Distinguished Name fields,即证书的识别名信息字段,简称为DN字段,如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
You are about to be asked to enter information that will be incorporated into your certificate request.  
What you are about to enter is what is called a Distinguished Name or a DN.  
There are quite a few fields but you can leave some blank  
For some fields there will be a default value,  
If you enter '.', the field will be left blank.  
-----  
Country Name (2 letter code) [GB]:US  
State or Province Name (full name) [Berkshire]:California  
Locality Name (eg, city) [Newbury]:  
Organization Name (eg, company) [My Company Ltd]:GeoAuth Inc.  
Organizational Unit Name (eg, section) []:.  
Common Name (eg, your name or your server's hostname) []:Authentication Global Root  
Email Address []:.  

这些DN字段大部分有默认值,默认值由配置文件 openssl.cnf中相关条目指定。如要在某一个DN字段使用默认值,则无需输入任何信息,直接点击"Enter"键;如果确实某个DN字段的值要置为空,则输入一个 ‘.’ 后,点击"Enter"键。 这些DN字段主要是拿来识别证书持有者身份的,下表是关于它们的缩写、说明和一些填写说明。

DN字段名 缩写 说明 填写要求
Country Name C 证书持有者所在国家 要求填写国家代码,用2个字母表示
State or Province Name ST 证书持有者所在州或省份 填写全称,可省略不填
Locality Name L 证书持有者所在城市 可省略不填
Organization Name O 证书持有者所属组织或公司 最好还是填一下
Organizational Unit Name OU 证书持有者所属部门 可省略不填
Common Name CN 证书持有者的通用名 必填。对于非应用证书,它应该在一定程度上具有惟一性;对于应用证书,一般填写服务器域名或通配符样式的域名。
Email Address 证书持有者的通信邮箱 可省略不填

注:表中所谓,证书不是应用证书时,其持有者的通用名要有“唯一性”,是指其通用名不要与一般主机

上常见的信任证书列表或撤销证书列表中的证书产生重复。

如果不想在运行过程中逐个输入这些DN字段的值,则可以使用 -subj 选项在命令中直接指定。选项 -subj 的使用形式为: -subj arg 其中,arg是选项 -subj 的参数,其格式类似于:/type0=value0/type1=value1/type2=… 形式。每一个 /type=value 形式的单元,都对应了一个完整的DN字段。其中,

  • / 是每一个DN字段的开始标志;
  • type0、type1等等,就是表2-1中提到的DN字段名的缩写;
  • =是DN字段的名和值之间的间隔符;
  • value0、value1等等,就是原本你要在运行过程中逐个输入的DN字段的值。

对于您要将值置为空的DN字段,您可以略去不写。本小节典型示例中的命令,可以用 -subj 选项改造如下【略去了 L 与 OU 两个DN字段】: openssl req -new -x509 -days 5480 -subj /C=US/ST=California/O=GeoAuth\ Inc./CN=Authentication\ Global\ Root -keyout CA.key -out CA.crt

需要特别注意的是,如果您设定的DN字段的值如果存在一些特殊字符【比如 (空格)、((半角左括号)、)(半角右括号)……】,必须经过\(反斜杆)转义。上例中已经给出了空格转义的情况。

如何指定自签名证书的密钥长度和类型

如前所述,示例只能生成密钥长度为1024bits的RSA公私钥对。如果要生成密钥长度不为1024bits的RSA公私钥对,或是其他类型的【比如DSA、EC】公私钥对,则必须使用选项 -newkey 来代替 -new 。选项 -newkey 的用法比较复杂,如需详细了解,请参看《OpenSSL官方文档》的相关页面 。本小节典型示例中的命令等效于以下命令: openssl req -newkey rsa:1024 -x509 -days 5480 -keyout CA.key -out CA.crt 要生成一个密钥长度为2048bits的RSA公私钥对,命令为: openssl req -newkey rsa:2048 -x509 -days 5480 -keyout CA2048.key -out CA2048.crt

为顶级CA的私钥文件去除加密保护

上节提到,顶级CA的私钥文件是经过加密保护的,以后每当需读取 CA.key 文件中的私钥信息时,都需输入解密口令。这种做法适合有安全需求的场合,但如果觉得不方便,也可以去除这个口令。 典型示例:penssl rsa -in CA.key -out CA.key

命令选项和参数解读

示例中,各选项/参数的意义如下:

rsa //使用openssl的rsa子命令
-in CA.key //经加密保护的私钥文件
-out CA.key //解除加密保护后的私钥文件【可以改名】

关于私钥文件解密口令的指定

运行中会提示输入解密口令,如下:

Enter pass phrase for CA.key:
writing RSA key

如果不想在运行过程中还要输入解密口令,则可以使用选项 -passin 在命令中直接指定。选项 -passin 的使用形式为: -passin arg

其中,arg是选项 -passin 的参数,其格式同选项 -passout 的参数,详参《OpenSSL官方文档》中关于"PASS_PHRASE_ARGUMENTS"的介绍。

本小节典型示例中的命令,可以用选项 -passin 改造如下: openssl rsa -in CA.key -out CA.key -passin pass:1314 基于同选项 -passout 一样的考量,一般不建议直接在命令中指定解密口令。

rsa子命令的实际功能

rsa子命令的功能,更完整来说,应该是RSA密钥的管理。除了此处用到的去除加密保护,还可以用来:更换加解密口令,更换加解密算法【DES、三重DES、IDEA(官方文档上提到过,但是有些版本好像不支持,比如说我安装的)、AES(官方文档上没提到,我安装的版本支持)】,更换密钥文件的编码方式等等。

为应用证书/中级证书生成私钥文件

典型示例:openssl genrsa -out app.key 2048

命令选项和参数解读

示例中,各选项/参数的意义如下:

1
2
3
genrsa         //使用openssl的genrsa子命令  
-out app.key   //指定输出生成的私钥信息到文件, 且文件名为app.key【建议别省略】   
2048           //指定所生成私钥的比特长度【务必放在最后一个】 

其中,选项 -out 若是省略的话,openssl不会以文件形式输出生成的 私钥信息,而是会默认将私钥的信息直接打印到屏幕上,这不符合我们的要求。所以建议这个选项不要省略!

genrsa子命令还可以有其他一些选项及参数,但不论还有多少选项,都必须把指定私钥长度的参数放在最后一个。【如果指定私钥长度的参数不是最后一个,则其后的参数好像会被舍弃。】

私钥文件的加密保护

本小节典型示例所给出的命令,生成的是未经任何加密保护的私钥文件。这种方式用起来比较方便,但非常不安全。如果要为私钥文件附加加密保护,则有两种方式:

  • 在生成私钥文件的同时就指定输出前要用某种加密算法加密保护;
  • 生成明文的私钥文件后用 rsa 子命令附加某种加密算法的加密保护

我计算机上安装的openssl版本,在两种方式下都支持这些加密算法选项:-des,-des3,-aes128,-aes192,-aes256。以加密选用192位的AES算法为例,命令如下:

1
2
3
4
# 在生成私钥文件的同时就指定输出前要用192位的AES算法加密保护  
openssl genrsa -aes192 -out app.key 1024  
# 生成明文的私钥文件后用 rsa 子命令附加192位的AES算法加密保护  
openssl rsa -aes192 -in app.key -out app.key

上例中的两个命令,都会要求在运行中输入加密口令。同样的,虽然不建议,但是openssl依然支持在命令中使用选项 -passout 指定加密口令,选项 -passout 的用法参考2.1[2]小节。唯一需要额外注意的是,对于genrsa子命令,选项 -passout 应放在 指定密钥长度的参数 前面。

为应用证书/中级证书生成 csr 文件【证书请求文件】

典型示例:openssl req -new -key app.key -out app.csr

命令选项和参数解读

1
2
3
4
req             //使用openssl的req子命令  
-new            //生成新的证书请求  
-key app.key    //指定是为app.key文件中的私钥生成证书请求  
-out app.csr    //指定输出所生成证书请求的信息到文件,且文件名为app.csr  

这和2.1节使用的是openssl的同一个子命令,所以参数的意义很多具有共通性。命令运行中也会要求输入一些DN字段的值,参看本文2.1 [3] 小节中的相关介绍。同样的,可以使用-subj选项在命令中直接指定这些DN字段的值。

为 csr 文件签名,生成应用证书/中级证书

典型示例:openssl ca -in app.csr -out app.crt -cert CA.crt -keyfile CA.key -days 1826 -policy policy_anything

命令选项和参数解读

示例中,各选项/参数的意义如下:

1
2
3
4
5
6
7
ca                        #使用openssl的ca子命令  
-in app.csr               #指定待签发证书的 CSR文件为 app.csr  
-out app.crt              #指定输出所签发证书的信息到文件,且文件名为app.crt【建议不要省略】   
-cert CA.crt              #指定为应用/中级证书签名的CA的公钥证书为CA.crt【用到CA证书的持有者信息】  
-keyfile CA.key           #指定为应用/中级证书签名的CA的私钥文件为CA.key【用CA私钥实际执行签名】  
-days 1826                #指定所签发证书的有效期为1826天(5年)  
-policy policy_anything   #指定签发策略为 policy_anything 【即,允许所签发证书的持有者信息和颁发者信息之间不遵守任何匹配策略】  

其中,选项 -out 若是省略的话,openssl不会以文件形式输出生成的 应用证书/中级证书信息,而是会默认将证书的信息直接打印到屏幕上,这一般不符合我们的要求。所以建议这个选项不要省略!

关于签发策略

在本节的典型示例中,我们使用了选项-policy来指定签发策略为 policy_anything。如果没有使用此选项,则签发策略使用由配置文件 openssl.cnf 中相关条目指定的默认策略。如果您没动过该配置文件的话,则默认策略为 policy_match。此策略要求CA的公钥证书和应用证书请求文件中的Country Name、State or Province Name、Organization Name这三个字段必须 match 【也就是一样】。如果您在应用证书请求文件中指定了和CA的公钥证书不一样的Country Name,且没有使用选项-policy,则会得到类似于下方的提示:

The countryName field needed to be the same in the
CA certificate (US) and the request (CN)

查看配置文件 openssl.cnf 中相关条目,我们可以看到以下内容,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# A few difference way of specifying how similar the request should look  
# For type CA, the listed attributes must be the same, and the optional  
# and supplied fields are just that :-)  
policy      = policy_match  
  
# For the CA policy  
[ policy_match ]  
countryName     = match  
stateOrProvinceName = match  
organizationName    = match  
organizationalUnitName  = optional  
commonName      = supplied  
emailAddress        = optional  
  
# For the 'anything' policy  
# At this point in time, you must list all acceptable 'object'  
# types.  
[ policy_anything ]  
countryName     = optional  
stateOrProvinceName = optional  
localityName        = optional  
organizationName    = optional  
organizationalUnitName  = optional  
commonName      = supplied  
emailAddress        = optional  

看到这,原因就很显然了。所以若是您要在应用证书请求文件中指定和CA的公钥证书不一样的C/ST/O,且要签名成功,则有以下三种方式:

  1. 加上-policy选项,指定本次使用的签发策略为 policy_anything
  2. 将配置文件 openssl.cnf 中的默认策略由 policy_match改成 policy_anything
  3. 将策略 policy_match 中的相关条目由 match改成 optional

为什么openssl会使用这样的默认策略呢?其实很简单,openssl认定,仅当您建立组织内部的信任关系时,您才需要自己做CA。既然是组织内部的信任关系,当然CA和应用证书就该有一样的所在国家、所在省份,以及所属组织。

关于签发中级证书(二级/三级CA,子CA)

由本节典型示例命令生成的证书只能作为应用证书,因为它不具有继续签署下级证书的权限。下图展示了中级证书与应用证书的区别。 由上图可以看出,二者主要是在证书的扩展字段Subject Type的取值有所不同。中级证书该字段取值为"CA”,表明此证书依然可以作为CA,继续签发下级证书;一般应用证书该字段的取值为"End Entity”,表明这已经是证书链的最后一个结点,自然也就不能继续签发下级证书。

查看配置文件 openssl.cnf 中相关条目,可以看到如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[ ca ]  
default_ca  = CA_default        # The default ca section  
……………………  
[ CA_default ]  
……………………  
x509_extensions = usr_cert      # The extentions to add to the cert  
……………………  
[ usr_cert ]  
  
# These extensions are added when 'ca' signs a request.  
  
# This goes against PKIX guidelines but some CAs do it and some software  
# requires this to avoid interpreting an end user certificate as a CA.  
  
basicConstraints=CA:FALSE  
……………………  
[ v3_ca ]  
……………………  
# This is what PKIX recommends but some broken software chokes on critical  
# extensions.  
#basicConstraints = critical,CA:true  
# So we do this instead.  
basicConstraints = CA:true  
……………………  

由此可以看出,为什么默认生成的都是纯应用证书。若要生成中级证书(一般是出于信任关系建立的便利),则有以下方式:

  1. 上级CA在签署此证书时,加上 -extensions 选项,且选项参数设为 v3_ca (如步骤简记中所示);
  2. 将 CA_default 条目中 x509_extensions 的值由 usr_cert改为 v3_ca;
  3. 将 usr_cert 条目中 basicConstraints的值由 CA:FALSE 改为 CA:true;

关于批处理模式

执行本节典型示例命令生成证书时,会遇到类似于下面的提示:

1
2
3
4
5
6
7
Certificate is to be certified until Mar 27 03:44:08 2015 GMT (365 days)  
Sign the certificate? [y/n]:y  
  
  
1 out of 1 certificate requests certified, commit? [y/n]y  
Write out database with 1 new entries  
Data Base Updated 

也就是说,这个命令是有交互的,你必须在运行过程中手工完成两次确认输入。但我们在自己生成证书链的时候,证书请求文件和CA证书都是由我们自己生成的,一般并不需要确认。另外,我们一旦需要用程序自动化/批量化处理,也会显得麻烦 说到底,一句话,如果你不想有交互,那么可以使用 -batch 选项。本小节典型示例中的命令,可以用选项 -batch 改造如下: openssl ca -in app.csr -out app.crt -cert CA.crt -keyfile CA.key -days 1826 -policy policy_anything -batch

常见问题及其解决

没有权限访问

问题表现:没有权限访问 /etc/pki/CA/newcerts

解决办法:需要root权限执行

找不到 index.txt 问题

问题表现: ubable to open '/etc/pki/CA/index.txt' 解决办法: 需要在/etc/pki/CA/目录下建立一个空文件 index.txt

serial 文件问题

找不到文件

问题表现: 找不到文件serial文件( No such file or directory: bss_file.c: 402: fopen( ‘/etc/pki/CA/serial’, ‘r’)) 解决办法: 需要在/etc/pki/CA/目录下建立一个名为 serial的文本文件切内容为00

文件加载出错

问题表现: 文件load出错(unable to load number from /etc/pki/CA/serial) 解决办法: 查看 serial文件内容是否为数字