Introducción.
En esta entrada vamos a mostrar como realizar una petición a un servidor OCSP para determinar el estado de revocación de un certificado.
En el ejemplo se han usado las librerías Bouncy Castle (JDK 1.5 - JDK 1.7) disponibles en los repositorios MAVEN.
Dependencias.
Las dependencias del proyecto son:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>es.ine.sgtic</groupId>
<artifactId>cliente-ocsp</artifactId>
<version>1.0</version>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.48</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk15on</artifactId>
<version>1.48</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.48</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcmail-jdk15on</artifactId>
<version>1.48</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk15on</artifactId>
<version>1.48</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
Codigo.
El código fuente del ejemplo es el siguiente:
package es.ine.sgtic;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
public class TestOCSP {
// private static final String URL_OCSP_RESPONDER = "http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder";
// private static final String URL_OCSP_RESPONDER = "http://ocspape.cert.fnmt.es/ocspape/OcspResponder";
// private static final String URL_OCSP_RESPONDER = "http://ocsp.dnie.es";
private static final String URL_OCSP_RESPONDER = "http://ocsp.dnielectronico.es/";
public static void main (String args[]) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, OCSPException, OperatorCreationException{
//Cargamos el almacen de certificados que contiene el certificado a verificar
final InputStream fis = TestOCSP.class.getClassLoader().getResourceAsStream("almacen.p12");
final KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(fis,"passwordAlmacen".toCharArray());
//Sacamos el certificado del que necesitamos conocer su estado de revocacion
final X509Certificate certAnalizable = (X509Certificate)ks.getCertificate("aliasCert");
//Sacamos el certificado raíz de la cadena de certificación
final Certificate[] chain = ks.getCertificateChain("aliasCert");
final X509Certificate certRaiz = (X509Certificate)chain[1];
//Se carga el proveedor necesario para la petición OCSP
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
//Generamos el ID del cetificado que estamos buscando
final CertificateID id = new CertificateID(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build().get(CertificateID.HASH_SHA1), new X509CertificateHolder(certRaiz.getEncoded()), certAnalizable.getSerialNumber());
//Instanciamos el generador de requestOCSP
final OCSPReqBuilder ocspPeticionBuilder = new OCSPReqBuilder();
ocspPeticionBuilder.addRequest(id);
//
// //create details for nonce extension
// final BigInteger nonce = BigInteger.valueOf(System.currentTimeMillis());
// final Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, new DEROctetString(nonce.toByteArray()));
// ocspPeticionBuilder.setRequestExtensions(new Extensions(new Extension[] { ext }));
//Se genera la petición con el certificado a verificar y su número de serie
final OCSPReq ocspPeticion = ocspPeticionBuilder.build();
//Se establece la conexión HTTP con el ocsp del DNIe
final URL url = new URL(URL_OCSP_RESPONDER);
final HttpURLConnection con = (HttpURLConnection)url.openConnection();
//Se configuran las propiedades de la petición HTTP
con.setRequestProperty("Content-Type", "application/ocsp-request");
con.setRequestProperty("Accept", "application/ocsp-response");
con.setDoOutput(true);
final OutputStream out = con.getOutputStream();
final DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));
//Se obtiene la respuesta del servidos OCSP del DNIe
dataOut.write(ocspPeticion.getEncoded());
dataOut.flush();
dataOut.close();
//Se parsea la respuesta y se obtiene el estado del certificado retornado por el OCSP
final InputStream in = (InputStream)con.getContent();
final OCSPResp ocspRespuesta = new OCSPResp(in);
int estado = ocspRespuesta.getStatus();
//Analizamos la respuesta
if (estado==OCSPResp.SUCCESSFUL) {
System.out.println("OCSP-RESPONDER: PETICION TRATADA CORRECTAMENTE");
System.out.println(tratarRespuestaOK(ocspRespuesta));
} else if (estado==OCSPResp.MALFORMED_REQUEST) {
System.out.println("OCSP-RESPONDER: ERROR. PETICION MAL FORMADA");
} else if (estado==OCSPResp.INTERNAL_ERROR) {
System.out.println("OCSP-RESPONDER: ERROR. ERROR INTERNO DEL SERVIDOR");
} else if (estado==OCSPResp.TRY_LATER) {
System.out.println("OCSP-RESPONDER: ERROR. REPETIR LA PETICION MAS TARDE");
} else if (estado==OCSPResp.SIG_REQUIRED) {
System.out.println("OCSP-RESPONDER: ERROR. PETICION DEBE SER FIRMADA");
} else if (estado==OCSPResp.UNAUTHORIZED) {
System.out.println("OCSP-RESPONDER: ERROR. PETICION OCSP NO ESTA AUTORIZADA");
} else {
System.out.println("OCSP-RESPONDER: ERROR. TIPO DE RESPUESTA DESCONOCIDO");
}
}
private static String tratarRespuestaOK(final OCSPResp ocspRespuesta) throws OCSPException, IOException {
//TODO:Afinar este método
final Object responseObject = ocspRespuesta.getResponseObject();
final BasicOCSPResp basicOCSPResp = (BasicOCSPResp) responseObject;
final SingleResp[] responses = basicOCSPResp.getResponses();
if (responses.length == 1) {
final SingleResp resp = responses[0];
final Object status = resp.getCertStatus();
if (status == CertificateStatus.GOOD) {
return "ESTADO DEL CERTIFICADO: VALIDO";
}
else if (status instanceof org.bouncycastle.ocsp.RevokedStatus) {
return "ESTADO DEL CERTIFICADO: REVOCADO";
}
else {
return "ESTADO DEL CERTIFICADO: DESCONOCIDO";
}
}
return "VARIAS RESPUESTAS. AFINAR EL CODIGO";
}
}
Alternativas.
Una posible alternativa a picar código en Java es usar openssl.
Las peticiones a los cuatro servidores OCSP indicados en el código equivaldrían a:
openssl ocsp -url http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocspape.cert.fnmt.es/ocspape/OcspResponder -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocsp.dnie.es -resp_text -issuer FNMT.pem -cert aliasCert.pem
openssl ocsp -url http://ocsp.dnielectronico.es/ -resp_text -issuer FNMT.pem -cert aliasCert.pem
Pendiente.
Sería interesante realizar las pruebas con un certificado válido, uno revocado y uno caducado (expirado) para analizar a fondo la respuesta contenida en el objeto BasicOCSPResp. En las pruebas, con un certificado expirado tenemos la salida:
OCSP-RESPONDER: PETICION TRATADA CORRECTAMENTE
ESTADO DEL CERTIFICADO: DESCONOCIDO
Hay que profundizar también en el mecanismo para firmar la petición, dado que por ejemplo el servidor: http://apus.cert.fnmt.es/appsUsuario/ocsp/OcspResponder pide la petición firmada.
Por último conviene aclarar si cualquier servidor OCSP ofrece información sobre cualquier certificado o solo por los emitidos por ciertas CA, de ahí el exceso de respuestas con: "ESTADO DEL CERTIFICADO: DESCONOCIDO".
Enlaces de interes:
http://tools.ietf.org/html/rfc2560
http://www.bouncycastle.org/latest_releases.html
Hola.
ResponderEliminarEl artículo está muy bien, pero estoy teniendo problemas con la conexión cuando intento realizar la siguiente operación
final DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));
en este caso me da el siguiente error:
200 : OK
true
Exception in thread "main" java.net.ProtocolException: Cannot write output after reading input.
sabes cual puede ser el motivo???
Hola
ResponderEliminarEn la línea
final OutputStream out = con.getOutputStream();
Recogemos la respuesta que es un 200, pero que tiene que tener contenido que luego volcamos sobre ocspPeticion:
final DataOutputStream dataOut = new DataOutputStream(new BufferedOutputStream(out));
dataOut.write(ocspPeticion.getEncoded());
Puede que no te esté llegando nada, si es así habla con la gente responsable del servicio OCSP para que te comenten porqué puede estar llegándote vacío
Un saludo