Check if Certificate Is Self-Signed or CA-Signed With Java – 使用 Java 检查证书是自签名还是 CA 签名

最后修改: 2023年 8月 29日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

Digital certificates are important in establishing trusted and secure online communication. We often use them to ensure the data exchanged between the client and the web server remains secure.

数字证书对于建立可信和安全的在线通信非常重要。我们经常使用数字证书来确保客户端和网络服务器之间的数据交换安全。

In this tutorial, we’ll explore how to determine in Java whether a given certificate is self-signed or signed by a trusted Certificate Authority (CA).

在本教程中,我们将探讨如何在 Java 中确定给定证书是自签名还是由受信任的证书颁发机构 (CA) 签名。

However, due to the diversity of certificates and security concepts, there is no one-size-fits-all solution. We often need to choose the best approach for our specific context and requirements.

然而,由于证书和安全概念的多样性,没有放之四海而皆准的解决方案。我们往往需要根据具体情况和要求选择最佳方法。

2. Self-Signed vs. CA-Signed Certificate

2.自签名证书与 CA 签名证书

First, let’s examine the differences between self-signed and CA-signed certificates.

首先,我们来看看自签名证书和 CA 签名证书的区别。

Simply put, a self-signed certificate is generated and signed by the same entity. Even though it provides encryption, it doesn’t verify trust by an independent authority. In other words, it doesn’t involve any third-party Certificate Authority (CA).

简而言之,自签名证书是由同一个实体生成和签署的。尽管自签名证书提供加密功能,但它并不验证独立机构的信任。换句话说,它不涉及任何第三方证书颁发机构(CA)。

Consequently, when a user’s web browser encounters a self-signed certificate, it may issue a security warning since the certificate’s authenticity can’t be independently verified.

因此,当用户的网络浏览器遇到自签名证书时,可能会发出安全警告,因为证书的真实性无法独立验证。

We often use them in private networks and for testing purposes.

我们经常将它们用于私人网络和测试目的。

On the other hand, CA-signed certificates are signed by trusted Certificate Authorities. The majority of web browsers and operating systems recognize and accept these CAs.

另一方面,由 CA 签名的证书由可信的证书颁发机构签署。大多数网络浏览器和操作系统都承认并接受这些 CA。

Additionally, the CA-signed certificate proves the entity holding the certificate is the legitimate owner of the domain, helping users trust they’re communicating with the genuine server and not an intermediary.

此外,CA 签发的证书还能证明持有证书的实体是域名的合法所有者,从而帮助用户相信他们是在与真正的服务器而非中介进行通信。

Now, let’s see how to check whether we’re dealing with a self-signed or CA-signed certificate using Java.

现在,让我们看看如何使用 Java 检查我们处理的是自签证书还是 CA 签发的证书。

3. Checking if the Certificate Is Self-Signed

3.检查证书是否自签名

Before we start, let’s generate the certificate we’ll use throughout our examples. The easiest way to generate a self-signed certificate is with the keytool tool:

在开始之前,让我们先生成将在示例中使用的证书。生成自签名证书的最简单方法是使用 keytool 工具:

keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -validity 365 -keysize 2048

Here, we created a self-signed certificate with the selfsigned alias. Furthermore, we stored it inside the keysore.jks keystore.

在这里,我们使用 selfsigned 别名创建了自签名证书。此外,我们还将其存储在 keysore.jks keystore 中。

3.1. Comparing Issuer and Subject Values

3.1.比较发行人和主体价值

As previously mentioned, the same entity generates and signs a self-signed certificate.

如前所述,同一实体生成并签署自签名证书。

The issuer part of the certificate represents the certificate’s signer. The self-signed certificate has the same value for the subject (Issued To) and the issuer (Issued By). To put it differently, to determine whether we’re dealing with a self-signed certificate, we’ll compare its subject and issuer information.

证书的签发人部分代表证书的签名人。自签名证书的主题(Issued To)和签发人(Issued By)具有相同的值。换句话说,要确定我们处理的是否是自签名证书,我们要比较其主题和签发人信息。

Furthermore, Java API provides the java.security.cert.X509Certificate class for dealing with certificates. With this class, we can interact with X.509 certificates and perform various checks and validations.

此外,Java API 还提供了用于处理证书的 java.security.cert.X509Certificate 类。通过该类,我们可以与 X.509 证书交互,并执行各种检查和验证。

Let’s check whether the subject and issuer match. We can achieve this by extracting the relevant fields from the X509Certificate object and checking if they match:

让我们检查一下主题和签发人是否匹配。我们可以从 X509Certificate 对象中提取相关字段,并检查它们是否匹配:

@Test
void whenCertificateIsSelfSigned_thenSubjectIsEqualToIssuer() throws Exception {
    X509Certificate certificate = (X509Certificate) keyStore.getCertificate("selfsigned");
    assertEquals(certificate.getSubjectDN(), certificate.getIssuerDN());
}

3.2. Verifying the Signature

3.2.验证签名

Another way we can check if we’re dealing with a self-signed certificate is to verify it using its own public key.

另一种检查自签名证书的方法是使用自签名证书的公开密钥进行验证。

Let’s check the self-signed certificate using the verify() method:

让我们使用 verify() 方法检查自签名证书:

@Test
void whenCertificateIsSelfSigned_thenItCanBeVerifiedWithItsOwnPublicKey() throws Exception {
    X509Certificate certificate = (X509Certificate) keyStore.getCertificate("selfsigned");
    assertDoesNotThrow(() -> certificate.verify(certificate.getPublicKey()));
}

However, if we pass the CA-signed certificate, the method would throw an exception.

但是,如果我们传递 CA 签名的证书,该方法就会出现异常。

4. Checking if the Certificate Is CA-Signed

4.检查证书是否由 CA 签名

To qualify as a CA-signed, a certificate must be part of a chain of trust leading back to a trusted root CA. Simply put, a certificate chain contains a list of certificates starting from the root certificate and ending with the user’s certificate. Each certificate in the chain signs the next certificate.

要获得 CA 签发的资格,证书必须是一个信任链的一部分,该信任链可追溯到一个受信任的根 CA。简单地说,证书链包含一个证书列表,从根证书开始,到用户证书结束。证书链中的每个证书都会签署下一个证书。

When we talk about the chain of trust, there are different certificate types:

说到信任链,有不同的证书类型:

  • Root Certificate
  • Intermediate Certificate
  • End Entity Certificate

Furthermore, we use the root and intermediate certificates of the hierarchy to issue and verify end entity certificates.

此外,我们还使用层次结构的根证书和中间证书来签发和验证终端实体证书。

For the purposes of this tutorial, we’ll use the certificate obtained from the Baeldung site:

在本教程中,我们将使用从 Baeldung 站点获得的证书

Baeldung certificate

The complexity of checking CA-signed certificates increases if the end entity certificate is part of a certificate chain. In such a scenario, we might need to examine the entire chain to determine if we have a CA-signed certificate. The issuer of the certificate might not be the root CA directly but rather an intermediate CA that signed with the root CA.

如果终端实体证书是证书链的一部分,检查 CA 签发证书的复杂性就会增加。在这种情况下,我们可能需要检查整个证书链,以确定是否有 CA 签发的证书。证书的签发者可能不是直接的根 CA,而是与根 CA 签名的中间 CA。

Now, if we examine the certificate hierarchy, we can see the certificate is part of a certificate chain:

现在,如果我们检查证书层次结构,就会发现证书是证书链的一部分:

Baeldung Certificate Hierarchy

  • Baltimore CyberTrust Root – Root CA
  • Cloudflare Inc ECC CA-3 – Intermediate CA
  • sni.cloudflaressl.com – End Entity (used on the Baeldung site)

4.1. Using Truststore

4.1.使用 Truststore

We can create our own truststore to check if one of the certificates we trust signed the end entity certificate.

我们可以创建自己的 truststore 来检查我们信任的证书之一是否签署了终端实体证书。

When setting up a truststore, we typically include the root CA certificates as well as any intermediate CA certificates required to build the chain of trust. This way, our application can effectively validate certificates presented by other parties.

在建立信任库时,我们通常会包含根 CA 证书以及建立信任链所需的任何中间 CA 证书。这样,我们的应用程序就能有效地验证其他方提交的证书。

An advantage of using a truststore is the ability to decide which CA certificates we trust and which we don’t.

使用信任库的一个好处是,我们可以决定信任哪些 CA 证书,不信任哪些。

From our example, the Baltimore CyberTrust Root certificate signed the intermediate Cloudflare certificate, which signed our end entity certificate.

在我们的示例中,巴尔的摩网络信任根证书签署了中间 Cloudflare 证书,该证书签署了我们的终端实体证书。

Now, let’s add both to our truststore:

现在,让我们把两者都添加到信任存储中:

keytool -importcert -file cloudflare.cer -keystore truststore.jks -alias cloudflare
keytool -importcert -file root.cer -keystore truststore.jks -alias root

Next, to check if we trust the given end entity certificate, we need to find a way to get the root certificate. Let’s create the getRootCertificate() method that searches for a root certificate:

接下来,为了检查我们是否信任给定的终端实体证书,我们需要找到获取根证书的方法。让我们创建getRootCertificate()方法来搜索根证书:

public static X509Certificate getRootCertificate(X509Certificate endEntityCertificate, KeyStore trustStore)
        throws Exception {
    X509Certificate issuerCertificate = findIssuerCertificate(endEntityCertificate, trustStore);
    if (issuerCertificate != null) {
        if (isRoot(issuerCertificate)) {
            return issuerCertificate;
        } else {
            return getRootCertificate(issuerCertificate, trustStore);
        }
    }
    return null;
}

private static boolean isRoot(X509Certificate certificate) {
    try {
        certificate.verify(certificate.getPublicKey());
        return certificate.getKeyUsage() != null && certificate.getKeyUsage()[5];
    } catch (Exception e) {
        return false;
    }
}

First, we attempt to locate the issuer of a provided certificate within the trust store:

首先,我们尝试在信任存储中查找所提供证书的签发者:

static X509Certificate findIssuerCertificate(X509Certificate certificate, KeyStore trustStore)
        throws KeyStoreException {
    Enumeration<String> aliases = trustStore.aliases();
    while (aliases.hasMoreElements()) {
        String alias = aliases.nextElement();
        Certificate cert = trustStore.getCertificate(alias);
        if (cert instanceof X509Certificate) {
            X509Certificate x509Cert = (X509Certificate) cert;
            if (x509Cert.getSubjectX500Principal().equals(certificate.getIssuerX500Principal())) {
                return x509Cert;
            }
        }
    }
    return null;
}

Then, if a match is discovered, we proceed to verify whether the certificate is a self-signed CA certificate. In the event of successful verification, we reach the root certificate. If not, we continue our search.

然后,如果发现匹配,我们就继续验证该证书是否为自签 CA 证书。如果验证成功,我们就获得了根证书。否则,我们继续搜索。

Finally, let’s test our method to check whether it works properly:

最后,让我们测试一下我们的方法,看看它是否能正常工作:

@Test
void whenCertificateIsCASigned_thenRootCanBeFoundInTruststore() throws Exception {
    X509Certificate endEntityCertificate = (X509Certificate) keyStore.getCertificate("baeldung");
    X509Certificate rootCertificate = getRootCertificate(endEntityCertificate, trustStore);
    assertNotNull(rootCertificate);
}

If we perform the same test using the self-signed certificate, we won’t get the root since our trust store doesn’t contain it.

如果我们使用自签名证书进行同样的测试,我们将无法获得根证书,因为我们的信任存储中不包含它。

5. Checking if the Certificate Is a CA Certificate

5.检查证书是否为 CA 证书

In cases when we’re dealing only with root or intermediate certificates, we may need to perform additional checks.

在只处理根证书或中间证书的情况下,我们可能需要执行额外的检查。

It’s important to note a root certificate is also a self-signed certificate. However, the difference between the root and the user’s self-signed certificate is the former will have the keyCertSign flag enabled (since we can use it to sign other certificates).

需要注意的是,根证书也是自签名证书不过,根证书和用户自签名证书的区别在于,前者将启用keyCertSign标志(因为我们可以用它来签署其他证书)。

We can identify root or intermediate certificates by checking the key usage:

我们可以通过检查密钥使用情况来识别根证书或中间证书:

@Test
void whenCertificateIsCA_thenItCanBeUsedToSignOtherCertificates() throws Exception {
    X509Certificate certificate = (X509Certificate) keyStore.getCertificate("cloudflare");
    assertTrue(certificate.getKeyUsage()[5]);
}

Moreover, one of the checks we can perform is checking basic constraints extension.

此外,我们可以进行的检查之一是检查基本约束扩展。

The Basic Constraints extension is a field in X.509 certificates that provides information about the certificate’s intended usage and whether it represents a Certificate Authority (CA) or an end entity.

基本约束扩展是 X.509 证书中的一个字段,提供有关证书预期用途的信息,以及证书是代表证书颁发机构(CA)还是终端实体。

If the basic constraints extension doesn’t exist, the getBasicConstraints() method returns -1:

如果基本约束扩展不存在,getBasicConstraints() 方法将返回-1:

@Test
void whenCertificateIsCA_thenBasicConstrainsReturnsZeroOrGreaterThanZero() throws Exception {
    X509Certificate certificate = (X509Certificate) keyStore.getCertificate("cloudflare");
    assertNotEquals(-1, certificate.getBasicConstraints());
}

6. Conclusion

6.结论

In this article, we learned how to check whether a certificate is self-signed or CA-signed.

在本文中,我们学习了如何检查证书是自签名还是 CA 签名。

To sum up, self-signed certificates have the same subject and issuer components, and additionally, they can be verified using their own public key.

总之,自签名证书具有相同的主体和签发者组件,而且可以使用自己的公开密钥进行验证。

On the other hand, CA-signed certificates are usually part of the certificate chain. To validate them, we need to create a trust store that contains the trusted root and intermediate certificates and check if the root of the end entity certificate matches one of the trusted certificates.

另一方面,由 CA 签发的证书通常是证书链的一部分。要验证它们,我们需要创建一个包含受信任的根证书和中间证书的信任存储,并检查终端实体证书的根是否与其中一个受信任的证书相匹配。

Finally, if we’re working with root or intermediate certificates, we can identify them by checking whether they’re used for signing other certificates or not.

最后,如果我们使用的是根证书或中间证书,我们可以通过检查它们是否用于签署其他证书来识别它们。

As always, the entire code examples can be found over on GitHub.

一如既往,您可以在 GitHub 上找到 完整的代码示例。