Java tutorial
/* * File: OcspValidatorHandler.java * * Created on Nov 6, 2007 * * * Copyright 2006-2007 Felix Garcia Borrego (borrego@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.viafirma.nucleo.validacion; import java.io.BufferedOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.cert.CertPathValidatorException; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; import java.util.Set; import java.util.Vector; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers; import org.bouncycastle.asn1.ocsp.OCSPResponseStatus; import org.bouncycastle.asn1.x509.X509Extension; import org.bouncycastle.asn1.x509.X509Extensions; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.ocsp.BasicOCSPResp; import org.bouncycastle.ocsp.CertificateID; import org.bouncycastle.ocsp.CertificateStatus; import org.bouncycastle.ocsp.OCSPException; import org.bouncycastle.ocsp.OCSPReq; import org.bouncycastle.ocsp.OCSPReqGenerator; import org.bouncycastle.ocsp.OCSPResp; import org.bouncycastle.ocsp.SingleResp; import org.viafirma.excepciones.CodigoError; import org.viafirma.excepciones.ExcepcionErrorInterno; import org.viafirma.util.SendMailUtil; /** * Implementa el protocolo de validacin de certificados OCSP compatible con el * RFC 2560. (Online Certificate Status Protocol) Es compatible con la * validacin del edni. * * * @author Felix Garcia Borrego (borrego@gmail.com) */ public class OcspValidatorHandler { private final Log log = LogFactory.getFactory().getInstance(SendMailUtil.class); /** * URL de conexin por defecto para la validacin de certificados basados en * el edni. */ public static final String URL_OCSP_EDNI = "http://ocsp.dnie.es/"; /** * Crea un nuevo validador de ocsps * * @param validacionOnline * @param certificadosConfianza */ public OcspValidatorHandler(boolean validacionOnline, Set<TrustAnchor> certificadosConfianza) { this.validacionOnline = validacionOnline; this.certificadosConfianza = certificadosConfianza; } /** * Valida mediante ocsp RFC 2560, el certificado indicado. Recuperando desde * certificadosConfianza el certificado emisor. * * @param certificadoX509 * @return * @throws CertPathValidatorException */ public CodigoError validarOCSP(X509Certificate certificadoX509) throws CertPathValidatorException { // Recuperamos el certificado emisor try { X509Certificate issuerX509 = getIssuerX509(certificadoX509); return validarOCSP(certificadoX509, issuerX509); } catch (ExcepcionErrorInterno e) { log.warn("No se puede validar el certificado por OCSP." + e.getMessage() + ",Cdigo: " + e.getCodError()); return CodigoError.ERROR_VALIDACION_AUTORIDAD_NO_RECONOCIDA; } } /** * Retorna el certificado de condianza. * * @param certificadoX509 * @return * @throws ExcepcionErrorInterno * No se encuentra el certificado de confianza. */ private X509Certificate getIssuerX509(X509Certificate certificadoX509) throws ExcepcionErrorInterno { for (TrustAnchor trust : certificadosConfianza) { if (trust.getTrustedCert().getSubjectDN().getName().equals(certificadoX509.getIssuerDN().getName())) { return trust.getTrustedCert(); } } log.warn(" No hemos encontrado el certificado de confianza."); throw new ExcepcionErrorInterno(CodigoError.ERROR_VALIDACION_AUTORIDAD_NO_RECONOCIDA); } /** * Valida mediante ocsp RFC 2560, el certificado indicado. * * @param certificadoX509 * Certificado que deseamos validar. * @param certificadoX509Emisor * Certificado emisor del certificado a validar. * @return * @throws CertPathValidatorException */ private CodigoError validarOCSP(X509Certificate certificadoX509, X509Certificate certificadoX509Emisor) throws CertPathValidatorException { // Si la validacin esta desactivada consideramos el certificado // validado. if (validacionOnline) { try { // Generamos la peticin OCSP para el certificado. OCSPReq request = generateRequest(certificadoX509, certificadoX509Emisor); // Recuperamos la url desde la que realizar la validacin OCSP String url = getUrlOCSP(certificadoX509); // Bytes del request byte[] byteRequest = request.getEncoded(); // Enviamos la peticin y recuperamos la respuesta InputStream inResponse = sendRequest(url, byteRequest); OCSPResp ocspResponse = new OCSPResp(inResponse); // but why Response Status is 6(No Valid)..? if (OCSPResponseStatus.SUCCESSFUL == ocspResponse.getStatus()) { log.info("Obtenida respuesta correcta OCSP. Estado:" + ocspResponse.getStatus()); CertificateID certID = new CertificateID(CertificateID.HASH_SHA1, certificadoX509Emisor, certificadoX509.getSerialNumber()); BasicOCSPResp brep = (BasicOCSPResp) ocspResponse.getResponseObject(); // Comprobamos que la respuesta OCSP no ha sido manipulada y // ha sido firmada por un certificado de confianza. // Recupero el certificado con el que debe haber sido // firmado el ocsp checkOCSP(brep); SingleResp[] singleResp = brep.getResponses(); for (SingleResp resp : singleResp) { CertificateID respCertID = resp.getCertID(); if (respCertID.equals(certID)) { Object status = resp.getCertStatus(); if (status == CertificateStatus.GOOD) { log.debug("OCSPChecker: Status of certificate is: good"); break; } else if (status instanceof org.bouncycastle.ocsp.RevokedStatus) { log.debug("OCSPChecker: Status of certificate is: revoked"); throw new CertPathValidatorException("Certificate has been revoked"); } else if (status instanceof org.bouncycastle.ocsp.UnknownStatus) { log.debug("OCSPChecker: Status of certificate is: unknown"); throw new CertPathValidatorException("Certificate's revocation status is unknown"); } else { log.debug("Status of certificate is: not recognized"); throw new CertPathValidatorException("Unknown OCSP response for certificate"); } } } return CodigoError.OK_CERTIFICADO_VALIDADO; } else { /** * successful (0), --Response has valid confirmations * malformedRequest (1), --Illegal confirmation request * internalError (2), --Internal error in issuer tryLater * (3), --Try again later --(4) is not used sigRequired (5), * --Must sign the request unauthorized (6) --Request * unauthorized */ if (OCSPResponseStatus.MALFORMED_REQUEST == ocspResponse.getStatus()) { log.warn( "Obtenida respuesta Incorrecta OCSP: malformedRequest (1), --Illegal confirmation request. estatus: " + ocspResponse.getStatus()); return CodigoError.ERROR_OCSP_URL; } else if (OCSPResponseStatus.INTERNAL_ERROR == ocspResponse.getStatus()) { log.warn( "Obtenida respuesta Incorrecta OCSP: internalError (2),--Internal error in issuer. estatus: " + ocspResponse.getStatus()); return CodigoError.ERROR_OCSP_INTERNAL_ERROR; } else if (OCSPResponseStatus.TRY_LATER == ocspResponse.getStatus()) { log.warn("Obtenida respuesta Incorrecta OCSP: tryLater (3), --Try again later. estatus: " + ocspResponse.getStatus()); return CodigoError.ERROR_OCSP_TRY_LATER; } else if (OCSPResponseStatus.SIG_REQUIRED == ocspResponse.getStatus()) { log.warn( "Obtenida respuesta Incorrecta OCSP: sigRequired(5),--Must sign the request. estatus: " + ocspResponse.getStatus()); return CodigoError.ERROR_OCSP_INTERNAL_ERROR; } else if (OCSPResponseStatus.SIG_REQUIRED == ocspResponse.getStatus()) { log.warn( "Obtenida respuesta Incorrecta OCSP: unauthorized (6)--Request unauthorized. estatus: " + ocspResponse.getStatus()); return CodigoError.ERROR_OCSP_INTERNAL_ERROR; } else { log.warn( "Obtenida una respuesta incorrecta OCSP, probablemente el certificado este caducado."); return CodigoError.ERROR_VALIDACION_CERTIFICADO_CADUCADO; } } } catch (OCSPException e) { log.fatal(CodigoError.ERROR_OCSP_INTERNAL_ERROR, e); return CodigoError.ERROR_OCSP_INTERNAL_ERROR; } catch (ExcepcionErrorInterno e) { log.error("No se puede validar el certificado. ", e); return e.getCodError(); } catch (IOException e) { log.fatal(CodigoError.ERROR_INTERNO, e); return CodigoError.ERROR_INTERNO; } } else { log.info("La validacin online OCSP esta desactivada. Consideramos el certificado Vlido."); return CodigoError.OK_CERTIFICADO_VALIDADO; } } /** * Comprueba que la respuesta OCSP no ha sido manipulada y es correcta. * * @param certificadoX509Emisor * @param brep * @throws OCSPException * @throws CertPathValidatorException */ private void checkOCSP(BasicOCSPResp ocspResponse) throws OCSPException, CertPathValidatorException { // Recuperamos la clave pblica esperada con el OCSP firmo la respuesta. X509Certificate certificatePath[] = null; try { certificatePath = ocspResponse.getCerts(BouncyCastleProvider.PROVIDER_NAME); } catch (NoSuchProviderException e) { throw new CertPathValidatorException("La respuesta OCSP no puede ser validada.", e); } // Recuperamos el inicio del camino ( suponemos que el resto de // certificados estara ya if (certificatePath == null || certificatePath.length == 0) { throw new CertPathValidatorException( "No se ha podido encontrar un certificado en la respuesta OCSP. La respuesta OCSP debe ser firmada por el servidor de OCSP."); } X509Certificate certificadoResponseOCSP = certificatePath[0]; // Recuperamos la clave pblica almacenada en nuestros certificados de // confianza. PublicKey keyCertificadoOCSP = getPublicKeyBySubjectName(certificadoResponseOCSP); if (keyCertificadoOCSP == null) { throw new CertPathValidatorException( "No hay un certificado de confianza asociado a al certificado con el que se firmo esta respuesta OCSP. " + certificadoResponseOCSP.getSubjectDN().getName()); } try { if (!ocspResponse.verify(keyCertificadoOCSP, BouncyCastleProvider.PROVIDER_NAME)) { throw new CertPathValidatorException( "La respuesta OCSP no es vlida, La firma no corresponde a un certificado de confianza."); } } catch (NoSuchProviderException e) { throw new CertPathValidatorException("La respuesta OCSP no puede ser validada.", e); } } /** * Envia los datos de la peticin OCSP y retorna la respuesta. * * @param url * url del servicio OCSP * @param byteRequest * Datos * @return respuesta OCSP * @throws ExcepcionErrorInterno * No se puede enviar la peticin o recuperar la respuest */ private InputStream sendRequest(String url, byte[] byteRequest) throws ExcepcionErrorInterno { try { HttpURLConnection con = null; URL urlOCSP; urlOCSP = new URL(url); // Establecemos la conexin con = (HttpURLConnection) urlOCSP.openConnection(); // Enviamos las cabeceras 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)); // Escribo el request dataOut.write(byteRequest); // Enviamos y cerramos el envio dataOut.flush(); dataOut.close(); // Chequeo la respuesta if (con.getResponseCode() / 100 != 2) { throw new ExcepcionErrorInterno(CodigoError.ERROR_OCSP_NO_DISPONIBLE, con.getResponseMessage() + ",Cdigo: " + con.getResponseCode()); } // Obtengo la respuesta del servidor OCSP InputStream in = (InputStream) con.getContent(); return in; } catch (MalformedURLException e) { throw new ExcepcionErrorInterno(CodigoError.ERROR_OCSP_URL, e); } catch (IOException e) { throw new ExcepcionErrorInterno(CodigoError.ERROR_OCSP_IO, e); } } /** * Recupera la url para la validacin OCSP del certificado indicado. En el * caso de que el certificado sea eDNI el punto OCSP no esta informado, por * lo que utilizamos la url prefijada para el eDNI. * * @param certificadoX509 * certificado a validar. * @return Url */ private String getUrlOCSP(X509Certificate certificadoX509) { if (certificadoX509.getIssuerDN().getName().contains("OU=DNIE")) { if (log.isDebugEnabled()) log.debug("Es un eDNI, utilizando la url:" + URL_OCSP_EDNI); return URL_OCSP_EDNI; } else { // TODO Implementar extraccin del punto OCSP desde los oids. throw new UnsupportedOperationException( "TODO implementar la extraccin de puntos de validacin OCSP desde los oids."); } } /** * Genera una nueva peticin OCSP para el certificado indicado. * * @param certificadoX509 * Certificado que deseamos validar. * @param certificadoX509Emisor * Certificado emisor del certificado a validar. * @return Peticin OCSP * @throws OCSPException */ private OCSPReq generateRequest(X509Certificate certificadoX509, X509Certificate certificadoX509Emisor) throws OCSPException { // 1 -Generamos el identificador CertificateID id = new CertificateID(CertificateID.HASH_SHA1, certificadoX509Emisor, certificadoX509.getSerialNumber()); // 2- Generador de peticiones ocsp OCSPReqGenerator requestGenerator = new OCSPReqGenerator(); requestGenerator.addRequest(id); // 3- extensiones necesarias. RFC 2560 BigInteger time = BigInteger.valueOf(System.currentTimeMillis()); Vector<DERObjectIdentifier> oids = new Vector<DERObjectIdentifier>(); oids.add(OCSPObjectIdentifiers.id_pkix_ocsp_nonce); Vector<X509Extension> values = new Vector<X509Extension>(); values.add(new X509Extension(false, new DEROctetString(time.toByteArray()))); // 4. Aadimos las extensiones necesarias al generador requestGenerator.setRequestExtensions(new X509Extensions(oids, values)); // Generamos la peticin OCSP return requestGenerator.generate(); } /** * Recupera el certificado con el DN indicado * * @param alias * Alias del certificado deseado. * @return Certificado con el alias indicado * @throws ExcepcionErrorInterno */ private PublicKey getPublicKeyBySubjectName(X509Certificate certificadoResponseOCSP) { // ej: C=ES,O=DIRECCION GENERAL DE LA POLICIA,OU=DNIE,OU=FNMT,CN=AV DNIE // FNMT // cn:AV DNIE FNMT String cn = getCN(certificadoResponseOCSP); for (TrustAnchor certificadoConcianza : certificadosConfianza) { String cnConcianza = getCN(certificadoConcianza.getTrustedCert()); // si los CN coninciden, encontrado! if (cn.equals(cnConcianza)) { // Hemos encontrado el certificado de confianza return certificadoConcianza.getTrustedCert().getPublicKey(); } } return null; } /** * Retorna el CN de un certificado. * * @param certificadoResponseOCSP * @return */ private String getCN(X509Certificate certificadoResponseOCSP) { // ej: C=ES,O=DIRECCION GENERAL DE LA POLICIA,OU=DNIE,OU=FNMT,CN=AV DNIE // FNMT // cn:AV DNIE FNMT String cn = StringUtils.substringBetween(certificadoResponseOCSP.getSubjectDN().getName(), "CN=", ","); if (cn == null) { // Puede que este al final de Subcjet cn = StringUtils.substringAfterLast(certificadoResponseOCSP.getSubjectDN().getName(), "CN="); } return cn; } /** * Conjunto de certificados de confianza reconocidos por el sistema. Se * inicializan al arrancar la aplicacin. */ private Set<TrustAnchor> certificadosConfianza; /** * Indica si se conectara remotamente para realizar la validacin. */ private boolean validacionOnline; }