自签名证书制作

X509 是一种证书标准,定义了证书应该包含的内容,也是SSL使用的证书标准,可以参考rfc5280

编码格式

存储证书时主要有:

  1. pem(Privacy Enhanced Mail)文本编码
    以”—–BEGIN—–”开头,以”—–END—–” 结尾,内容是BASE64编码。
    Apache和*NIX服务器偏向于使用此类编码格式。
    可通过如下方式查看:

    1
    openssl x509 -in your.pem -text -noout
  2. der(Distinguished Encoding Rules)二进制编
    二进制格式不可读,Java和Windows服务器偏向于使用这种编码格式。
    可通过如下方式查看:

    1
    openssl x509 -in your.der -inform der -text -noout

文件扩展名

上面是编码格式,本来就有pem和der的文件扩展名,但是,我们常常还能看到下面的扩展名(按文件功能来区分),其内可能是如上两种编码格式。

  1. key
    key文件后缀一般用于存储私钥。
    私钥需要谨慎存储,以防泄漏,可以给私钥加密,并设置文件读取权限。
    可以用如下方式生成私钥:
    1
    2
    3
    4
    5
    6
    7
    8
    $ openssl genrsa -out ca.key 2048
    $ cat ca.key
    -----BEGIN RSA PRIVATE KEY-----
    MIIEpAIBAAKCAQEA2XAVPi2soM0Wtx50r38O2Vilznb38MJnwadsm1OwKpRWHj8I
    ...
    vwiKSZ8Tca0PkfoTE4QQRxHxpoI6Guhrqgxp4nTLaDJ6fO8VzSXGJg==
    -----END RSA PRIVATE KEY-----
私钥生成的时候,如果你想对私钥做加密存储,可以指定加密方法,如-des3,然后输入密码后私钥就被加密存储了,文件内容表现上,会多一些头部信息:  
1
2
3
4
5
6
7
8
9
10
11
$ openssl genrsa -des3 -out ca.key 2048
$ cat ca.key
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,24E18B27E7DA3DBA
FEMUvp7Rrjz2RmIqmRCb5Z3EIyqf4pkRM0hxuiu+ouvW08Qs/YATw2A9BXHq7vfX
...
8eZjXcPPpTTg8GRht4ScratQzgSx8XJ5ArxF5v3cl1323bYUAJVJnQ==
-----END RSA PRIVATE KEY-----
可通过如下方式查看:
1
openssl rsa -in your.key -text -noout
或der格式:
1
openssl rsa -in your.key -inform der -text -noout
  1. csr(Cerificate Signing Request)
    csr为签名请求文件,其核心内容为自己的公钥和一些附加信息(如域名匹配字串),一般来说,给证书签名的都是受信任机构,主要是浏览器和操作系统里面内置的那几家,而普通厂商或个人,若想让浏览器也信任你的网站,必须先到受信任的证书机构去申请,让其用他们的证书为咱们的证书做一次签名,这个请求过程,理论上就需要咱们提供csr文件。

    可通过如下方式查看:

    1
    openssl req -in your.csr -text -noout

    若是der格式,同理加入-inform der即可。

  2. crt(Certificate)
    被证书机构签名后的csr,就是crt文件,这是签署人用自己的秘钥给你签署的凭证。(windows下导入证书的时候,扩展名默认是csr,其实应该是crt)。

  3. pfx/p12(predecessor of PKCS#12)
    *nix服务器一般将crt和key分开存放,但Windows的IIS则将它们存在一个pfx文件中,这样的好处是移动方便,但是可能会泄漏私钥,没关系,pfx文件查看是需要输入“提取密码”的,和上面的key文件指定的存储密码类似。

    pfx内部同样可能是der或pem格式,如何将pfx的der格式转换为pfx的pem格式?

    1
    openssl pkcs12 -in your.pfx -out your.pem -nodes
如何生成pfx文件:  
1
openssl pkcs12 -export -in 你的证书.crt -inkey 你的私钥.key -out your.pfx -certfile 权威机构的签名证书.crt
  1. jks(Java Key Storage)
    这是Java自己的一套,和OpenSSL关系不大,需要利用Java自带的keytool工具进行操作。

  2. crl(Certificate Revocation List)
    证书吊销列表,不是用来存储证书或私钥的。

自签名证书生成

之前使用的SSL自签名证书,很简陋,怎么做也没法让浏览器真正信任网站,Chrome报错为NET::ERR_CERT_COMMON_NAME_INVALID:
err_cert_common_name_invalid

研究了一下,生成自签名证书,需要附加“使用者备用名称”,将域名或IP加入证书,我将关键的配置抽象成模板,如下:

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
26
27
28
29
30
31
32
33
34
35
36
~/cert $ cat cfg/req-ssign-one.template
[ req ]
default_bits = 2048
default_keyfile = private/ca.key
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = x509_ext
string_mask = utf8only
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name (full name)
localityName = Locality Name (eg, city)
organizationName = Organization Name (eg, company)
commonName = Common Name (e.g. server FQDN or YOUR name)
emailAddress = Email Address
#%distinguish%
[ x509_ext ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alt_names
[ req_ext ]
subjectKeyIdentifier = hash
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alt_names
[ alt_names ]
#%altnames%

你只需要替换其中的“#%distinguish%”为自己需要的信息,例如:

1
2
3
4
5
6
7
~/cert/ssign $ cat distinguish.userdef
countryName_default = CN
stateOrProvinceName_default = Guangdong
localityName_default = ShenZhen
organizationName_default = X-Security
commonName_default = X-Security Platform
emailAddress_default = plat@x-security.com

替换#%altnames%(使用者备用名称)为自定义域名或IP,例如:

1
2
3
4
5
6
~/cert/ssign $ ssign/altnames.userdef
DNS.1 = plat.x-security.com
DNS.2 = plat1.x-security.com
DNS.3 = google.plat
IP.1 = 192.168.111.10
IP.2 = 200.200.221.186

配置写成这种方式是不行的,只能取最后一个:

1
2
DNS = plat.x-security.com
DNS = plat1.x-security.com

如果同value的话,最后生成的证书也会出现多条同value的项,并不会在生成过程中uniq:

1
2
DNS.1 = plat1.x-security.com
DNS.2 = plat1.x-security.com

几者结合起来,综合成一个配置文件(例如req.conf),便可以使用如下命令生成秘钥和证书:

1
~/cert/ssign $ openssl req -config req.conf -new -sha256 -newkey rsa:2048 -nodes -keyout server.key -x509 -days 3650 -out server.crt

将生成的私钥和证书替换到Apache配置下对应位置:

1
2
3
~ $ cat httpd.conf
SSLCertificateFile "你的路径/server.crt"
SSLCertificateKeyFile "你的路径/server.key"

重启Apache,浏览器ctrl+F5请求便能看到新版本证书。在Windows客户端,可指定伪域名,将证书里面的域名加入hosts文件:C:\Windows\System32\drivers\etc\hosts,例如添加:

1
2
3
192.168.111.10 plat.x-security.com
192.168.111.10 plat1.x-security.com
200.200.221.186 google.plat

此时打开Chrome浏览器,错误变为了NET::ERR_CERT_AUTHORITY_INVALID:
err_cert_authority_invalid

看得出,这报错是证书不受信任,而不再是域名匹配不上。

Windows证书自签名导入

解决上面的问题,只需要将证书导入Windows即可,以Chrome为例(Chrome和IE都是用的Windows自己的证书管理系统,而Firefox不是),步骤为:

  1. Chrome打开网站,点击“不安全”->”证书”
    import setup1

  2. 点击“详细信息”->”复制到文件”->”下一步”
    import setup2

  3. 点击”下一步”
    import setup3

  4. 点击”浏览”,选择输出路径,然后点击”下一步”
    import setup4

  5. 双击导出到桌面的证书文件,点击”安装证书”->”下一步”
    import setup5

  6. 选择”将所有的证书放入下列存储”->”浏览”选择存储位置为”受信任的根证书颁发机构”->”确定”->”下一步”
    import setup6

  7. 此时会弹出一个告警框,点击”是”
    import setup7

经过如上几个步骤,证书便导入了系统,此时再次打开Chrome浏览器,之前的红色告警和阻止页面便会消失:

  1. 受信任的域名
    valid domain

  2. 受信任的IP地址
    valid ip

如果遇到NET::ERR_CERT_DATE_INVALID错误,可能是因为你签名的服务器时钟比较快,导致生效时间还没到。

##浏览器差异性

  1. Chrome和Firefox支持IP地址的证书认证,而IE(实测IE8)并不支持IP,只支持域名。
  2. 自签名的证书,虽然Chrome和IE是认的,但Firefox不认,Firfox会报错MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT,且无法导入自签名证书到Firefox,会提示:这不是一个证书颁发机构证书,因此无法导入到证书颁发机构列表。效果仍然是被拒绝:
    firefox-self-sign

    你可以添加例外,添加后效果如下:
    firefox-self-sign-show

第三方认证证书生成

自己用自己的秘钥为自己的csr文件签名,叫做自签名。
而正常情况下,我们是将自己的csr文件发给第三方权威机构给我们签名,我们模拟一下,假设我们就算第三方权威机构,如何给别人颁发证书。

角色权威机构

  1. 生成私钥

    1
    openssl genrsa -out private/ca.key 2048
  2. 生成csr

    1
    openssl req -new -key private/ca.key -out ca.csr

    上述命令需要一步一步输入提示信息,多次操作比较麻烦,我将输入配置成模板:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ~/cert $ cat cfg/req.template
    [ req ]
    default_bits = 2048
    default_keyfile = private/ca.key
    distinguished_name = req_distinguished_name
    string_mask = utf8only
    [ req_distinguished_name ]
    countryName = Country Name (2 letter code)
    stateOrProvinceName = State or Province Name (full name)
    localityName = Locality Name (eg, city)
    organizationName = Organization Name (eg, company)
    commonName = Common Name (e.g. server FQDN or YOUR name)
    emailAddress = Email Address
    #%distinguish%

    使用者只需要替换#%distinguish%为自己的信息,例如:

    1
    2
    3
    4
    5
    6
    7
    ~/cert $ cat x-securityca/distinguish.userdef
    countryName_default = CN
    stateOrProvinceName_default = Guangdong
    localityName_default = ShenZhen
    organizationName_default = x-security
    commonName_default = x-security Certification Center
    emailAddress_default = cert@x-security.com

    几者结合起来,综合成一个配置文件(例如req.conf),便可以使用如下命令生成签名请求文件:

    1
    ~/cert/x-securityca $ openssl req -new -key private/ca.key -out ca.csr -config req.conf
  3. 生成crt
    由于是自签名,所以只需要用自己的私钥作为signkey,下面给的签名期限为20年:

    1
    ~/cert/x-securityca $ openssl x509 -req -in ca.csr -out ca.crt -signkey private/ca.key -days 7305

如上,权威机构一方的私钥和证书生成完毕,但是要用来签名,还需要做一些环境配置:

1
2
3
~/cert/x-securityca $ mkdir newcerts
~/cert/x-securityca $ touch index.txt
~/cert/x-securityca $ echo 01 > serial

两次为同名证书签名的时候,如果报错:

1
2
failed to update database
TXT_DB error number 2

这表示unique_subject选项开启了(见index.txt.attr),你可以在配置里面关闭或者直接:

1
~/cert/x-securityca $ echo "unique_subject = no" > index.txt.attr

角色第三方

权威机构一方已经ready,随时可以帮助第三方机构提供签名服务。现在我们模拟第三方机构请求签名过程。
其中:

1. 生成私钥。
2. 生成csr。

两步骤和“角色权威机构”的做法完全一致,只需要调整 distinguish.userdef 里面的自定义内容。

差别在于第3步,我们还是通过配置模板的方式来实现:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
~/cert $ cat cfg/osign.template
[ ca ]
default_ca = CA_default # The default ca section
[ CA_default ]
dir = %secretRoot% # Where everything is kept 修改ca目录
certs = $dir/certs # Where the issued certs are kept
crl_dir = $dir/crl # Where the issued crl are kept
database = $dir/index.txt # database index file.
unique_subject = no # 一般我们会多次尝试创建相同subject名称的证书,所以关掉unique_subject功能,默认是yes(开启)
new_certs_dir = $dir/newcerts # default place for new certs.
certificate = $dir/ca.crt # The CA certificate 修改crt路径
serial = $dir/serial # The current serial number
crlnumber = $dir/crlnumber # the current crl number
# must be commented out to leave a V1 CRL
crl = $dir/crl.pem # The current CRL
private_key = $dir/private/ca.key # The private key 修改key路径
x509_extensions = v3_req # The extensions to add to the cert
name_opt = ca_default # Subject Name options
cert_opt = ca_default # Certificate field options
default_days = 3650 # how long to certify for 修改默认天数
default_crl_days= 30 # how long before next CRL
default_md = default # use public key default MD
preserve = no # keep passed DN ordering
policy = policy_match
[ policy_match ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[ alt_names ]
#%altnames%

对于上述模板,你需要修改两个地方,其一,将 %secretRoot% 替换为刚才权威机构生成证书和相关辅助文件的所在目录(上例为~/cert/x-securityca);其二,将#%altnames%替换为altnames.userdef文件里面的自定义内容:

1
2
3
4
5
6
~/cert/user1 $ cat altnames.userdef
DNS.1 = plat.x-security.com
DNS.2 = plat1.x-security.com
DNS.3 = google.plat
IP.1 = 192.168.111.10
IP.2 = 200.200.221.186

执行权威机构的签名:

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
26
27
28
~/cert/user1 $ openssl ca -in ca.csr -out ca.crt -config req.conf
Using configuration from req.conf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 4 (0x4)
Validity
Not Before: Dec 14 10:08:26 2018 GMT
Not After : Dec 11 10:08:26 2028 GMT
Subject:
countryName = CN
stateOrProvinceName = Guangdong
organizationName = x-security
commonName = x-security Security intelligence Platform
emailAddress = plat@x-security.com
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Subject Alternative Name:
DNS:plat.x-security.com, DNS:plat1.x-security.com, DNS:google.plat, IP Address:192.168.111.10, IP Address:200.200.221.186
Certificate is to be certified until Dec 11 10:08:26 2028 GMT (3650 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

第三方认证证书导入

与自签名证书导入有一定差异的是,这里操作系统导入的证书必须是“权威机构”的证书,而不是被签名的第三方证书。
表现出来就是,我们需要分发上述步骤中的~/cert/x-securityca/ca.crt到浏览器端,双击ca.crt导入,其导入方法和“Windows证书自签名导入”一节的第5步以后完全一致,略述。

第三方认证证书导入Firefox

Firefox并不是使用的Windows的证书管理,所以,导入了Windows的信任证书,对Firefox并不生效,仍然报错SEC_ERROR_UNKNOWN_ISSUER,需要单独导入:

  1. 输入 about:preferences#privacy
  2. 点击“隐私与安全”->找到“证书”一节->点击“查看证书”
  3. 点击“证书颁发机构”->点击“导入”->勾选两个checkbox->点击“确定”->点击“确定”

附操作图:
firefox import

导入后,Firefox效果如:
firefox other sign show