Ich schreibe eine Anwendung in Java, die über HTTPS eine Verbindung zu zwei Webservern herstellt. Einer hat ein Zertifikat erhalten, dem über die Standard-Vertrauenskette vertraut wird, der andere verwendet ein selbstsigniertes Zertifikat. Die Verbindung zum ersten Server funktionierte natürlich sofort, während die Verbindung zum Server mit dem selbstsignierten Zertifikat erst funktionierte, als ich einen TrustStore mit dem Zertifikat von diesem Server erstellte. Die Verbindung zum standardmäßig vertrauenswürdigen Server funktioniert jedoch nicht mehr, da anscheinend der standardmäßige TrustStore ignoriert wird, sobald ich meinen eigenen erstellt habe.
Eine Lösung, die ich gefunden habe, bestand darin, die Zertifikate aus dem Standard-TrustStore zu meinen eigenen hinzuzufügen. Diese Lösung gefällt mir jedoch nicht, da ich diesen TrustStore weiterhin verwalten muss. (Ich kann nicht davon ausgehen, dass diese Zertifikate auf absehbare Zeit statisch bleiben, oder?)
Abgesehen davon habe ich zwei 5 Jahre alte Threads mit einem ähnlichen Problem gefunden:
Registrieren mehrerer Schlüsselspeicher in JVM
Wie kann ich mehrere SSL-Zertifikate für einen Java-Server haben?
Beide gehen tief in die Java SSL-Infrastruktur ein. Ich hatte gehofft, dass es inzwischen eine bequemere Lösung gibt, die ich in einer Sicherheitsüberprüfung meines Codes leicht erklären kann.
quelle
Antworten:
Sie könnten ein ähnliches Muster verwenden wie in einer früheren Antwort (für ein anderes Problem).
Besorgen Sie sich im Wesentlichen den Standard-Vertrauensmanager und erstellen Sie einen zweiten Vertrauensmanager, der Ihren eigenen Vertrauensspeicher verwendet. Schließen Sie beide in eine benutzerdefinierte Trust Manager-Implementierung ein, die den Aufruf an beide delegiert (bei einem Ausfall auf den anderen zurückgreifen).
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);
Sie müssen diesen Kontext nicht als Standardkontext festlegen. Wie Sie es verwenden, hängt von der Client-Bibliothek ab, die Sie verwenden (und woher es seine Socket-Fabriken bezieht).
Grundsätzlich müssten Sie den Truststore jedoch ohnehin immer nach Bedarf aktualisieren. Das Java 7 JSSE-Referenzhandbuch enthielt einen "wichtigen Hinweis", der jetzt in Version 8 desselben Handbuchs auf nur einen "Hinweis" herabgestuft wurde :
quelle
Sie können den Standard-Vertrauensspeicher abrufen, indem Sie aufrufen
TrustManagerFactory.init((KeyStore)null)
und dessenX509Certificate
s abrufen . Kombinieren Sie dies mit Ihrem eigenen Zertifikat. Sie können entweder das selbstsignierte Zertifikat aus einer.jks
oder einer.p12
Datei mitKeyStore.load
laden.crt
oder eine (oder.cer
) Datei über ladenCertificateFactory
.Hier ist ein Demo-Code, der den Punkt veranschaulicht. Sie können den Code ausführen, wenn Sie das Zertifikat von stackoverflow.com mit Ihrem Browser herunterladen. Wenn Sie das Hinzufügen des geladenen Zertifikats und des Standardzertifikats auskommentieren
SSLHandshakeException
, erhält der Code ein . Wenn Sie eines der beiden beibehalten, wird der Statuscode 200 zurückgegeben.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); } } } } }
quelle
load(null, null)
. Obwohl es seltsam aussieht, ist es wichtig, da es den Keystore initialisiert 🤓Vielleicht bin ich 6 Jahre zu spät, um diese Frage zu beantworten, aber es könnte vielleicht auch für andere Entwickler hilfreich sein. Ich hatte auch die gleiche Herausforderung, den Standard-Truststore und meinen eigenen benutzerdefinierten Truststore zu laden. Nachdem ich dieselbe benutzerdefinierte Lösung für mehrere Projekte verwendet hatte, hielt ich es für praktisch, eine Bibliothek zu erstellen und sie auch öffentlich verfügbar zu machen, um einen Beitrag zur Community zu leisten. Bitte schauen Sie hier: Github - SSLContext-Kickstart
Verwendung:
import nl.altindag.sslcontext.SSLFactory; import javax.net.ssl.SSLContext; import java.security.cert.X509Certificate; import java.util.List; public class App { public static void main(String[] args) { String trustStorePath = ...; char[] password = "password".toCharArray(); SSLFactory sslFactory = SSLFactory.builder() .withDefaultTrustMaterial() .withTrustMaterial(trustStorePath, password) .build(); SSLContext sslContext = sslFactory.getSslContext(); List<X509Certificate> trustedCertificates = sslFactory.getTrustedCertificates(); } }
Ich war mir nicht ganz sicher, ob ich dies hier posten sollte, da es auch als ein Weg gesehen werden könnte, "meine Bibliothek" zu bewerben, aber ich dachte, es könnte für Entwickler hilfreich sein, die die gleichen Herausforderungen haben.
quelle
Hier ist eine sauberere Version von Brunos Antwort
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); }
quelle
Wie ich herausgefunden habe, können Sie auch eine
SSLContextBuilder
Klasse aus der Apache HttpComponents-Bibliothek verwenden, um Ihren benutzerdefinierten Schlüsselspeicher zu einem hinzuzufügenSSLContext
: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();
quelle
SSLContextBuilder#loadTrustMaterial
kann verwendet werden, um nur einen Truststore zu laden. Die andere Methode istSSLContextBuilder#loadKeyMaterial
nicht für den Truststore, sondern für den Keystore, der einem anderen Zweck dient. Ist es nicht?loadTrustMaterial
wird. Zumindest in der Java11-Implementierung wird die Liste durch die angegebene Liste ersetzt. Wenn Sie dekompilierensun.security.provider.JavaKeyStore#engineLoad
, werden Siethis.entries.clear();
dort sehen.