From 87b442bfc514c10d50cb515a92b31d54c3bb198a Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 31 Jan 2014 15:45:42 +0400 Subject: [PATCH] Always ask about untrusted certificates, in particular when default trust store is missing on Mac --- .../util/net/ssl/CertificateUtil.java | 13 +++ .../net/ssl/CertificateWarningDialog.java | 6 +- .../util/net/ssl/CertificatesManager.java | 4 +- .../util/net/ssl/ClientOnlyTrustManager.java | 15 +++ .../util/net/ssl/ConfirmingTrustManager.java | 96 ++++++++----------- .../actions/ShowCertificateInfoAction.java | 2 +- 6 files changed, 74 insertions(+), 62 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/util/net/ssl/ClientOnlyTrustManager.java diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateUtil.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateUtil.java index 886e456db6b5..c02a9382a721 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateUtil.java +++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateUtil.java @@ -2,6 +2,7 @@ package com.intellij.util.net.ssl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.io.StreamUtil; +import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -28,6 +29,18 @@ public class CertificateUtil { } } + // Standard Names + // See complete reference at http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html + // certificate format + @Nls public static final String X509 = "X.509"; + // Java Key Store - standard type of keystores used by keytool utility + @Nls public static final String JKS = "JKS"; + // another standard type of keystore + @Nls public static final String PKCS12 = "PKCS12"; + // type of trust manager factory + @Nls public static final String PKIX = "PKIX"; + @Nls public static final String TLS = "TLS"; + /** * Utility class */ diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java index 4a7e4af67de0..c07516cfd4c9 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java +++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificateWarningDialog.java @@ -20,10 +20,10 @@ import java.security.cert.X509Certificate; public class CertificateWarningDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance(CertificateWarningDialog.class); - public static CertificateWarningDialog createSelfSignedCertificateWarning(@NotNull X509Certificate certificate) { + public static CertificateWarningDialog createUntrustedCertificateWarning(@NotNull X509Certificate certificate) { return new CertificateWarningDialog(certificate, - "Self-signed Server's Certificate", - "Server's certificate is untrusted and appears to be self-signed"); + "Untrusted Server's Certificate", + "Server's certificate is not trusted"); } public static CertificateWarningDialog createExpiredCertificateWarning(@NotNull X509Certificate certificate) { diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/CertificatesManager.java b/platform/platform-api/src/com/intellij/util/net/ssl/CertificatesManager.java index 667e1ec1dd58..bc1f52629de6 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/CertificatesManager.java +++ b/platform/platform-api/src/com/intellij/util/net/ssl/CertificatesManager.java @@ -165,11 +165,11 @@ public class CertificatesManager implements ApplicationComponent, PersistentStat // default context with can't be initialized twice try { // actually TLSv1 support is mandatory for Java platform - return SSLContext.getInstance("TLS"); + return SSLContext.getInstance(CertificateUtil.TLS); } catch (NoSuchAlgorithmException e) { LOG.error(e); - throw new IllegalStateException("Can't get system SSL context"); + throw new AssertionError("Can't get system SSL context"); } } diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/ClientOnlyTrustManager.java b/platform/platform-api/src/com/intellij/util/net/ssl/ClientOnlyTrustManager.java new file mode 100644 index 000000000000..79ecb8862baf --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/net/ssl/ClientOnlyTrustManager.java @@ -0,0 +1,15 @@ +package com.intellij.util.net.ssl; + +import javax.net.ssl.X509TrustManager; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** +* @author Mikhail Golubev +*/ +public abstract class ClientOnlyTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] certificates, String s) throws CertificateException { + throw new UnsupportedOperationException("Should not be called by client"); + } +} diff --git a/platform/platform-api/src/com/intellij/util/net/ssl/ConfirmingTrustManager.java b/platform/platform-api/src/com/intellij/util/net/ssl/ConfirmingTrustManager.java index 3352d850fb31..0b8a963da112 100644 --- a/platform/platform-api/src/com/intellij/util/net/ssl/ConfirmingTrustManager.java +++ b/platform/platform-api/src/com/intellij/util/net/ssl/ConfirmingTrustManager.java @@ -1,16 +1,13 @@ package com.intellij.util.net.ssl; -import com.intellij.notification.Notification; -import com.intellij.notification.NotificationType; -import com.intellij.notification.Notifications; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.StreamUtil; +import com.intellij.util.ArrayUtil; import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,36 +31,46 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * Special kind of trust manager, that asks user to confirm untrusted certificate, e.g. if - * it wasn't found in system-wide storage. + * The central piece of our SSL support - special kind of trust manager, that asks user to confirm + * untrusted certificate, e.g. if it wasn't found in system-wide storage. * * @author Mikhail Golubev */ -class ConfirmingTrustManager implements X509TrustManager { +public class ConfirmingTrustManager extends ClientOnlyTrustManager { private static final Logger LOG = Logger.getInstance(ConfirmingTrustManager.class); private static final X509Certificate[] NO_CERTIFICATES = new X509Certificate[0]; + private static final X509TrustManager MISSING_TRUST_MANAGER = new ClientOnlyTrustManager() { + @Override + public void checkServerTrusted(X509Certificate[] certificates, String s) throws CertificateException { + LOG.debug("Trust manager is missing. Retreating."); + throw new CertificateException("Missing trust manager"); + } - // Errors - @NonNls public static final String ERR_EMPTY_TRUST_ANCHORS = "It seems, that your JRE installation doesn't have system trust store.\n" + - "If you're using Mac JRE, try upgrading to the latest version."; - + @Override + public X509Certificate[] getAcceptedIssuers() { + return NO_CERTIFICATES; + } + }; public static ConfirmingTrustManager createForStorage(@NotNull String path, @NotNull String password) { return new ConfirmingTrustManager(getSystemDefault(), new MutableTrustManager(path, password)); } private static X509TrustManager getSystemDefault() { - X509TrustManager systemManager; try { TrustManagerFactory factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + // hacky way to get default trust store factory.init((KeyStore)null); // assume that only X509 TrustManagers exist - systemManager = findX509TrustManager(factory.getTrustManagers()); + X509TrustManager systemManager = findX509TrustManager(factory.getTrustManagers()); + if (systemManager != null && systemManager.getAcceptedIssuers().length != 0) { + return systemManager; + } } catch (Exception e) { - throw new AssertionError(e); + LOG.error("Cannot get system trust store", e); } - return systemManager; + return MISSING_TRUST_MANAGER; } private final X509TrustManager mySystemManager; @@ -75,7 +82,7 @@ class ConfirmingTrustManager implements X509TrustManager { myCustomManager = custom; } - static X509TrustManager findX509TrustManager(TrustManager[] managers) { + private static X509TrustManager findX509TrustManager(TrustManager[] managers) { for (TrustManager manager : managers) { if (manager instanceof X509TrustManager) { return (X509TrustManager)manager; @@ -84,68 +91,50 @@ class ConfirmingTrustManager implements X509TrustManager { return null; } - @Override - public void checkClientTrusted(X509Certificate[] certificates, String s) throws CertificateException { - throw new UnsupportedOperationException("Should not be called by client"); - } - @Override public void checkServerTrusted(final X509Certificate[] certificates, String s) throws CertificateException { try { mySystemManager.checkServerTrusted(certificates, s); } - catch (RuntimeException e) { - Throwable cause = e.getCause(); - // this can happen on some version of Apple's JRE, e.g. see IDEA-115565 - if (cause != null && cause.getMessage().equals("the trustAnchors parameter must be non-empty")) { - LOG.error(ERR_EMPTY_TRUST_ANCHORS, e); - Notifications.Bus.notify(new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, - "No default keystore", ERR_EMPTY_TRUST_ANCHORS, - NotificationType.ERROR)); - throw e; - } - } catch (CertificateException e) { - X509Certificate certificate = certificates[0]; - // looks like self-signed certificate - if (certificates.length == 1) { - // check-then-act sequence - synchronized (myCustomManager) { - try { - myCustomManager.checkServerTrusted(certificates, s); - } - catch (CertificateException e2) { - if (myCustomManager.isBroken() || !updateTrustStore(certificate)) { - throw e; - } + // check-then-act sequence + synchronized (myCustomManager) { + try { + myCustomManager.checkServerTrusted(certificates, s); + } + catch (CertificateException e2) { + if (myCustomManager.isBroken() || !confirmAndUpdate(certificates)) { + throw e; } } } } } - private boolean updateTrustStore(final X509Certificate certificate) { + private boolean confirmAndUpdate(final X509Certificate[] chain) { Application app = ApplicationManager.getApplication(); + final X509Certificate endPoint = chain[0]; if (app.isUnitTestMode() || app.isHeadlessEnvironment()) { - myCustomManager.addCertificate(certificate); + myCustomManager.addCertificate(endPoint); return true; } boolean accepted = CertificatesManager.showAcceptDialog(new Callable() { @Override public DialogWrapper call() throws Exception { - return CertificateWarningDialog.createSelfSignedCertificateWarning(certificate); + // TODO may be another kind of warning, if default trust store is missing + return CertificateWarningDialog.createUntrustedCertificateWarning(endPoint); } }); if (accepted) { LOG.info("Certificate was accepted"); - myCustomManager.addCertificate(certificate); + myCustomManager.addCertificate(endPoint); } return accepted; } @Override public X509Certificate[] getAcceptedIssuers() { - return mySystemManager.getAcceptedIssuers(); + return ArrayUtil.mergeArrays(mySystemManager.getAcceptedIssuers(), myCustomManager.getAcceptedIssuers()); } public X509TrustManager getSystemManager() { @@ -157,10 +146,10 @@ class ConfirmingTrustManager implements X509TrustManager { } /** - * Trust manager that supports addition of new certificates (most likely self-signed) to corresponding physical + * Trust manager that supports addition of new certificates (most likely self-signed) to underlying physical * key store. */ - static class MutableTrustManager implements X509TrustManager { + static class MutableTrustManager extends ClientOnlyTrustManager { private final String myPath; private final String myPassword; private final TrustManagerFactory myFactory; @@ -349,11 +338,6 @@ class ConfirmingTrustManager implements X509TrustManager { } } - @Override - public void checkClientTrusted(X509Certificate[] certificates, String s) throws CertificateException { - throw new UnsupportedOperationException("Should not be called"); - } - @Override public void checkServerTrusted(X509Certificate[] certificates, String s) throws CertificateException { myReadLock.lock(); diff --git a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/ShowCertificateInfoAction.java b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/ShowCertificateInfoAction.java index 112bd6a60ec4..ca8a4ad2c5c1 100644 --- a/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/ShowCertificateInfoAction.java +++ b/plugins/tasks/tasks-core/src/com/intellij/tasks/actions/ShowCertificateInfoAction.java @@ -28,7 +28,7 @@ public class ShowCertificateInfoAction extends AnAction { final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(stream, "changeit".toCharArray()); X509Certificate certificate = (X509Certificate)keyStore.getCertificate("mykey"); - CertificateWarningDialog dialog = CertificateWarningDialog.createSelfSignedCertificateWarning(certificate); + CertificateWarningDialog dialog = CertificateWarningDialog.createUntrustedCertificateWarning(certificate); LOG.debug("Accepted: " + dialog.showAndGet()); } finally {