/*
* Copyright (c) 2011-2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE.txt file for licensing information.
*/
package eu.emi.security.authn.x509.impl;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.security.cert.CRL;
import java.security.cert.CertStoreSpi;
import java.security.cert.X509CRL;
import java.security.cert.X509CRLSelector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import static org.junit.Assert.*;
import org.apache.commons.io.FileUtils;
import org.bouncycastle.util.encoders.Base64;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import eu.emi.security.authn.x509.RiskyIntegrationTests;
import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.helpers.ObserversHandler;
import eu.emi.security.authn.x509.helpers.crl.OpensslCRLStoreSpi;
import eu.emi.security.authn.x509.helpers.crl.PlainCRLStoreSpi;
import eu.emi.security.authn.x509.helpers.trust.OpensslTruststoreHelper;
public class CRLTest
{
static
{
//Required as we call low-level code directly (OpensslCRLStoreSpi)
CertificateUtils.configureSecProvider();
}
private int notificationOK;
private int localPort;
private int opensslWarn, opensslErr;
private static File initDir() throws IOException
{
File dir = new File("target/test-tmp/crls/diskCache");
FileUtils.deleteDirectory(dir);
dir.mkdirs();
return dir;
}
@Test
public void testUpdateCleanup() throws Exception
{
File dir = initDir();
Timer t = new Timer(true);
List<String> crls = new ArrayList<String>();
String crlURL1 = dir.getPath() + "/*.in";
crls.add(crlURL1);
File target = new File(dir, "file.in");
FileUtils.copyFile(new File("src/test/resources/test-pems/crls/relaxationsubca.crl"),
target);
CRLParameters params = new CRLParameters(crls, 250,
5000, dir.getPath());
PlainCRLStoreSpi store = new PlainCRLStoreSpi(params, t,
new ObserversHandler());
store.start();
checkCRL("CN=the subca CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", store, 1);
target.delete();
Thread.sleep(500);
checkCRL("CN=the subca CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", store, 0);
store.dispose();
}
@Test
public void testNotificationsAndUpdate() throws Exception
{
File dir = initDir();
Timer t = new Timer(true);
List<String> crls = new ArrayList<String>();
final String crlURL1 = "http://127.0.0.1/non-existing/crl.pem";
final String crlURL2 = "http://127.0.0.1/non-existing2/crl2.pem";
crls.add(crlURL1);
crls.add(crlURL2);
String base64URL = new String(Base64.encode(crlURL1.getBytes())) + "-crl.der";
FileUtils.copyFile(new File("src/test/resources/test-pems/crls/relaxationsubca.crl"),
new File(dir, base64URL));
CRLParameters params = new CRLParameters(crls, 500,
100, dir.getPath());
notificationOK=0;
StoreUpdateListener listener = new StoreUpdateListener()
{
public void loadingNotification(String crlLocation, String type,
Severity level,
Exception cause)
{
assertEquals(type, StoreUpdateListener.CRL);
if (level.equals(Severity.ERROR))
{
assertEquals(crlURL2, crlLocation);
assertTrue(cause instanceof IOException);
notificationOK++;
} else if (level.equals(Severity.WARNING))
{
assertEquals(crlURL1, crlLocation);
assertNotNull(cause);
assertTrue(cause.toString(), cause instanceof IOException);
assertTrue(cause.getMessage().contains("cached copy"));
notificationOK++;
}
}
};
ObserversHandler observers = new ObserversHandler(Collections.singleton(listener));
PlainCRLStoreSpi store = new PlainCRLStoreSpi(params, t, observers);
store.start();
assertEquals(2, notificationOK);
observers.removeObserver(listener);
observers.addObserver(listener);
Thread.sleep(750);
assertEquals(4, notificationOK);
store.setUpdateInterval(-1);
Thread.sleep(750);
assertEquals(4, notificationOK);
store.dispose();
}
@Test
public void testTimeout() throws Exception
{
Thread server = new Thread()
{
public void run()
{
try
{
ServerSocket ss = new ServerSocket(0, 0,
InetAddress.getByName("127.0.0.1"));
localPort = ss.getLocalPort();
Socket s = ss.accept();
System.out.println("Got connection");
Thread.sleep(5000);
s.close();
ss.close();
} catch (Exception e)
{
fail(e.toString());
}
}
};
server.start();
Thread.sleep(250);
File dir = initDir();
Timer t = new Timer(true);
List<String> crls = new ArrayList<String>();
final String crlURL1 = "http://127.0.0.1:"+ localPort + "/crl.pem";
crls.add(crlURL1);
CRLParameters params = new CRLParameters(crls, -1, 500, dir.getPath());
notificationOK=0;
StoreUpdateListener listener = new StoreUpdateListener()
{
public void loadingNotification(String crlLocation, String type, Severity level,
Exception cause)
{
assertEquals(type, StoreUpdateListener.CRL);
assertEquals(level, Severity.ERROR);
assertEquals(crlURL1, crlLocation);
assertTrue(cause instanceof SocketTimeoutException);
System.out.println(crlLocation + " " + cause.toString());
notificationOK++;
}
};
long start = System.currentTimeMillis();
PlainCRLStoreSpi store = new PlainCRLStoreSpi(params, t, new ObserversHandler(
Collections.singleton(listener)));
store.start();
assertEquals(1, notificationOK);
start = System.currentTimeMillis() - start;
assertTrue(start < 500*3);
store.dispose();
}
@Test
public void testLoadPlain() throws Exception
{
File dir = initDir();
Timer t = new Timer(true);
List<String> crls = new ArrayList<String>();
String crlURL1 = "http://www.man.poznan.pl/plgrid-ca/crl.pem";
String crlURL2 = "http://127.0.0.1/non-existing/crl.pem";
String crlURL3 = "src/test/resources/test-pems/crls/*.pem";
crls.add(crlURL1);
crls.add(crlURL2);
crls.add(crlURL3);
String base64URL1 = new String(Base64.encode(crlURL1.getBytes())) + "-crl.der";
String base64URL2 = new String(Base64.encode(crlURL2.getBytes())) + "-crl.der";
FileUtils.copyFile(new File("src/test/resources/test-pems/crls/relaxationsubca.crl"),
new File(dir, base64URL2));
CRLParameters params = new CRLParameters(crls, -1,
5000, dir.getPath());
PlainCRLStoreSpi store = new PlainCRLStoreSpi(params, t, new ObserversHandler());
store.start();
checkCRL("CN=Polish Grid CA,O=GRID,C=PL", store, 1);
String[] ls = dir.list();
assertTrue(ls.length == 2);
assertTrue(ls[0].equals(base64URL1) || ls[1].equals(base64URL1));
checkCRL("CN=the subca CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", store, 1);
checkCRL("CN=the trusted CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", store, 1);
checkCRL("CN=missing CA,C=UG", store, 0);
assertEquals(crls, store.getLocations());
store.dispose();
}
@Test
@Category(RiskyIntegrationTests.class)
public void testMemoryFootprint() throws Exception
{
File dir = new File("target/test-tmp/crls/copiedCrls");
FileUtils.deleteDirectory(dir);
dir.mkdirs();
int N = 100;
File crl1 = new File("src/test/resources/test-pems/crls/relaxationsubca.crl");
File crl2 = new File("src/test/resources/test-pems/crls/tropiccacrl.pem");
for (int i=0; i<N; i++)
{
FileUtils.copyFile(crl1, new File(dir, "crl1_"+i+".pem"));
FileUtils.copyFile(crl2, new File(dir, "crl2_"+i+".pem"));
}
Timer t = new Timer(true);
List<String> crls = new ArrayList<String>();
crls.add(dir.getPath()+"/*.pem");
CRLParameters params = new CRLParameters(crls, -1,
5000, dir.getPath());
int M = 150;
PlainCRLStoreSpi[] stores = new PlainCRLStoreSpi[M];
for (int i=0; i<M; i++)
{
stores[i] = new PlainCRLStoreSpi(params, t, new ObserversHandler());
stores[i].start();
if ((i %10) == 0)
{
Runtime r = Runtime.getRuntime();
System.out.println("Loaded " + i + "\t: " + ((r.totalMemory()-r.freeMemory()))/1024 + "kb");
}
}
for (int i=0; i<M; i++)
{
checkCRL("CN=the subca CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", stores[i], N);
checkCRL("CN=the trusted CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", stores[i], N);
assertEquals(crls, stores[i].getLocations());
}
for (int i=0; i<M; i++)
stores[i].dispose();
}
@Test
public void testLoadOpenssl() throws Exception
{
Timer t = new Timer(true);
opensslErr = 0;
opensslWarn = 0;
StoreUpdateListener listener = new StoreUpdateListener()
{
public void loadingNotification(String crlLocation, String type, Severity level,
Exception cause)
{
assertEquals(type, StoreUpdateListener.CRL);
if (level == Severity.ERROR)
opensslErr++;
else if (level == Severity.WARNING)
opensslWarn++;
}
};
OpensslCRLStoreSpi store = new OpensslCRLStoreSpi(
"src/test/resources/openssl-testcrldir", -1, t,
new ObserversHandler(Collections.singleton(listener)), false);
checkCRL("CN=the trusted CA,OU=Relaxation,O=Utopia,L=Tropic,C=UG", store, 1);
assertEquals(1, opensslErr);
assertEquals(0, opensslWarn);
store.dispose();
}
@Test
public void checkPattern() throws Exception
{
assertNotNull(OpensslTruststoreHelper.getFileHash("5a1a2F89.r0",
"^([0-9a-fA-F]{8})\\.r[\\d]+$"));
}
private static void checkCRL(String caDN, CertStoreSpi store, int expected) throws Exception
{
X509CRLSelector selector = new X509CRLSelector();
selector.addIssuerName(caDN);
Collection<? extends CRL> matched = store.engineGetCRLs(selector);
assertEquals(expected, matched.size());
if (expected > 0)
{
X509CRL crl = (X509CRL) matched.iterator().next();
assertTrue(X500NameUtils.equal(crl.getIssuerX500Principal(), caDN));
}
}
}