为什么无法将使用 keytool 以某种方式生成的 public 密钥证书导入 Firefox?

Why can't I import a public key certificate into Firefox that is generated using keytool in a certain way?

我正在尝试为 CA2 生成证书,以便:

  1. 有一个名为 CA0 的根 CA。
  2. 有一个称为 CA1 的中间 CA。
  3. 还有一个称为 CA2 的中间 CA。
  4. CA0 签署 CA1 的证书。
  5. CA1 签署 CA2 的证书。

我使用各种方法使用 keytool 生成 CA2。

方法一:CA0对CA1进行签名并写入文件; CA1 签署 CA2 并写入文件; CA0 从密钥库导出到文件

# Start afresh
rm -f foo.jks
rm -f *.cer

# Generate self-signed CA0 (root), CA1 (intermediate) and CA2 (another intermediate).
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca0 -keypass kpass0 -dname CN=CA0 -ext bc=ca:true
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 -dname CN=CA1
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2 -dname CN=CA2

# CA0 signs CA1.
keytool -certreq -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 |
keytool -gencert -keystore foo.jks -storepass stpass -alias ca0 -keypass kpass0 -ext bc=ca:true -outfile ca1.cer

# CA1 signs CA2.
keytool -certreq -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2 |
keytool -gencert -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 -ext bc=ca:true -outfile ca2.cer

# Export CA0
keytool -export -keystore foo.jks -storepass stpass -alias ca0 -file ca0.cer

当我打开 Firefox 并转到“首选项”>“高级”>“查看证书”>“颁发机构”时,单击“导入”并逐个导入 ca0.cer、ca1.cer 和 ca2.cer,它们可以正常导入.然后,如果我 select CA2 并单击“查看”>“详细信息”,我可以在“证书层次结构”窗格中看到完整的证书链。这一切都很好。

方法二:CA0对CA1进行签名,导入keystore; CA1对CA2进行签名并导入keystore; CA0、CA1 和 CA2 从密钥库导出到文件

# Start afresh
rm -f foo.jks
rm -f *.cer

# Generate self-signed CA0 (root), CA1 (intermediate) and CA2 (another intermediate).
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca0 -keypass kpass0 -dname CN=CA0 -ext bc=ca:true
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 -dname CN=CA1
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2 -dname CN=CA2

# CA0 signs CA1.
keytool -certreq -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 |
keytool -gencert -keystore foo.jks -storepass stpass -alias ca0 -keypass kpass0 -ext bc=ca:true |
keytool -importcert -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1

# CA1 signs CA2.
keytool -certreq -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2 |
keytool -gencert -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 -ext bc=ca:true |
keytool -importcert -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2

# Export CA0, CA1 and CA2
keytool -export -keystore foo.jks -storepass stpass -alias ca0 -file ca0.cer
keytool -export -keystore foo.jks -storepass stpass -alias ca1 -file ca1.cer
keytool -export -keystore foo.jks -storepass stpass -alias ca1 -file ca2.cer

同样,我可以在 Firefox 中将 ca0.cer、ca1.cer 和 ca2.cer 导入 Authorities。

方法三:CA0对CA1进行签名,导入keystore; CA1 签名和 CA2 并导出到文件; CA0 和 CA1 从密钥库导出到文件

# Start afresh
rm -f foo.jks
rm -f *.cer

# Generate self-signed CA0 (root), CA1 (intermediate) and CA2 (another intermediate).
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca0 -keypass kpass0 -dname CN=CA0 -ext bc=ca:true
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 -dname CN=CA1
keytool -genkeypair -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2 -dname CN=CA2

# CA0 signs CA1.
keytool -certreq -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 |
keytool -gencert -keystore foo.jks -storepass stpass -alias ca0 -keypass kpass0 -ext bc=ca:true |
keytool -importcert -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1

# CA1 signs CA2.
keytool -certreq -keystore foo.jks -storepass stpass -alias ca2 -keypass kpass2 |
keytool -gencert -keystore foo.jks -storepass stpass -alias ca1 -keypass kpass1 -ext bc=ca:true -outfile ca2.cer

# Export CA0 and CA1
keytool -export -keystore foo.jks -storepass stpass -alias ca0 -file ca0.cer
keytool -export -keystore foo.jks -storepass stpass -alias ca1 -file ca1.cer

这次我可以将 ca0.cer 和 ca1.cer 导入到 Firefox 的 Authorities 中,但我无法导入 ca2.cer。当我在 'Select File Containing CA certificate(s) to import' 对话框中 select ca2.cer 并单击打开时,根本没有任何反应。对话框消失,证书未出现在“权限”窗格中。

keytool -export 仅将链中的第一个证书写入 -outfile,请参阅 keytool manual:

If alias refers to a trusted certificate, that certificate is output. Otherwise, alias refers to a key entry with an associated certificate chain. In that case, the first certificate in the chain is returned.

keytool -gencert 整个链 写入 -outfile。可以看到在命令中加上-rfc(PEM格式输出):

-----BEGIN CERTIFICATE-----
MIICqDCCAmagAwIBAgIEHhRohzALBgcqhkjOOAQDBQAwDjEMMAoGA1UEAxMDQ0ExMB4XDTE2MDYw
...
hkjOOAQDBQADLwAwLAIUfkhluVSKCpemYFYfKf2KfT7UQaACFFA8SLiKbfOo6xh5e01S1YXJhM/P
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICqDCCAmagAwIBAgIEZgEJrjALBgcqhkjOOAQDBQAwDjEMMAoGA1UEAxMDQ0EwMB4XDTE2MDYw
...
hkjOOAQDBQADLwAwLAIUd2DS+rPrJqlGwziqenDdVaYQWaoCFHleJS/5XfDk+GaEMSUw53gQ0vd7
-----END CERTIFICATE-----

因此,ca2.cer 包含两个简单连接的 DER 格式的证书(CA1 和 CA2)。 Firefox 无法处理这一点不足为奇。

我认为没有任何标准允许串联 DER 证书。 PKCS#7 将是证书链的常用二进制格式。串联的 PEM 文件也很常见,但不是 DER。

keytool documentation 没有说明将链写入文件。事实上,它说 "the X.509 certificate":

The command reads the request from infile (if omitted, from the standard input), signs it using alias's private key, and outputs the X.509 certificate into outfile (if omitted, to the standard output).

查看 sources of keytool,它将生成的证书写入文件和链 - 不包括根:

dumpCert(cert, out);
for (Certificate ca: keyStore.getCertificateChain(alias)) {
    if (ca instanceof X509Certificate) {
        X509Certificate xca = (X509Certificate)ca;
        if (!isSelfSigned(xca)) {
            dumpCert(xca, out);
        }
     }
}

不包括根证书,因为处理端无论如何都会验证链直至信任锚(根 CA)(与 SSL 链验证的概念相同)。