/*
* 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 org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
/**
* OCSP responses cache: in memory with disk persistence.
*
* @author K. Benedyczak
*/
public class OCSPResponsesCache extends OCSPCacheBase
{
private Map<String, ResponseCacheEntry> responsesCache;
/**
*
* @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 OCSPResponsesCache(long maxTtl, File diskPath, String prefix)
{
super(maxTtl, diskPath, prefix);
responsesCache = Collections.synchronizedMap(new BoundedSizeLruMap<String, ResponseCacheEntry>(100));
}
/**
*
* @param responseKey response key
* @param client OCSP client
* @param toCheckCert mandatory certificate to be checked
* @param issuerCert mandatory certificate of the toCheckCert issuer
* @return The cached response if available, null otherwise.
* @throws IOException IO exception
*/
public SingleResp getCachedResp(String responseKey, OCSPClientImpl client, X509Certificate toCheckCert,
X509Certificate issuerCert) throws IOException
{
ResponseCacheEntry cachedResp = responsesCache.get(responseKey);
if (cachedResp == null && diskPath != null)
{
File f = new File(diskPath, prefix + responseKey);
if (f.exists())
cachedResp = loadResponseFromDisk(f, client, toCheckCert, issuerCert);
}
if (cachedResp == null)
return null;
Date nextUpdate = cachedResp.response != null ? cachedResp.response.getNextUpdate() : null;
Date maxCacheValidity = new Date(cachedResp.cacheDate.getTime() + maxTtl);
if (nextUpdate != null && maxCacheValidity.after(nextUpdate))
maxCacheValidity = nextUpdate;
if (maxCacheValidity.after(cachedResp.maxValidity))
maxCacheValidity = cachedResp.maxValidity;
Date now = new Date();
if (now.after(maxCacheValidity))
{
responsesCache.remove(responseKey);
if (diskPath != null)
{
File f = new File(diskPath, prefix + responseKey);
f.delete();
}
return null;
}
return cachedResp.response;
}
public String createResponseKey(X509Certificate toCheckCert, 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());
digest.update(toCheckCert.getSerialNumber().toByteArray());
return encodeDigest(digest);
}
public void addToCache(String key, OCSPResponseStructure fullResp, SingleResp singleResp) throws IOException
{
if (fullResp.getMaxCache() == null)
fullResp.setMaxCache(singleResp.getNextUpdate());
responsesCache.put(key, new ResponseCacheEntry(new Date(), fullResp.getMaxCache(), singleResp));
if (diskPath != null)
{
File f = new File(diskPath, prefix + key);
storeResponseToDisk(f, fullResp);
}
}
public void clearMemoryCache()
{
responsesCache.clear();
}
private void storeResponseToDisk(File f, OCSPResponseStructure fullResp) throws IOException
{
if (f.exists())
f.delete();
Date maxCache = fullResp.getMaxCache();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
try
{
oos.writeObject(maxCache);
oos.writeObject(fullResp.getResponse().getEncoded());
} finally
{
oos.close();
}
}
private ResponseCacheEntry loadResponseFromDisk(File f, OCSPClientImpl client, X509Certificate toCheckCert,
X509Certificate issuerCert)
{
ObjectInputStream ois = null;
try
{
ois = new ObjectInputStream(new FileInputStream(f));
Date maxCache = (Date)ois.readObject();
byte[] resp = (byte[]) ois.readObject();
OCSPResp fullResp = new OCSPResp(resp);
SingleResp diskResp = client.verifyResponse(fullResp, toCheckCert, issuerCert, null);
return new ResponseCacheEntry(new Date(f.lastModified()), maxCache, diskResp);
} catch (Exception e)
{
f.delete();
return null;
} finally
{
if (ois != null)
try
{
ois.close();
} catch (IOException e)
{ //ok
}
}
}
private static class ResponseCacheEntry
{
private Date cacheDate;
private Date maxValidity;
private SingleResp response;
public ResponseCacheEntry(Date cacheDate, Date maxValidity, SingleResp response)
{
this.cacheDate = cacheDate;
this.maxValidity = maxValidity;
this.response = response;
}
}
}