org.viafirma.nucleo.validacion.OcspValidatorHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.viafirma.nucleo.validacion.OcspValidatorHandler.java

Source

/* 
 * 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;

}