package ve.gob.cenditel.tibisaymovil; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.URL; import java.security.Security; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import org.spongycastle.asn1.DEROctetString; import org.spongycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.spongycastle.asn1.oiw.OIWObjectIdentifiers; import org.spongycastle.asn1.x509.AccessDescription; import org.spongycastle.asn1.x509.AlgorithmIdentifier; import org.spongycastle.asn1.x509.AuthorityInformationAccess; import org.spongycastle.asn1.x509.Extension; import org.spongycastle.asn1.x509.Extensions; import org.spongycastle.asn1.x509.X509Extension; import org.spongycastle.cert.CertException; import org.spongycastle.cert.X509CertificateHolder; import org.spongycastle.cert.jcajce.JcaX509CertificateConverter; import org.spongycastle.cert.jcajce.JcaX509CertificateHolder; import org.spongycastle.cert.ocsp.BasicOCSPResp; import org.spongycastle.cert.ocsp.CertificateID; import org.spongycastle.cert.ocsp.CertificateStatus; import org.spongycastle.cert.ocsp.OCSPException; import org.spongycastle.cert.ocsp.OCSPReq; import org.spongycastle.cert.ocsp.OCSPReqBuilder; import org.spongycastle.cert.ocsp.OCSPResp; import org.spongycastle.cert.ocsp.OCSPRespBuilder; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.operator.ContentVerifierProvider; import org.spongycastle.operator.DigestCalculator; import org.spongycastle.operator.OperatorCreationException; import org.spongycastle.operator.bc.BcDigestCalculatorProvider; import org.spongycastle.operator.jcajce.JcaContentVerifierProviderBuilder; /** * Utilities to deal with X509 certificates. * * @author José M. Prieto (jmprieto@emergya.com) */ public class CertificateUtils { static { Security.addProvider(new BouncyCastleProvider()); } /** * Default OCSP responder. * * @see #checkWithOCSP(X509Certificate, X509Certificate) */ private static final String DEFAULT_OCSP_RESPONDER = "http://publicador-psc.fii.gob.ve:2560/ocsp/"; /** * Validates the status of a certificate with an OCSP responder, according to RFC 2560. The Online Certificate * Status Protocol (OCSP) enables applications to determine the (revocation) state of an identified certificate. * Uses the responder specified in the corresponding certificate extension if any or a default one. * * @param certificate the certificate to be validated * @param issuerCertificate the certificate of the issuer authority * @param responderUrl the URL of the OCSP responder service to be used * @return true if the certificate is known by the responder and good, false if it is unknown or revoked * @throws CertificateException on errors when dealing with certificates * @throws IOException on I/O errors * @throws OCSPException on OCSP protocol errors */ public static ValidationResult checkWithOCSP(X509Certificate certificate, X509Certificate issuerCertificate) throws CertificateException, IOException, OCSPException { String responderUrl = DEFAULT_OCSP_RESPONDER; X509CertificateHolder holder = new JcaX509CertificateHolder(certificate); Extension extension = holder.getExtension(X509Extension.authorityInfoAccess); if (extension != null) { AuthorityInformationAccess authority = AuthorityInformationAccess.getInstance(extension.getParsedValue()); AccessDescription[] descriptions = authority.getAccessDescriptions(); for (AccessDescription description : descriptions) { if (AccessDescription.id_ad_ocsp.equals(description.getAccessMethod())) { responderUrl = description.getAccessLocation().getName().toString(); } } } return checkWithOCSP(certificate, issuerCertificate, responderUrl); } public static String getSubject(X509Certificate cert) { return cert.getSubjectDN().getName(); } public static String getSerial(X509Certificate cert) { return cert.getSerialNumber().toString(); } public static String getIssuer(X509Certificate cert) { return cert.getIssuerDN().getName(); } /** * Validates the status of a certificate with the specified OCSP responder, according to RFC 2560. * * @param certificate the certificate to be validated * @param issuerCertificate the certificate of the issuer authority * @param responderUrl the URL of the OCSP responder service to be used * @return true if the certificate is known by the responder and good, false if it is unknown or revoked * @throws CertificateException on errors when dealing with certificates * @throws IOException on I/O errors * @throws OCSPException on OCSP protocol errors */ static ValidationResult checkWithOCSP(X509Certificate certificate, X509Certificate issuerCertificate, String responderUrl) throws CertificateException, IOException, OCSPException { try { OCSPReq request = buildOCSPRequest(certificate, issuerCertificate); URL url = new URL(responderUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Content-Type", "application/ocsp-request"); connection.setRequestProperty("Accept", "application/ocsp-response"); connection.setDoOutput(true); DataOutputStream output = new DataOutputStream(new BufferedOutputStream(connection.getOutputStream())); output.write(request.getEncoded()); output.flush(); output.close(); int statusCode = connection.getResponseCode(); if (statusCode == 301 || statusCode == 302 || statusCode == 307) { return checkWithOCSP(certificate, issuerCertificate, connection.getHeaderField("Location")); } else if (statusCode < 200 || statusCode >= 300) { throw new IOException(); } return isOCSPResponseValid(new OCSPResp( getBytesFromStream((InputStream) connection.getContent(), connection.getContentLength())), issuerCertificate); } catch (OperatorCreationException e) { //throw new RuntimeException(e); throw new CertificateException(e); } catch (CertException e) { //throw new RuntimeException(e); throw new CertificateException(e); } } /** * Checks a response received from the OCSP service. * * @param response the OCSP service response * @param issuerCertificate X509Certificate of the certificate being checked * @return true if the certificate is known by the responder and good, false if it is unknown or revoked * @throws CertificateException on errors when dealing with certificates * @throws IOException on I/O errors * @throws OCSPException on OCSP protocol errors * @throws OperatorCreationException on errors from the crypto library (hopefully never) * @throws CertException on certificate handling errors */ private static ValidationResult isOCSPResponseValid(OCSPResp response, X509Certificate issuerCertificate) throws IOException, OCSPException, OperatorCreationException, CertificateException, CertException { if (response.getStatus() != OCSPRespBuilder.SUCCESSFUL) { throw new IOException(); } BasicOCSPResp validations = (BasicOCSPResp) response.getResponseObject(); assert validations.getResponses().length == 1; CertificateStatus status = validations.getResponses()[0].getCertStatus(); X509CertificateHolder signer = validations.getCerts()[0]; ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().build(signer); //return (status == null) && validations.isSignatureValid(verifier) && isValidSigner(signer, issuerCertificate); return new ValidationResult(status, validations.isSignatureValid(verifier), isValidSigner(signer, issuerCertificate)); } /** * Check if the OCSP response has been signed by a legitimate authority. From the RFC: * * All definitive response messages SHALL be digitally signed. The key * used to sign the response MUST belong to one of the following: * * -- the CA who issued the certificate in question * -- a Trusted Responder whose public key is trusted by the requester * -- a CA Designated Responder (Authorized Responder) who holds a * specially marked certificate issued directly by the CA, indicating * that the responder may issue OCSP responses for that CA * * @param signer * @param issuerCertificate * @return true if the signer is valid. false otherwise * @throws CertificateException on certificate handling errors * @throws CertException if the signature cannot be processed or is inappropriate * @throws OperatorCreationException on errors from the crypto library (hopefully never) */ private static boolean isValidSigner(X509CertificateHolder signer, X509Certificate issuerCertificate) throws CertificateException, CertException, OperatorCreationException { X509Certificate signerCertificate = new JcaX509CertificateConverter().getCertificate(signer); ContentVerifierProvider verifier = new JcaContentVerifierProviderBuilder().build(issuerCertificate); return signerCertificate.equals(issuerCertificate) || signer.isSignatureValid(verifier); } /** * Build a request to validate a certificate with an OCSP responder. * * @param certificate the certificate to be validated * @param issuerCertificate the certificate of the issuer authority * @return the request object * @throws CertificateException on errors when dealing with certificates * @throws OCSPException on OCSP protocol errors * @throws OperatorCreationException on errors from the crypto library (hopefully never) */ private static OCSPReq buildOCSPRequest(X509Certificate certificate, X509Certificate issuerCertificate) throws OperatorCreationException, CertificateEncodingException, OCSPException { DigestCalculator sha1; sha1 = new BcDigestCalculatorProvider().get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); X509CertificateHolder issuer = new JcaX509CertificateHolder(issuerCertificate); CertificateID id = new CertificateID(sha1, issuer, certificate.getSerialNumber()); OCSPReqBuilder gen = new OCSPReqBuilder(); BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis()); Extension extension = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, false, new DEROctetString( nonce.toByteArray())); gen.addRequest(id); gen.setRequestExtensions(new Extensions(new Extension[] { extension })); OCSPReq request = gen.build(); return request; } /** * Read data from a stream. * * @param stream the stream to be read * @param length number of bytes to read * @return the data read * @throws EOFException if the file has less than length bytes available to read * @throws IOException on I/O errors */ private static byte[] getBytesFromStream(InputStream stream, int length) throws IOException { byte[] bytes = new byte[length]; int numRead = 0; for (int offset = 0; offset < length; offset += (numRead = stream.read(bytes, offset, length - offset))) { if (numRead < 0) { throw new EOFException(); } } stream.close(); return bytes; } }