/*
* Copyright (c) 2012 ICM Uniwersytet Warszawski All rights reserved.
* See LICENCE.txt file for licensing information.
*/
package eu.emi.security.authn.x509.helpers.ocsp;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import eu.emi.security.authn.x509.OCSPParametes;
/**
* OCSP failing responses cache: in memory with disk persistence. Only IOExceptions are cached.
*
* @author K. Benedyczak
*/
public class OCSPRespondersCache extends OCSPCacheBase
{
private Map<String, ResponderCacheEntry> respondersCache;
/**
*
* @param maxTtl maximum time after each cached response expires. Negative for no cache at all, 0 for no limit
* (i.e. caching time will be only controlled by the OCSP response validity period). In ms.
* @param diskPath if not null, cached responses will be stored on disk.
* @param prefix used if disk cache is enabled, as a common prefix for all files created in the cache directory.
*/
public OCSPRespondersCache(long maxTtl, File diskPath, String prefix)
{
super(maxTtl == 0 ? OCSPParametes.DEFAULT_CACHE : maxTtl, diskPath, prefix);
respondersCache = Collections.synchronizedMap(new BoundedSizeLruMap<String, ResponderCacheEntry>(100));
}
/**
* Checks if there is a cached and not outdated cache entry for a given responder key. If this is the case
* a cached exception is thrown.
* @param responderKey responder key
* @throws IOException IO exception
*/
public void checkCachedError(String responderKey) throws IOException
{
ResponderCacheEntry cachedError = respondersCache.get(responderKey);
if (cachedError == null && diskPath != null)
{
File f = new File(diskPath, prefix + responderKey);
if (f.exists())
cachedError = loadResponderFromDisk(f);
}
if (cachedError == null)
return;
Date now = new Date();
if (now.after(cachedError.maxValidity))
{
respondersCache.remove(responderKey);
if (diskPath != null)
{
File f = new File(diskPath, prefix + responderKey);
f.delete();
}
return;
}
if (cachedError.error != null)
throw cachedError.error;
}
public void addToCache(String key, IOException error) throws IOException
{
Date maxCacheValidity = new Date(System.currentTimeMillis() + maxTtl);
ResponderCacheEntry entry = new ResponderCacheEntry(maxCacheValidity, error);
respondersCache.put(key, entry);
if (diskPath != null)
{
File f = new File(diskPath, prefix + key);
storeResponderToDisk(f, entry);
}
}
public void clearMemoryCache()
{
respondersCache.clear();
}
private void storeResponderToDisk(File f, ResponderCacheEntry entry) throws IOException
{
if (f.exists())
f.delete();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
try
{
oos.writeObject(entry.maxValidity);
oos.writeObject(entry.error);
} finally
{
oos.close();
}
}
private ResponderCacheEntry loadResponderFromDisk(File f)
{
ObjectInputStream ois = null;
try
{
ois = new ObjectInputStream(new FileInputStream(f));
Date maxCache = (Date)ois.readObject();
IOException error = (IOException) ois.readObject();
return new ResponderCacheEntry(maxCache, error);
} catch (Exception e)
{
f.delete();
return null;
} finally
{
if (ois != null)
try
{
ois.close();
} catch (IOException e)
{ //ok
}
}
}
public String createResponderKey(X509Certificate issuerCert)
{
MessageDigest digest;
try
{
digest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e)
{
throw new RuntimeException("JDK problem: SHA-1 hash not supported by any provider!", e);
}
PublicKey issuerKey = issuerCert.getPublicKey();
digest.update(issuerCert.getSubjectX500Principal().getEncoded());
digest.update(issuerKey.getEncoded());
return encodeDigest(digest);
}
private static class ResponderCacheEntry
{
private Date maxValidity;
private IOException error;
public ResponderCacheEntry(Date maxValidity, IOException error)
{
this.maxValidity = maxValidity;
this.error = error;
}
}
}