package org.signserver.common; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.ocsp.BasicOCSPResp; import org.bouncycastle.ocsp.OCSPResp; import org.bouncycastle.ocsp.SingleResp; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.ocsp.CertificateID; import org.bouncycastle.cert.ocsp.OCSPException; import org.bouncycastle.ocsp.OCSPReqGenerator; import org.bouncycastle.ocsp.OCSPReq; import org.bouncycastle.ocsp.OCSPRespStatus; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.util.Store; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.FileInputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.cert.CertStore; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.security.cert.X509CRL; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.Vector; import org.apache.log4j.Logger; import org.signserver.common.*; import org.signserver.common.util.*; import org.signserver.common.dbdao.*; import javax.xml.bind.DatatypeConverter; public class CertificateStatus { private static final Logger LOG = Logger.getLogger(CertificateStatus.class); private static CertificateStatus instance = null; final private static String CERTIFICATE_OCSP = "OCSP"; final private static String CERTIFICATE_CRL = "CRL"; final private static String CERTIFICATE_EXP = "Exception"; public CertificateStatus() { } public static CertificateStatus getInstance() { if (instance == null) { instance = new CertificateStatus(); } return instance; } public OcspStatus checkRevocationStatus(String channelName, String user, String ocspURL, X509Certificate cert, X509Certificate issuerCert, int retry, int endpointConfigId) { EndpointServiceResp endpointServiceResp = null; try { OCSPReq request = generateOCSPRequest(issuerCert, cert.getSerialNumber()); byte[] array = request.getEncoded(); //String[] endpointParams = DBConnector.getInstances().authEndPointParamsGet(Defines.CONNECTION_PARAMS_OCSP); endpointServiceResp = EndpointService.getInstance().checkOcsp(channelName, user, array, ocspURL, retry, endpointConfigId); if(endpointServiceResp.getResponseData() == null) { LOG.error("OCSP response. Please check Endpoint connection"); return new OcspStatus(OcspStatus.ERROR, false, endpointServiceResp.getEndpointId()); } OCSPResp ocspResponse = new OCSPResp(endpointServiceResp.getResponseData()); if (OCSPRespStatus.SUCCESSFUL == ocspResponse.getStatus()) LOG.info("OCSP response fine"); BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse.getResponseObject(); SingleResp[] responses = (basicResponse == null) ? null: basicResponse.getResponses(); if (responses != null && responses.length == 1) { SingleResp resp = responses[0]; Object status = resp.getCertStatus(); if (status instanceof org.bouncycastle.ocsp.RevokedStatus) { return new OcspStatus(OcspStatus.REVOKED, false, endpointServiceResp.getEndpointId()); } else if (status instanceof org.bouncycastle.ocsp.UnknownStatus) { return new OcspStatus(OcspStatus.UNKNOWN, false, endpointServiceResp.getEndpointId()); } else { return new OcspStatus(OcspStatus.GOOD, true, endpointServiceResp.getEndpointId()); } } else { LOG.info("OCSP response NULL or length != 1"); return new OcspStatus(OcspStatus.ERROR, false, endpointServiceResp.getEndpointId()); } } catch(Exception e) { e.printStackTrace(); LOG.info(e.toString()); return new OcspStatus(OcspStatus.ERROR, false, (endpointServiceResp != null)?endpointServiceResp.getEndpointId():null); } } public OcspStatus checkRevocationStatus(String ocspURL, X509Certificate cert, X509Certificate issuerCert, int retry) { while (retry > 0) { try { OCSPReq request = generateOCSPRequest(issuerCert, cert.getSerialNumber()); OCSPResp ocspResponse = getOCSPResponce(ocspURL, request); if (OCSPRespStatus.SUCCESSFUL == ocspResponse.getStatus()) LOG.info("OCSP response fine"); BasicOCSPResp basicResponse = (BasicOCSPResp) ocspResponse .getResponseObject(); SingleResp[] responses = (basicResponse == null) ? null : basicResponse.getResponses(); if (responses != null && responses.length == 1) { SingleResp resp = responses[0]; Object status = resp.getCertStatus(); if (status instanceof org.bouncycastle.ocsp.RevokedStatus) { return new OcspStatus(OcspStatus.REVOKED, false); } else if (status instanceof org.bouncycastle.ocsp.UnknownStatus) { return new OcspStatus(OcspStatus.UNKNOWN, false); } else { return new OcspStatus(OcspStatus.GOOD, true); } } else { LOG.info("OCSP response NULL or length != 1"); return new OcspStatus(OcspStatus.ERROR, false); } } catch (Exception e) { retry--; } } return new OcspStatus(OcspStatus.ERROR, false); } private OCSPResp getOCSPResponce(String serviceUrl, OCSPReq request) throws Exception { System.out .println("[CertificateStatus-getOCSPResponce] getOCSPResponce"); byte[] array = request.getEncoded(); HttpURLConnection con; URL url = new URL(serviceUrl); con = (HttpURLConnection) url.openConnection(); con.setRequestProperty("Content-Type", "application/ocsp-request"); con.setRequestProperty("Accept", "application/ocsp-response"); con.setDoOutput(true); OutputStream out = con.getOutputStream(); DataOutputStream dataOut = new DataOutputStream( new BufferedOutputStream(out)); dataOut.write(array); dataOut.flush(); dataOut.close(); // Get Response InputStream in = (InputStream) con.getContent(); OCSPResp ocspResponse = new OCSPResp(in); return ocspResponse; } private OCSPReq generateOCSPRequest(X509Certificate issuerCert, BigInteger serialNumber) throws Exception { // CertID structure is used to uniquely identify certificates that are // the subject of // an OCSP request or response and has an ASN.1 definition. CertID // structure is defined in RFC 2560 CertificateID id = new CertificateID(CertificateID.HASH_SHA1, issuerCert, serialNumber); // basic request generation with nonce OCSPReqGenerator generator = new OCSPReqGenerator(); generator.addRequest(id); // create details for nonce extension. The nonce extension is used to // bind // a request to a response to prevent replay attacks. As the name // implies, // the nonce value is something that the client should only use once // within a reasonably small period. BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis()); Vector objectIdentifiers = new Vector(); Vector values = new Vector(); // to create the request Extension objectIdentifiers.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); values.add(new X509Extension(false, new DEROctetString(nonce .toByteArray()))); generator.setRequestExtensions(new X509Extensions(objectIdentifiers, values)); return generator.generate(); } public CRLStatus checkCRLCertificate(X509Certificate x509, String pathToCrl) { try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509CRL crl = (X509CRL) cf.generateCRL(new FileInputStream(pathToCrl)); if (crl.isRevoked(x509)) { LOG.info("Certificate is revoked!"); return new CRLStatus(CRLStatus.REVOKED, crl .getRevokedCertificate(x509).getRevocationDate()); } } catch (Exception ex) { LOG.error(CertificateStatus.CERTIFICATE_EXP + ": "+ ex.getMessage()); return new CRLStatus(ex.getMessage(), true); } LOG.info("Certificate is good!"); return new CRLStatus(CRLStatus.GOOD, false); } private boolean checkDataValidity(X509Certificate x509) { try { x509.checkValidity(); return true; } catch (CertificateExpiredException e) { LOG.error("Certificate has been expired"); } catch (CertificateNotYetValidException e) { LOG.error("Certificate is not valid yet"); } return false; } public static int compareDate(Date date1, Date date2) { if (date1.after(date2)) { return -1; } if (date1.before(date2)) { return 1; } if (date1.equals(date2)) { return 0; } return -2; } public static boolean isCertificateValid(String certificate) { try { X509Certificate cert = ExtFunc.convertToX509Cert(certificate); String issuer = cert.getIssuerDN().toString(); String issuerName = ""; String[] pairs = issuer.split(","); for (String pair : pairs) { String[] paramvalue = pair.split("="); if (paramvalue[0].compareTo("CN") == 0 || paramvalue[0].compareTo(" CN") == 0) { issuerName = paramvalue[1]; break; } } ArrayList caProviders = new ArrayList(); caProviders = DBConnector.getInstances().getCAProviders(); String caCertificate = ""; String caCertificate2 = ""; String ocspURL = ""; String crlUrl = ""; if (issuerName.compareTo("") != 0) { for (Ca ca : caProviders) { if (ca.getCaDesc().compareTo(issuerName) == 0) { ocspURL = ca.getOcspUrl(); caCertificate = ca.getCert(); crlUrl = ca.getCrlPath(); caCertificate2 = ca.getCert2(); break; } } } else { return false; } try { cert.checkValidity(new Date()); } catch (CertificateExpiredException ex) { LOG.error("Certificate has been expired"); return false; } catch (CertificateNotYetValidException ex) { LOG.error("Certificate is not valid yet"); return false; } int methodValidateCert = DBConnector.getInstances() .getMethodValidateCert(issuerName); switch (methodValidateCert) { case 0: // no check LOG.info("No checking certificate status"); return true; case 1: // CRL LOG.info("CRL certificate status checking"); if (crlUrl.compareTo("") != 0 && caCertificate.compareTo("") != 0) { X509Certificate subX509 = cert; X509Certificate caX509 = ExtFunc.convertToX509Cert(caCertificate); if (!ExtFunc.checkCertificateRelation(caX509, subX509)) { if (caCertificate2 == null || caCertificate2.compareTo("") != 0) { caX509 = ExtFunc.convertToX509Cert(caCertificate2); if (!ExtFunc.checkCertificateRelation(caX509, subX509)) { return false; } } else { LOG.error("Invalid CA information"); return false; } } CRLStatus CRLVarification = CertificateStatus.getInstance() .checkCRLCertificate(subX509, crlUrl); if (!CRLVarification.getIsRevoked()) { LOG.info("Good certificate"); return true; } else { if (CRLVarification.getCertificateState().compareTo( CRLStatus.REVOKED) == 0) { LOG.error("Revoked certificate"); return false; } else { LOG.error("Error while checking certificate status"); return false; } } } else { LOG.error("Invalid CA information"); return false; } case 2: // OCSP LOG.info("OCSP certificate status checking"); if (ocspURL.compareTo("") != 0 && caCertificate.compareTo("") != 0) { X509Certificate subX509 = cert; X509Certificate caX509 = ExtFunc.convertToX509Cert(caCertificate); if (!ExtFunc.checkCertificateRelation(caX509, subX509)) { if (caCertificate2 == null || caCertificate2.compareTo("") != 0) { caX509 = ExtFunc.convertToX509Cert(caCertificate2); if (!ExtFunc.checkCertificateRelation(caX509, subX509)) { LOG.error("Fake certificate"); return false; } } else { LOG.error("Invalid CA information"); return false; } } boolean ocspStatus = false; int retryNumber = DBConnector.getInstances() .getNumberOCSPReTry(issuerName); OcspStatus ocsp_status = CertificateStatus.getInstance() .checkRevocationStatus(ocspURL, subX509, caX509, retryNumber); ocspStatus = ocsp_status.getIsValid(); if (ocspStatus) { LOG.info("Good certificate"); return true; } else { if (ocsp_status.getCertificateState().compareTo( OcspStatus.REVOKED) == 0) { LOG.error("Revoked certificate"); return false; } else if (ocsp_status.getCertificateState().compareTo( OcspStatus.UNKNOWN) == 0) { LOG.error("unknown certificate"); return false; } else { LOG.error("Error while checking certificate status"); return false; } } } else { LOG.error("Invalid CA information"); return false; } default: LOG.info("Signature validation and Certificate validation by OCSP (CRL if OCSP failure)"); if (crlUrl.compareTo("") != 0 && ocspURL.compareTo("") != 0 && caCertificate.compareTo("") != 0) { X509Certificate subX509 = cert; X509Certificate caX509 = ExtFunc.convertToX509Cert(caCertificate); if (!ExtFunc.checkCertificateRelation(caX509, subX509)) { if (caCertificate2 == null || caCertificate2.compareTo("") != 0) { caX509 = ExtFunc.convertToX509Cert(caCertificate2); if (!ExtFunc.checkCertificateRelation(caX509, subX509)) { LOG.error("Fake certificate"); return false; } } else { LOG.error("Invalid CA information"); return false; } } boolean ocspStatus = false; boolean crlStatus = false; int retryNumber = DBConnector.getInstances() .getNumberOCSPReTry(issuerName); OcspStatus ocsp_status = CertificateStatus.getInstance() .checkRevocationStatus(ocspURL, subX509, caX509, retryNumber); if (ocsp_status.getCertificateState().equals(OcspStatus.ERROR)) { CRLStatus CRLVarification = CertificateStatus.getInstance() .checkCRLCertificate(subX509, crlUrl); if (!CRLVarification.getIsRevoked()) { LOG.info("Good certificate"); return true; } else { if (CRLVarification.getCertificateState() .compareTo(CRLStatus.REVOKED) == 0) { LOG.error("Revoked certificate"); return false; } else { LOG.error("Error while checking certificate status"); return false; } } } else { ocspStatus = ocsp_status.getIsValid(); if (ocspStatus) { LOG.info("Good certificate"); return true; } else { if (ocsp_status.getCertificateState().compareTo( OcspStatus.REVOKED) == 0) { LOG.error("Revoked certificate"); return false; } else if (ocsp_status.getCertificateState().compareTo( OcspStatus.UNKNOWN) == 0) { LOG.error("unknown certificate"); return false; } else { LOG.error("Error while checking certificate status"); return false; } } } } else { LOG.error("Invalid CA information"); return false; } } } catch (Exception e) { LOG.error("Something wrong: "+e.getMessage()); } return false; } }