/*
* 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.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.x509.AccessDescription;
import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.cert.ocsp.OCSPException;
import eu.emi.security.authn.x509.OCSPParametes;
import eu.emi.security.authn.x509.OCSPResponder;
import eu.emi.security.authn.x509.StoreUpdateListener;
import eu.emi.security.authn.x509.StoreUpdateListener.Severity;
import eu.emi.security.authn.x509.ValidationErrorCode;
import eu.emi.security.authn.x509.helpers.ObserversHandler;
import eu.emi.security.authn.x509.helpers.pkipath.SimpleValidationErrorException;
/**
* OCSP checker - uses provided {@link OCSPParametes} to perform OCSP calls using
* {@link OCSPCachingClient} and returns the final response. Failures (exceptions) are reported via provided callback.
* @author K. Benedyczak
*/
public class OCSPVerifier
{
private OCSPParametes params;
private ObserversHandler observers;
public static String OCSP_CACHE_PFX = "ocspresp_";
public OCSPVerifier(OCSPParametes params, ObserversHandler observers)
{
this.params = params;
this.observers = observers;
}
public OCSPResult verify(X509Certificate toCheck, X509Certificate issuerCert) throws SimpleValidationErrorException
{
List<OCSPResponder> certResponders = getOCSPUrls(toCheck, issuerCert);
OCSPResponder[] localResponders = params.getLocalResponders();
List<OCSPResponder> allResponders = new ArrayList<OCSPResponder>();
if (params.isPreferLocalResponders())
{
Collections.addAll(allResponders, localResponders);
allResponders.addAll(certResponders);
} else
{
allResponders.addAll(certResponders);
Collections.addAll(allResponders, localResponders);
}
if (allResponders.size() == 0)
throw new SimpleValidationErrorException(ValidationErrorCode.ocspNoResponder);
File diskCachePath = params.getDiskCachePath() == null ? null : new File(params.getDiskCachePath());
OCSPCachingClient client = new OCSPCachingClient(params.getCacheTtl(), diskCachePath,
OCSP_CACHE_PFX);
for (int i=0; i<allResponders.size(); i++)
{
OCSPResponder responder = allResponders.get(i);
OCSPResult status;
if (i<allResponders.size()-1)
{
try
{
status = client.queryForCertificate(responder.getAddress(), toCheck,
responder.getCertificate(), null, params.isUseNonce(),
params.getConntectTimeout());
} catch (Exception e)
{
observers.notifyObservers(responder.getAddress().toExternalForm(),
StoreUpdateListener.OCSP, Severity.WARNING, e);
continue;
}
} else
{
try
{
status = client.queryForCertificate(responder.getAddress(), toCheck,
responder.getCertificate(), null, params.isUseNonce(),
params.getConntectTimeout());
} catch (IOException e)
{
observers.notifyObservers(responder.getAddress().toExternalForm(),
StoreUpdateListener.OCSP, Severity.WARNING, e);
throw new SimpleValidationErrorException(ValidationErrorCode.ocspResponderQueryError,
responder.getAddress(), e.getMessage());
} catch (OCSPException e)
{
observers.notifyObservers(responder.getAddress().toExternalForm(),
StoreUpdateListener.OCSP, Severity.WARNING, e);
throw new SimpleValidationErrorException(ValidationErrorCode.ocspResponseInvalid,
responder.getAddress(), e.getMessage());
} catch (Exception e)
{
observers.notifyObservers(responder.getAddress().toExternalForm(),
StoreUpdateListener.OCSP, Severity.WARNING, e);
throw new SimpleValidationErrorException(ValidationErrorCode.ocspOtherError,
responder.getAddress(), e.toString());
}
}
if (status.getStatus() != OCSPResult.Status.unknown)
return status;
}
return new OCSPResult(OCSPResult.Status.unknown);
}
protected List<OCSPResponder> getOCSPUrls(X509Certificate certificate, X509Certificate issuerCert)
throws SimpleValidationErrorException
{
AuthorityInformationAccess authInfoAcc = null;
byte[] authInfoExt = certificate.getExtensionValue(Extension.authorityInfoAccess.getId());
if (authInfoExt != null)
{
ASN1InputStream aIn = new ASN1InputStream(authInfoExt);
ASN1Sequence seq;
try
{
ASN1OctetString octs = (ASN1OctetString)aIn.readObject();
aIn.close();
aIn = new ASN1InputStream(octs.getOctets());
seq = ASN1Sequence.getInstance(aIn.readObject());
aIn.close();
} catch (IOException e)
{
throw new SimpleValidationErrorException(ValidationErrorCode.ocspOtherError,
"unknown", "Can't extract Authority Info Access extension: "
+ e.toString());
}
authInfoAcc = AuthorityInformationAccess.getInstance(seq);
} else
return new ArrayList<OCSPResponder>();
List<OCSPResponder> ret = new ArrayList<OCSPResponder>();
AccessDescription[] ads = authInfoAcc.getAccessDescriptions();
for (int i = 0; i < ads.length; i++)
{
if (ads[i].getAccessMethod().equals(AccessDescription.id_ad_ocsp))
{
GeneralName name = ads[i].getAccessLocation();
if (name.getTagNo() == GeneralName.uniformResourceIdentifier)
{
String url = ((DERIA5String) name.getName()).getString();
try
{
ret.add(new OCSPResponder(new URL(url), issuerCert));
} catch (MalformedURLException e)
{
observers.notifyObservers(url, StoreUpdateListener.OCSP, Severity.ERROR,
new Exception("OCSP responder address in certificate being " +
"checked is not a valid URL: " + e.getMessage(), e));
}
}
}
}
return ret;
}
}