ssl 在Java中使用自定义信任库以及默认信任库[重复]

mlnl4t2r  于 2023-08-06  发布在  Java
关注(0)|答案(5)|浏览(135)

此问题在此处已有答案

How can I use different certificates on specific connections?(5个答案)
去年关闭。
截至6天前,社区正在审查是否重新讨论这个问题。
我正在用Java编写一个应用程序,它通过HTTPS连接到两个Web服务器。一个通过默认的信任链获得受信任的证书,另一个使用自签名证书。当然,连接到第一台服务器是开箱即用的,而连接到使用自签名证书的服务器是不起作用的,直到我使用来自该服务器的证书创建了一个trustStore。但是,到默认受信任服务器的连接不再起作用,因为显然,一旦我创建了自己的信任存储,默认的信任存储就会被忽略。
我发现的一个解决方案是将默认trustStore中的证书添加到我自己的证书中。但是,我不喜欢这个解决方案,因为它需要我继续管理那个trustStore。(我不能假设这些证书在可预见的未来保持不变,对吗?)
除此之外,我发现两个5岁的线程有类似的问题:
Registering multiple keystores in JVM
How can I have multiple SSL certificates for a Java server
它们都深入到Java SSL基础设施中。我希望现在有一个更方便的解决方案,我可以在代码的安全审查中轻松解释。

r1zk6ea1

r1zk6ea11#

您可以使用与我在previous answer中提到的模式类似的模式(针对不同的问题)。
实际上,获取默认信任管理器,创建使用您自己的信任存储的第二个信任管理器。将它们都 Package 在一个自定义信任管理器实现中,该实现将调用委托给这两个对象(当其中一个失败时,回退到另一个对象)。

TrustManagerFactory tmf = TrustManagerFactory
    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
// Using null here initialises the TMF with the default trust store.
tmf.init((KeyStore) null);

// Get hold of the default trust manager
X509TrustManager defaultTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
    if (tm instanceof X509TrustManager) {
        defaultTm = (X509TrustManager) tm;
        break;
    }
}

FileInputStream myKeys = new FileInputStream("truststore.jks");

// Do the same with your trust store this time
// Adapt how you load the keystore to your needs
KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
myTrustStore.load(myKeys, "password".toCharArray());

myKeys.close();

tmf = TrustManagerFactory
    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(myTrustStore);

// Get hold of the default trust manager
X509TrustManager myTm = null;
for (TrustManager tm : tmf.getTrustManagers()) {
    if (tm instanceof X509TrustManager) {
        myTm = (X509TrustManager) tm;
        break;
    }
}

// Wrap it in your own class.
final X509TrustManager finalDefaultTm = defaultTm;
final X509TrustManager finalMyTm = myTm;
X509TrustManager customTm = new X509TrustManager() {
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        // If you're planning to use client-cert auth,
        // merge results from "defaultTm" and "myTm".
        return finalDefaultTm.getAcceptedIssuers();
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain,
            String authType) throws CertificateException {
        try {
            finalMyTm.checkServerTrusted(chain, authType);
        } catch (CertificateException e) {
            // This will throw another CertificateException if this fails too.
            finalDefaultTm.checkServerTrusted(chain, authType);
        }
    }

    @Override
    public void checkClientTrusted(X509Certificate[] chain,
            String authType) throws CertificateException {
        // If you're planning to use client-cert auth,
        // do the same as checking the server.
        finalDefaultTm.checkClientTrusted(chain, authType);
    }
};

SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { customTm }, null);

// You don't have to set this as the default context,
// it depends on the library you're using.
SSLContext.setDefault(sslContext);

字符串
您不必将该上下文设置为默认上下文。如何使用它取决于您正在使用的客户端库(以及它从何处获取其套接字工厂)。
也就是说,原则上,无论如何都必须根据需要更新信任库。Java 7 JSSE参考指南对此有一个“重要说明”,现在在同一指南的版本8中降级为“说明”:
JDK在java-home/lib/security/cacerts文件中附带了有限数量的受信任根证书。如keytool参考页中所述,如果您将此文件用作信任库,则您有责任维护(即添加和删除)此文件中包含的证书。
根据您联系的服务器的证书配置,您可能需要添加其他根证书。从相应的供应商处获取所需的特定根证书。

4dc9hkyq

4dc9hkyq2#

也许我晚了6年才回答这个问题,但它可能对其他开发人员也有帮助。我还遇到了加载默认信任库和我自己的自定义信任库的相同挑战。在多个项目中使用相同的定制解决方案之后,我认为创建一个库并将其公开以回馈社区会很方便。请看这里:Github - SSLContext-Kickstart
使用方法:

import nl.altindag.ssl.SSLFactory;

import javax.net.ssl.SSLContext;
import java.security.cert.X509Certificate;
import java.nio.file.Path;
import java.util.List;

public class App {

    public static void main(String[] args) {
        Path trustStorePath = ...;
        char[] password = "password".toCharArray();

        SSLFactory sslFactory = SSLFactory.builder()
                .withDefaultTrustMaterial() // JDK trusted CA's
                .withSystemTrustMaterial()  // OS trusted CA's
                .withTrustMaterial(trustStorePath, password)
                .build();

        SSLContext sslContext = sslFactory.getSslContext();
        List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates();
    }

}

字符串
我不太确定我是否应该在这里发布这篇文章,因为它也可以被视为推广“我的库”的一种方式,但我认为它对那些有同样挑战的开发人员可能会有帮助。
您可以使用以下代码段添加依赖项:

<dependency>
    <groupId>io.github.hakky54</groupId>
    <artifactId>sslcontext-kickstart</artifactId>
    <version>8.1.5</version>
</dependency>

h5qlskok

h5qlskok3#

可以通过调用TrustManagerFactory.init((KeyStore)null)检索默认信任存储区并获取其X509Certificate。将其与您自己的证书结合使用。您可以使用KeyStore.load.jks.p12文件加载自签名证书,也可以通过CertificateFactory加载.crt(或.cer)文件。
下面是一些演示代码来说明这一点。如果使用浏览器从www.example.com下载证书,则可以运行代码stackoverflow.com。如果注解掉添加加载的证书和默认证书,代码将得到SSLHandshakeException,但如果保留其中任何一个,它将返回状态代码200。

import javax.net.ssl.*;
import java.io.*;
import java.net.URL;
import java.security.*;
import java.security.cert.*;

public class HttpsWithCustomCertificateDemo {
    public static void main(String[] args) throws Exception {
        // Create a new trust store, use getDefaultType for .jks files or "pkcs12" for .p12 files
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        // Create a new trust store, use getDefaultType for .jks files or "pkcs12" for .p12 files
        trustStore.load(null, null);

        // If you comment out the following, the request will fail
        trustStore.setCertificateEntry(
                "stackoverflow",
                // To test, download the certificate from stackoverflow.com with your browser
                loadCertificate(new File("stackoverflow.crt"))
        );
        // Uncomment to following to add the installed certificates to the keystore as well
        //addDefaultRootCaCertificates(trustStore);

        SSLSocketFactory sslSocketFactory = createSslSocketFactory(trustStore);

        URL url = new URL("https://stackoverflow.com/");
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        // Alternatively, to use the sslSocketFactory for all Http requests, uncomment
        //HttpsURLConnection.setDefaultSSLSocketFactory(sslSocketFactory);
        conn.setSSLSocketFactory(sslSocketFactory);
        System.out.println(conn.getResponseCode());
    }

    private static SSLSocketFactory createSslSocketFactory(KeyStore trustStore) throws GeneralSecurityException {
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);
        TrustManager[] trustManagers = tmf.getTrustManagers();

        SSLContext sslContext = SSLContext.getInstance("SSL");
        sslContext.init(null, trustManagers, null);
        return sslContext.getSocketFactory();
    }

    private static X509Certificate loadCertificate(File certificateFile) throws IOException, CertificateException {
        try (FileInputStream inputStream = new FileInputStream(certificateFile)) {
            return (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(inputStream);
        }
    }

    private static void addDefaultRootCaCertificates(KeyStore trustStore) throws GeneralSecurityException {
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
        // Loads default Root CA certificates (generally, from JAVA_HOME/lib/cacerts)
        trustManagerFactory.init((KeyStore)null);
        for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
            if (trustManager instanceof X509TrustManager) {
                for (X509Certificate acceptedIssuer : ((X509TrustManager) trustManager).getAcceptedIssuers()) {
                    trustStore.setCertificateEntry(acceptedIssuer.getSubjectDN().getName(), acceptedIssuer);
                }
            }
        }
    }
}

字符串

odopli94

odopli944#

下面是Bruno's answer的更简洁版本

public void configureTrustStore() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException,
        CertificateException, IOException {
    X509TrustManager jreTrustManager = getJreTrustManager();
    X509TrustManager myTrustManager = getMyTrustManager();

    X509TrustManager mergedTrustManager = createMergedTrustManager(jreTrustManager, myTrustManager);
    setSystemTrustManager(mergedTrustManager);
}

private X509TrustManager getJreTrustManager() throws NoSuchAlgorithmException, KeyStoreException {
    return findDefaultTrustManager(null);
}

private X509TrustManager getMyTrustManager() throws FileNotFoundException, KeyStoreException, IOException,
        NoSuchAlgorithmException, CertificateException {
    // Adapt to load your keystore
    try (FileInputStream myKeys = new FileInputStream("truststore.jks")) {
        KeyStore myTrustStore = KeyStore.getInstance("jks");
        myTrustStore.load(myKeys, "password".toCharArray());

        return findDefaultTrustManager(myTrustStore);
    }
}

private X509TrustManager findDefaultTrustManager(KeyStore keyStore)
        throws NoSuchAlgorithmException, KeyStoreException {
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(keyStore); // If keyStore is null, tmf will be initialized with the default trust store

    for (TrustManager tm : tmf.getTrustManagers()) {
        if (tm instanceof X509TrustManager) {
            return (X509TrustManager) tm;
        }
    }
    return null;
}

private X509TrustManager createMergedTrustManager(X509TrustManager jreTrustManager,
        X509TrustManager customTrustManager) {
    return new X509TrustManager() {
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            // If you're planning to use client-cert auth,
            // merge results from "defaultTm" and "myTm".
            return jreTrustManager.getAcceptedIssuers();
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            try {
                customTrustManager.checkServerTrusted(chain, authType);
            } catch (CertificateException e) {
                // This will throw another CertificateException if this fails too.
                jreTrustManager.checkServerTrusted(chain, authType);
            }
        }

        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            // If you're planning to use client-cert auth,
            // do the same as checking the server.
            jreTrustManager.checkClientTrusted(chain, authType);
        }

    };
}

private void setSystemTrustManager(X509TrustManager mergedTrustManager)
        throws NoSuchAlgorithmException, KeyManagementException {
    SSLContext sslContext = SSLContext.getInstance("TLS");
    sslContext.init(null, new TrustManager[] { mergedTrustManager }, null);

    // You don't have to set this as the default context,
    // it depends on the library you're using.
    SSLContext.setDefault(sslContext);
}

字符串

xdyibdwo

xdyibdwo5#

正如我所理解的,您还可以使用Apache HttpComponents库中的SSLContextBuilder类将您的自定义密钥库添加到SSLContext

SSLContextBuilder builder = new SSLContextBuilder();
try {
     keyStore.load(null, null);
     builder.loadTrustMaterial(keyStore, null);
     builder.loadKeyMaterial(keyStore, null);
} catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException
          | UnrecoverableKeyException e) {
     log.error("Can not load keys from keystore '{}'", keyStore.getProvider(), e);
}
return builder.build();

字符串

相关问题