java - X509 认证路径验证

标签 java certificate x509

在我开发的应用程序中,我检查客户端在传入消息中发送的证书:除了基本证书有效性检查(证书签名、到期日期...)之外,我还检查客户端是否可信。

为此,我创建了一个仅包含受信任证书的 keystore :如果收到的证书不在列表中,我将拒绝传入的消息。 客户端证书位于证书路径中,我检查了所有证书路径。

对于认证路径验证,我使用以下算法: (wikipedia)

1)从客户端证书开始

2) 虽然当前证书不是根证书:

3) 在 keystore 中搜索父证书:为此,我在 keystore 中搜索证书,其中SubjectDN = 当前证书的IssuerDN。如果没有找到,则测试的证书无效

4)使用父证书的公钥检查当前证书的签名

因此,整个路径都已验证

这里是 validator 的完整代码: (感谢this article的作者)

请注意,此处吊销列表 (CRL) 检查已禁用。

 public class CertificateChainValidator {
private static Logger logger = Logger.getLogger(CertificateChainValidator.class);

/**
 * path of the keystore.
 */
private static final String TRUSTED_KEYSTORE = "/trusted.jks";

/**
 * password for opening the keystore.
 */
private static final char[] TRUSTED_KEYSTORE_PWD = new char[] { '$' , '$' , '$','$','$','$','$'};

/**
 * the keystore loaded.
 */
private static KeyStore trustedKeystore;

/**
 * Validate keychain
 *
 * @param client
 *            is the client X509Certificate
 * @param keyStore
 *            containing all trusted certificate
 * @return true if validation until root certificate success, false
 *         otherwise
 * @throws KeyStoreException
 * @throws CertificateException
 * @throws InvalidAlgorithmParameterException
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 */
public static boolean validateKeyChain(final X509Certificate client, final KeyStore keyStore)
        throws KeyStoreException, CertificateException, InvalidAlgorithmParameterException,
        NoSuchAlgorithmException, NoSuchProviderException {
    X509Certificate[] certs = new X509Certificate[keyStore.size()];
    int i = 0;
    Enumeration<String> alias = keyStore.aliases();

    while (alias.hasMoreElements()) {
        certs[i++] = (X509Certificate) keyStore.getCertificate(alias.nextElement());
    }

    return validateKeyChain(client, certs);
}

/**
 * Validate keychain
 *
 * @param client
 *            is the client X509Certificate
 * @param trustedCerts
 *            is Array containing all trusted X509Certificate
 * @return true if validation until root certificate success, false
 *         otherwise
 * @throws CertificateException
 * @throws InvalidAlgorithmParameterException
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 */
@SuppressWarnings("unchecked")
private static boolean validateKeyChain(final X509Certificate client, final X509Certificate... trustedCerts)
        throws CertificateException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
        NoSuchProviderException {
    boolean found = false;
    int i = trustedCerts.length;
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    TrustAnchor anchor;
    @SuppressWarnings("rawtypes")
    Set anchors;
    CertPath path;
    @SuppressWarnings("rawtypes")
    List list;
    PKIXParameters params;
    CertPathValidator validator = CertPathValidator.getInstance("PKIX");

    while (!found && i > 0) {
        anchor = new TrustAnchor(trustedCerts[--i], null);
        anchors = Collections.singleton(anchor);

        list = Arrays.asList(new Certificate[] { client });
        path = cf.generateCertPath(list);

        params = new PKIXParameters(anchors);
        params.setRevocationEnabled(false);

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Test certificate chain : client=%s IssuerDN=%s", client.getIssuerDN(),
                    trustedCerts[i].getSubjectDN()));
        }

        if (client.getIssuerDN().equals(trustedCerts[i].getSubjectDN())) {

            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Validation certificate : IssuerDN=%s", trustedCerts[i].getSubjectDN()));
            }

            try {
                validator.validate(path, params);
                if (isSelfSigned(trustedCerts[i])) {
                    // found root ca
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("certificate root validated : IssuerDN=%s",
                                trustedCerts[i].getSubjectDN()));
                    }
                    found = true;
                } else if (!client.equals(trustedCerts[i])) {
                    // find parent ca
                    if (logger.isDebugEnabled()) {
                        logger.debug(String.format("certificate intermerdiart validated : IssuerDN=%s",
                                trustedCerts[i].getSubjectDN()));
                    }
                    found = validateKeyChain(trustedCerts[i], trustedCerts);
                }
            } catch (CertPathValidatorException e) {
                // validation fail, check next certifiacet in the
                // trustedCerts array
            }
        }
    }

    return found;
}

/**
 *
 * @param cert
 *            is X509Certificate that will be tested
 * @return true if cert is self signed, false otherwise
 * @throws CertificateException
 * @throws NoSuchAlgorithmException
 * @throws NoSuchProviderException
 */
private static boolean isSelfSigned(final X509Certificate cert)
        throws CertificateException, NoSuchAlgorithmException, NoSuchProviderException {
    try {
        PublicKey key = cert.getPublicKey();

        cert.verify(key);
        return true;
    } catch (SignatureException sigEx) {
        return false;
    } catch (InvalidKeyException keyEx) {
        return false;
    }
}

/**
 * donne le trusted keystore (le charge une seule fois).
 *
 * @return
 * @throws CertificateIsabelSignatureVerificationException
 */
private synchronized static KeyStore getTrustedKeystore() throws CertificateIsabelSignatureVerificationException {
    if (trustedKeystore == null) {
        trustedKeystore = loadKeystore(TRUSTED_KEYSTORE, TRUSTED_KEYSTORE_PWD);
    }
    return trustedKeystore;
}

/**
 * chargement du keystore spécifié.
 *
 * @param path
 *            chemin du keystore, dans le classpath.
 * @return le keystore chargé.
 * @throws CertificateIsabelSignatureVerificationException
 */
private static KeyStore loadKeystore(final String path, final char[] passsword)
        throws CertificateIsabelSignatureVerificationException {
    try {

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("loading keystore %s ...", path));
        }

        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load( //
                CertificateChainValidator.class.getResourceAsStream(path) //
                , passsword //
        );

        return ks;
    } catch (Exception ex) {
        throw new CertificateIsabelSignatureVerificationException(
                String.format("Echec lecture du keystore %s", path), ex);
    }
}

/**
 * Valide le certificat client : s'assure qu'il est valide et vérifie qu'il
 * est bien associé à un certifact root connu (dans trusted.jks).
 *
 * @param certEncoded
 * @throws CertificateIsabelSignatureVerificationException
 */
public static void validateClientCertificate(final byte[] certEncoded)
        throws CertificateIsabelSignatureVerificationException {

    try {
        // le keystore contenant tous les certificats reconnus
        KeyStore ks = getTrustedKeystore();

        // récupère le certificat client
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        X509Certificate certificat = (X509Certificate) certFactory
                .generateCertificate(new ByteArrayInputStream(certEncoded));

        if (logger.isDebugEnabled()) {
            logger.debug(String.format("certificate validation : IssuerDN=%s", certificat.getSubjectDN()));
        }

        boolean valid = validateKeyChain(certificat, ks);
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("certificate valid : %s", valid));
        }
        if (!valid) {
            throw new CertificateIsabelSignatureVerificationException("The certificate is not valid");
        }

    } catch (CertificateIsabelSignatureVerificationException ex) {
        throw ex;
    } catch (Exception ex) {
        throw new CertificateIsabelSignatureVerificationException("Error during validation", ex);
    }
}

}

最佳答案

您无法比较颁发者DN,因为任何人都可以使用该字符串创建证书。

每个证书都已使用颁发者的私钥进行了数字签名,因此您需要使用信任库中现有证书的公钥来验证客户端证书的签名。如果存在匹配,则您的证书是“受信任的”,但会继续证书链中的下一个证书。

注意:我没有检查你的代码。您可能想查看建议的链接

关于java - X509 认证路径验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46709158/

相关文章:

python - 如何读取 PEM + PKCS#1 格式的 RSA 公钥

java - 如何计算dat文件中的字符数?

.net - .NET 中的 TLS - 第三方根 CA 未被识别为受信任的根 CA

visual-studio-2013 - 如何从 Window Store 证书签署我的 Windows Store 应用程序?

macos - 在 Mac OS X 中创建 PEM 格式的 google oauth X.509 证书

ssl - 有运行 x509 key 服务器的标准方法吗?

ios - 带有客户端证书的 SecTrustSetAnchorCertificates

java - Spring 值注入(inject)无法从属性中获取值

java - 仅运行 testMethod() 的 JMH 基准测试

java - Netty 服务器如何向不同的客户端发送数据?