blob: dd5a6bafa174bd44018b8712fdeec1c6223df089 [file] [log] [blame]
package com.intellij.util.net.ssl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Ref;
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;
import java.security.cert.X509Certificate;
import java.util.concurrent.Callable;
import static com.intellij.util.net.ssl.ConfirmingTrustManager.MutableTrustManager;
/**
* @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";
// TODO: Add proper tests of client authentication, when it'll be supported (see IDEA-124209).
// By now client certificate should be specified manually via VM options like -Djavax.net.ssl.keyStore.
@SuppressWarnings("UnusedDeclaration") @NonNls private static final String CLIENT_AUTH_CERT_CN = "client-auth.certificates-tests.labs.intellij.net";
//private static final Logger LOG = Logger.getInstance(CertificateTest.class);
private CloseableHttpClient myClient;
private MutableTrustManager myTrustManager;
private CertificateManager myCertificateManager;
private X509Certificate myAuthorityCertificate;
public void testSetUp() throws Exception {
assertTrue(myTrustManager.containsCertificate(AUTHORITY_CN));
}
/**
* 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());
}
}
public void testDeadlockDetection() throws Exception {
final Ref<Throwable> throwableRef = new Ref<Throwable>();
final long interruptionTimeout = CertificateManager.DIALOG_VISIBILITY_TIMEOUT + 1000;
// Will be interrupted after at most interruptionTimeout (6 seconds originally)
ApplicationManager.getApplication().invokeAndWait(new Runnable() {
@Override
public void run() {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
boolean accepted = CertificateManager.showAcceptDialog(new Callable<DialogWrapper>() {
@Override
public DialogWrapper call() throws Exception {
// this dialog will be attempted to show only if blocking thread was forcibly interrupted after timeout
throw new AssertionError("Deadlock was not detected in time");
}
});
// should be rejected after 5 seconds
assertFalse("Certificate should be rejected", accepted);
}
catch (Throwable e) {
throwableRef.set(e);
}
}
}, "Test EDT-blocking thread");
thread.start();
try {
thread.join(interruptionTimeout);
}
catch (InterruptedException ignored) {
// No one will attempt to interrupt EDT, right?
}
finally {
if (thread.isAlive()) {
thread.interrupt();
fail("Deadlock was not detected in time");
}
}
}
}, ModalityState.any());
if (!throwableRef.isNull()) {
throw new AssertionError(throwableRef.get());
}
}
@Override
public void setUp() throws Exception {
super.setUp();
myCertificateManager = CertificateManager.getInstance();
// add CA certificate
myTrustManager = myCertificateManager.getCustomTrustManager();
myAuthorityCertificate = CertificateUtil.loadX509Certificate(getTestDataPath() + "certificates/ca.crt");
myClient = HttpClientBuilder.create()
.setSslcontext(myCertificateManager.getSslContext())
.setHostnameVerifier(SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
.build();
myTrustManager.addCertificate(myAuthorityCertificate);
}
@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/";
}
}