diff --git a/community-tests/src/tests/testGroups.properties b/community-tests/src/tests/testGroups.properties index 96075ed68f28..5941b527969f 100644 --- a/community-tests/src/tests/testGroups.properties +++ b/community-tests/src/tests/testGroups.properties @@ -2,3 +2,4 @@ [DISABLED_TESTS] org.jetbrains.idea.svn.* org.jetbrains.idea.svn16.* +com.intellij.util.net.ssl.* 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 82dd36e7f41e..bb80da46e476 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 @@ -350,6 +350,34 @@ public class ConfirmingTrustManager extends ClientOnlyTrustManager { } } + /** + * Check that underlying trust store contains certificate with specified alias. + * + * @param alias - certificate's alias to be checked + * @return - whether certificate is in storage + */ + public boolean containsCertificate(@NotNull String alias) { + myReadLock.lock(); + try { + return myKeyStore.containsAlias(alias); + } + catch (KeyStoreException e) { + LOG.error(e); + return false; + } finally { + myReadLock.unlock(); + } + } + + boolean removeAllCertificates() { + for (X509Certificate certificate : getCertificates()) { + if (!removeCertificate(certificate)) { + return false; + } + } + return true; + } + @Override public void checkServerTrusted(X509Certificate[] certificates, String s) throws CertificateException { myReadLock.lock(); diff --git a/platform/platform-tests/platform-tests.iml b/platform/platform-tests/platform-tests.iml index a2fe52638ffd..d1018688da69 100644 --- a/platform/platform-tests/platform-tests.iml +++ b/platform/platform-tests/platform-tests.iml @@ -22,6 +22,7 @@ + diff --git a/platform/platform-tests/testData/certificates/ca.crt b/platform/platform-tests/testData/certificates/ca.crt new file mode 100644 index 000000000000..d258c4e4009a --- /dev/null +++ b/platform/platform-tests/testData/certificates/ca.crt @@ -0,0 +1,56 @@ +-----BEGIN CERTIFICATE----- +MIIKGTCCBgGgAwIBAgIJAMs2bK7YyVSbMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD +VQQGEwJSVTEPMA0GA1UECAwGUnVzc2lhMRYwFAYDVQQHDA1TdC5QZXRlcnNidXJn +MRswGQYDVQQKDBJDZXJ0aWZpY2F0ZXMgVGVzdHMxHjAcBgNVBAsMFUNlcnRpZmlj +YXRlIEF1dGhvcml0eTEtMCsGA1UEAwwkY2VydGlmaWNhdGVzLXRlc3RzLmxhYnMu +aW50ZWxsaWoubmV0MB4XDTE0MDIxMzE1MzkyOFoXDTI0MDIxMTE1MzkyOFowgaIx +CzAJBgNVBAYTAlJVMQ8wDQYDVQQIDAZSdXNzaWExFjAUBgNVBAcMDVN0LlBldGVy +c2J1cmcxGzAZBgNVBAoMEkNlcnRpZmljYXRlcyBUZXN0czEeMBwGA1UECwwVQ2Vy +dGlmaWNhdGUgQXV0aG9yaXR5MS0wKwYDVQQDDCRjZXJ0aWZpY2F0ZXMtdGVzdHMu +bGFicy5pbnRlbGxpai5uZXQwggQiMA0GCSqGSIb3DQEBAQUAA4IEDwAwggQKAoIE +AQCnFJ7EhraRT/sleDkR9fkHL7UT+ae3DSB5nJcc5sELv+5PirL2rYpwGtbdr4LX +bgsACCk2iRKlVdCA4RRhMBuPm7XQ88Wtj2cMV8FUcl0xrAhg27vIAViLsLNf5vFV +An+FUBfjxy++mMlYEFJXXILEE7TyfW1CHMBhn+2S0IBIvBjWDeWacvKrq/WUJ2qm +BQJrvubRL1MtXaUOt8naYgiLLZRFmcy4/id8rxA+7+nX02hckibQXPfEiEzYKB9c +zRN4fctYIAsVBY7dx6dDlysRPPzenBkd7ym3EB1ZBDobVJRb8cfct2ysl/C9B71t +FP9X7gfzSmm/vYdQw3EsIUMJQDWdiEmHAP+Bj9aOftVvi6I8OSuHr9SB7W52vgo6 +o+aYrKmCQGMs98WF8XjOsDa4T0MH4e0lUIZ5YuITlH6rG0/iM8GhuJJKaF9XJSia +Osj6YdkgUYSoK1ZYZwtLUJ30u2N7AvhQ0xRZbSjZCLFfA7eu3V9B4hBL4q+u97+M +CuqUje+KurrQKZ4XVnfiPNMjXocx3i1KULP9jW09ZAa1sGyv3sCETkP+dbbhcF5X +WA2Is+33zj452lX5PoCIkTHZtVTbixnJ64DsBX3YwfZFtwHH/wDwRWtxnwWNXGIq +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 +-----END CERTIFICATE----- diff --git a/platform/platform-tests/testSrc/com/intellij/util/net/ssl/CertificateTest.java b/platform/platform-tests/testSrc/com/intellij/util/net/ssl/CertificateTest.java new file mode 100644 index 000000000000..9a544b7158f8 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/util/net/ssl/CertificateTest.java @@ -0,0 +1,120 @@ +package com.intellij.util.net.ssl; + +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.PlatformTestUtil; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.File; + + +/** + * @author Mikhail Golubev + */ +public class CertificateTest extends PlatformTestCase { + @NonNls private static final String AUTHORITY_CN = "certificates-tests.labs.intellij.net"; + + @NonNls private static final String TRUSTED_CERT_CN = "trusted.certificates-tests.labs.intellij.net"; + @NonNls private static final String EXPIRED_CERT_CN = "expired.certificates-tests.labs.intellij.net"; + @NonNls private static final String SELF_SIGNED_CERT_CN = "self-signed.certificates-tests.labs.intellij.net"; + + // this is the only type of certificates, which 'Common Name' field doesn't match URL of server, where it's located + @NonNls private static final String WRONG_HOSTNAME_CERT_CN = "illegal.certificates-tests.labs.intellij.net"; + @NonNls private static final String WRONG_HOSTNAME_CERT_URL = "https://wrong-hostname.certificates-tests.labs.intellij.net"; + + private CloseableHttpClient myClient; + private ConfirmingTrustManager.MutableTrustManager myTrustManager; + + + /** + * Test that expired certificate doesn't pass JSSE timestamp check and hence untrusted and added explicitly, although + * issued by our test CA. + */ + public void testExpiredCertificate() throws Exception { + doTest(EXPIRED_CERT_CN, true); + } + + /** + * Test that self-signed certificate, that wasn't issued by out test CA, is untrusted and thus added explicitly. + */ + public void testSelfSignedCertificate() throws Exception { + doTest(SELF_SIGNED_CERT_CN, true); + } + + /** + * Hostname validity check (see {@link org.apache.http.conn.ssl.X509HostnameVerifier}) is disabled for now, so + * it merely tests that even certificate with illegal CN field (i.e. it doesn't match requested URL). + * is trusted, because issued by our test CA. + */ + public void testWrongHostnameCertificate() throws Exception { + // wrong hostname doesn't lead to any warning by now, thus it's treated the same as trusted certificate + doTest(WRONG_HOSTNAME_CERT_URL, WRONG_HOSTNAME_CERT_CN, false); + } + + /** + * Test that certificate with correct hostname, validity terms and issued by our test CA is trusted. + */ + public void testTrustedCertificate() throws Exception { + doTest(TRUSTED_CERT_CN, false); + } + + + private void doTest(@NonNls String alias, boolean willBeAdded) throws Exception { + doTest("https://" + alias, alias, willBeAdded); + } + + private void doTest(@NotNull String url, @NotNull String alias, boolean added) throws Exception { + CloseableHttpResponse response = myClient.execute(new HttpGet(url)); + try { + assertEquals(response.getStatusLine().getStatusCode(), HttpStatus.SC_OK); + } + finally { + response.close(); + } + if (added) { + assertTrue(myTrustManager.containsCertificate(alias)); + assertEquals(2, myTrustManager.getCertificates().size()); + } + else { + // only CA certificate + assertEquals(1, myTrustManager.getCertificates().size()); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + CertificatesManager certificatesManager = CertificatesManager.getInstance(); + myClient = HttpClientBuilder.create() + .setSslcontext(certificatesManager.getSslContext()) + .setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) + .build(); + + // add CA certificate + myTrustManager = certificatesManager.getCustomTrustManager(); + assertTrue(myTrustManager.addCertificate(getTestDataPath() + "certificates/ca.crt")); + assertTrue(myTrustManager.containsCertificate(AUTHORITY_CN)); + } + + @Override + public void tearDown() throws Exception { + try { + assertTrue(myTrustManager.removeAllCertificates()); + assertEmpty(myTrustManager.getCertificates()); + } + finally { + myClient.close(); + } + super.tearDown(); + } + + private static String getTestDataPath() { + return PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/platform/platform-tests/testData/"; + } +}