View Javadoc

1   /**
2    * LICENCIA LGPL:
3    * 
4    * Esta librería es Software Libre; Usted puede redistribuirla y/o modificarla
5    * bajo los términos de la GNU Lesser General Public License (LGPL) tal y como 
6    * ha sido publicada por la Free Software Foundation; o bien la versión 2.1 de 
7    * la Licencia, o (a su elección) cualquier versión posterior.
8    * 
9    * Esta librería se distribuye con la esperanza de que sea útil, pero SIN 
10   * NINGUNA GARANTÍA; tampoco las implícitas garantías de MERCANTILIDAD o 
11   * ADECUACIÓN A UN PROPÓSITO PARTICULAR. Consulte la GNU Lesser General Public 
12   * License (LGPL) para más detalles
13   * 
14   * Usted debe recibir una copia de la GNU Lesser General Public License (LGPL) 
15   * junto con esta librería; si no es así, escriba a la Free Software Foundation 
16   * Inc. 51 Franklin Street, 5º Piso, Boston, MA 02110-1301, USA o consulte
17   * <http://www.gnu.org/licenses/>.
18   *
19   * Copyright 2011 Agencia de Tecnología y Certificación Electrónica
20   */
21  package es.accv.arangi.base.certificate.validation;
22  
23  import java.io.ByteArrayInputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.io.InputStream;
29  import java.security.PublicKey;
30  
31  import javax.security.auth.x500.X500Principal;
32  
33  import org.apache.log4j.Logger;
34  import org.bouncycastle.asn1.ASN1Encoding;
35  import org.bouncycastle.asn1.DEROctetString;
36  import org.bouncycastle.asn1.DERSequence;
37  import org.bouncycastle.asn1.DERTaggedObject;
38  import org.bouncycastle.asn1.x500.X500Name;
39  import org.bouncycastle.cert.X509CertificateHolder;
40  import org.bouncycastle.cert.ocsp.BasicOCSPResp;
41  import org.bouncycastle.cert.ocsp.OCSPException;
42  import org.bouncycastle.cert.ocsp.OCSPResp;
43  import org.bouncycastle.operator.OperatorCreationException;
44  import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder;
45  
46  import es.accv.arangi.base.ArangiObject;
47  import es.accv.arangi.base.certificate.Certificate;
48  import es.accv.arangi.base.exception.certificate.NormalizeCertificateException;
49  import es.accv.arangi.base.exception.certificate.validation.MalformedOCSPResponseException;
50  import es.accv.arangi.base.util.validation.ValidationResult;
51  
52  /**
53   * Clase que representa una respuesta OCSP de acuerdo a la especificación indicada
54   * en el punto 4.2.1. de la <a href="http://www.ietf.org/rfc/rfc2560.txt" target="rfc">RFC-2560</a>. 
55   * Concretamente esta clase envuelve a una respuesta que contiene una respuesta
56   * básica OCSP: BasicOCSPResponse. 
57   * 
58   * @author <a href="mailto:jgutierrez@accv.es">José M Gutiérrez</a>
59   */
60  public class OCSPResponse extends ArangiObject {
61  
62  	/*
63  	 * Logger de la clase
64  	 */
65  	static Logger logger = Logger.getLogger(OCSPResponse.class);
66  	
67  	/*
68  	 * Respuesta básica OCSP
69  	 */
70  	private OCSPResp ocspResponse;
71  	
72  	/*
73  	 * Respuesta básica OCSP
74  	 */
75  	private BasicOCSPResp basicOcspResponse;
76  	
77  	/*
78  	 * Respuestas individuales
79  	 */
80  	private CertificateOCSPResponse[] responses;
81  	
82  	//-- Constructores
83  	
84  	/**
85  	 * Constructor que inicializa el objeto a partir de una respuesta OCSP.
86  	 * 
87  	 * @param ocspResponse Respuesta OCSP
88  	 * @throws MalformedOCSPResponseException La respuesta OCSP no contiene una respuesta OCSP 
89  	 * 	básica
90  	 */
91  	public OCSPResponse (OCSPResp ocspResponse) throws MalformedOCSPResponseException {
92  		//-- Inicializar el objeto respuesta
93  		try {
94  			this.ocspResponse = ocspResponse;
95  			this.basicOcspResponse = (BasicOCSPResp)ocspResponse.getResponseObject();
96  		} catch (OCSPException e) {
97  			logger.info ("[OCSPResponse]::No se encuentra una respuesta básica OCSP en la respuesta OCSP", e);
98  			throw new MalformedOCSPResponseException ("No se encuentra una respuesta básica OCSP en la respuesta OCSP", e);
99  		} catch (ClassCastException e) {
100 			logger.info ("[OCSPResponse]::El objeto contenido en la respuesta OCSP no es una respuesta básica OCSP y no puede ser tratada por esta clase", e);
101 			throw new MalformedOCSPResponseException ("El objeto contenido en la respuesta OCSP no es una respuesta básica OCSP y no puede ser tratada por esta clase", e);
102 		}
103 
104 		//-- Inicializar lista de respuestas individuales
105 		this.responses = getResponses (basicOcspResponse);
106 	}
107 	
108 	/**
109 	 * Constructor que inicializa el objeto a partir de un fichero que contiene la respuesta
110 	 * OCSP.
111 	 * 
112 	 * @param fileOCSPResponse Fichero que contiene la respuesta OCSP
113 	 * @throws MalformedOCSPResponseException El contenido no se corresponde con una respuesta
114 	 * 	OCSP o no puede ser leído
115 	 * @throws FileNotFoundException El fichero no existe
116 	 */
117 	public OCSPResponse (File fileOCSPResponse) throws MalformedOCSPResponseException, FileNotFoundException {
118 		
119 		FileInputStream fis = null;
120 		try {
121 			fis = new FileInputStream (fileOCSPResponse);
122 			initialize (fis);
123 		} finally {
124 			if (fis != null) {
125 				try {
126 					fis.close();
127 				} catch (IOException e) {
128 					logger.info ("[OCSPResponse(file)]::No se ha podido cerrar el stream de lectura al fichero: " + fileOCSPResponse.getAbsolutePath(), e);
129 				}
130 			}
131 		}
132 	}
133 	
134 	/**
135 	 * Constructor que inicializa el objeto a partir de un array de bytes con el contenido de 
136 	 * la respuesta OCSP.
137 	 * 
138 	 * @param bytesOCSPResponse Array de bytes con el contenido de la respuesta OCSP
139 	 * @throws MalformedOCSPResponseException El contenido no se corresponde con una respuesta
140 	 * 	OCSP
141 	 */
142 	public OCSPResponse (byte[] bytesOCSPResponse) throws MalformedOCSPResponseException {
143 		initialize (new ByteArrayInputStream (bytesOCSPResponse));
144 	}
145 	
146 	/**
147 	 * Constructor que inicializa el objeto a partir de un stream de lectura que apunta
148 	 * al contenido de la respuesta OCSP.
149 	 * 
150 	 * @param isOCSPResponse Stream de lectura al contenido de la respuesta OCSP
151 	 * @throws MalformedOCSPResponseException El contenido no se corresponde con una respuesta
152 	 * 	OCSP o no puede ser leído
153 	 */
154 	public OCSPResponse (InputStream isOCSPResponse) throws MalformedOCSPResponseException {
155 		initialize (isOCSPResponse);
156 	}
157 	
158 	//-- Métodos públicos
159 	
160 	/**
161 	 * Comprueba la firma de una respuesta OCSP. Este método sólo comprueba que los
162 	 * datos firmados no han sido modificados desde su firma.
163 	 * 
164 	 * @return Cierto si no se han modificado los datos de la respuesta OCSP
165 	 */
166 	public boolean isSignatureValid () {
167 		logger.debug ("[OCSPResponse.isSignatureValid]::Entrada");
168 		
169 		//-- Obtener el certificado de la firma
170 		Certificate signCertificate = getSignatureCertificate();
171 		
172 		//-- Validar que los datos de la firma no han cambiado
173 	 	PublicKey keyCertificadoOCSP = signCertificate.getPublicKey();
174 	 	try {
175 	 	    if (!basicOcspResponse.isSignatureValid(new JcaContentVerifierProviderBuilder().build(keyCertificadoOCSP))) {
176 	 	    	logger.debug ("[OCSPResponse.isSignatureValid]::Los datos de la respuesta OCSP han sido modificados desde su firma");
177 	 	    	return false;
178 	 	    }
179 	 	} catch (OCSPException e) {
180 	 		logger.info ("[OCSPResponse.isSignatureValid]::No se puede validar la firma", e);
181 	 		return false;
182 		} catch (OperatorCreationException e) {
183 	 		logger.info ("[OCSPResponse.isSignatureValid]::No se puede construir el validador de la firma", e);
184 	 		return false;
185 		}
186 	 	
187 	 	return true;
188 
189 	}
190 
191 	/**
192 	 * Obtiene el certificado con el que se firmó la respuesta OCSP
193 	 * 
194 	 * @return Certificado de firma de la respuesta OCSP
195 	 */
196 	public Certificate getSignatureCertificate () {
197 		logger.debug ("[OCSPResponse.getSignatureCertificate]::Entrada");
198 		
199 		return OCSPResponse.getSignatureCertificate(this.basicOcspResponse);
200 	}
201 	
202 	/**
203 	 * Obtiene las respuestas individuales para cada certificado del que se pidió
204 	 * su estado.
205 	 * 
206 	 * @return Respuestas individuales dentro de la respuesta OCSP
207 	 */
208 	public CertificateOCSPResponse[] getSingleResponses () {
209 		return responses;
210 	}
211 	
212 	/**
213 	 * Devuelve el estado de la respuesta para el certificado que se pasa como parámetro, 
214 	 * que será alguna de las siguientes constantes de la clase {@link CertificateValidator CertificateValidator}:<br>
215 	 * 
216 	 * <ul>
217 	 * 	<li><i>RESULT_CERTIFICATE_VALID</i>: El certificado es válido.</li>
218 	 * 	<li><i>RESULT_CERTIFICATE_REVOKED</i>: El certificado está revocado</li>
219 	 * 	<li><i>RESULT_CERTIFICATE_UNKNOWN</i>: El certificado es desconocido para el OCSP</li>
220 	 * 	<li><i>RESULT_CERTIFICATE_CANNOT_BE_VALIDATED</i>: No hay ninguna respuesta para el 
221 	 * 	certificado</li>
222 	 * </ul>
223 	 * 
224 	 * @param certificate Certificado que originó la respuesta OCSP
225 	 * @return Estado de la respuesta
226 	 */
227 	public int getStatus (ValidateCertificate certificate) {
228 		logger.debug("[OCSPResponse.getStatus]::Entrada");
229 		
230 		for (int i = 0; i < responses.length; i++) {
231 			if (responses[i].match(certificate)) {
232 				return responses[i].getStatus();
233 			}
234 		}
235 		
236 		return ValidationResult.RESULT_CERTIFICATE_CANNOT_BE_VALIDATED;
237 	}
238 	
239 	/**
240 	 * Devuelve el estado de la respuesta, que será alguna de las siguientes constantes 
241 	 * de la clase {@link CertificateValidator CertificateValidator}:<br>
242 	 * 
243 	 * <ul>
244 	 * 	<li><i>RESULT_CERTIFICATE_VALID</i>: El certificado es válido.</li>
245 	 * 	<li><i>RESULT_CERTIFICATE_REVOKED</i>: El certificado está revocado</li>
246 	 * 	<li><i>RESULT_CERTIFICATE_UNKNOWN</i>: El certificado es desconocido para el OCSP</li>
247 	 * 	<li><i>RESULT_CERTIFICATE_CANNOT_BE_VALIDATED</i>: No hay ninguna respuesta</li>
248 	 * </ul><br>
249 	 * 
250 	 * Si la petición OCSP incluía varios certificados, habrán varias respuestas en 
251 	 * este objeto. En este caso es mejor utilizar el método {@link #getStatus(ValidateCertificate) getStatus} 
252 	 * pasándole el certificado del que se quiere conocer la respuesta.
253 	 * 
254 	 * @return Estado de la primera respuesta contenida en este objeto
255 	 */
256 	public int getStatus () {
257 		logger.debug("[OCSPResponse.getStatus]::Entrada");
258 		
259 		if (responses.length == 0) {
260 			return ValidationResult.RESULT_CERTIFICATE_CANNOT_BE_VALIDATED; 
261 		} else {
262 			return responses[0].getStatus();
263 		}
264 	}
265 	
266 	/**
267 	 * Obtiene el valor contenido en el ResponderID de la respuesta OCSP si es un X500Name.
268 	 * 
269 	 * @return ResponderID de la respuesta OCSP si es un X500Name. Si es un keyhash devuelve null;
270 	 */
271 	public String getResponderIdName () {
272 		logger.debug("[OCSPResponse.getResponderIdName]::Entrada");
273 		
274 		DERTaggedObject derTagged = (DERTaggedObject)basicOcspResponse.getResponderId().toASN1Primitive().toASN1Primitive();
275 		if (!(derTagged.getObject() instanceof DERSequence)) {
276 			logger.debug("[OCSPResponse.getResponderIdName]::Es un KeyHash y no un X509Name");
277 			return null;
278 		}
279 		String valorResponder = X500Name.getInstance((DERSequence)derTagged.getObject()).toString();
280 		X500Name certX509Principal = new X500Name(valorResponder);
281 		X500Principal cerX500Principal;
282 		try {
283 			cerX500Principal = new X500Principal(certX509Principal.getEncoded(ASN1Encoding.DER));
284 		} catch (IOException e) {
285 			logger.debug("[OCSPResponse.getResponderIdName]::No se puede obtener el ResponderIDName", e);
286 			return null;
287 		}
288 		return cerX500Principal.getName();
289 
290 	}
291 	
292 	/**
293 	 * Obtiene el valor contenido en el ResponderID de la respuesta OCSP si es un KeyHash.
294 	 * 
295 	 * @return ResponderID de la respuesta OCSP si es un KeyHash. Si es un X509Name devuelve null;
296 	 */
297 	public String getResponderIdKeyHash () {
298 		logger.debug("[OCSPResponse.getResponderIdKeyHash]::Entrada");
299 		
300 		DERTaggedObject derTagged = (DERTaggedObject)basicOcspResponse.getResponderId().toASN1Primitive().toASN1Primitive();
301 		if (derTagged.getObject() instanceof DERSequence) {
302 			logger.debug("[OCSPResponse.getResponderIdKeyHash]::Es un X509Name y no un KeyHash");
303 			return null;
304 		}
305 		return ((DEROctetString)derTagged.getObject()).toString();
306 
307 	}
308 	
309 	
310 	/**
311 	 * Obtiene el contenido de la respuesta OCSP de forma que puede ser guardada.
312 	 * 
313 	 * @return Contenido de la respuesta OCSP
314 	 */
315 	public byte[] toDER () {
316 		try {
317 			return ocspResponse.getEncoded();
318 		} catch (IOException e) {
319 			//-- En la inicialización ya se comprueban estos errores
320 			logger.info("[OCSPResponse.toDER]::Error de entrada/salida", e);
321 			return null;
322 		}
323 	}
324 	
325 	/**
326 	 * Obtiene la respuesta básica contenida en la respuesta. En algunos tipos de
327 	 * firma es esta respuesta la que se guarda como respuesta OCSP.
328 	 * 
329 	 * @return Respuesta OCSP básica
330 	 */
331 	public BasicOCSPResp getBasicOCSPResponse () {
332 		return basicOcspResponse;
333 	}
334 	
335 	/**
336 	 * Obtiene la lista de respuestas contenidas en la respuesta OCSP: una por cada
337 	 * certificado que se mandó validar cuando se hizo la llamada al servidor OCSP.
338 	 * 
339 	 * @param basicOcspResponse Respuesta básica OCSP
340 	 * @return Array de respuestas contenidas en la respuesta OCSP
341 	 */
342 	public static CertificateOCSPResponse[] getResponses (BasicOCSPResp basicOcspResponse) {
343 		
344 		logger.debug ("[OCSPResponse.getListResponses]::Entrada::" + basicOcspResponse);
345 		
346      	//-- Obtener la lista
347 		CertificateOCSPResponse[] responses = new CertificateOCSPResponse [basicOcspResponse.getResponses().length];
348      	for (int i = 0; i < basicOcspResponse.getResponses().length; i++) {
349      		responses [i] = new CertificateOCSPResponse (getSignatureCertificate(basicOcspResponse),
350      				basicOcspResponse.getResponses() [i]);
351 		}
352      	
353      	return responses;
354 	}
355 	
356 	/**
357 	 * Obtiene el certificado con el que se firmó la respuesta OCSP que se pasa
358 	 * como parámetro
359 	 * 
360 	 * @param basicOcspResponse Respuesta básica OCSP
361 	 * @return Certificado de firma de la respuesta OCSP
362 	 */
363 	public static Certificate getSignatureCertificate (BasicOCSPResp basicOcspResponse) {
364 		logger.debug ("[OCSPResponse.getSignatureCertificate]::Entrada::" + basicOcspResponse);
365 		
366 		X509CertificateHolder[] certificatePath = basicOcspResponse.getCerts();
367 	 
368 	 	// Recuperamos el inicio del camino ( suponemos que el resto de
369 	 	// certificados estara ya
370 	 	if (certificatePath == null || certificatePath.length == 0) {
371 	 		logger.info("[OCSPResponse.getSignatureCertificate]::La respuesta OCSP no está firmada");
372 	 	    return null;
373 	 	}
374 	 	
375 	 	try {
376 			return new Certificate (certificatePath[0]);
377 		} catch (NormalizeCertificateException e) {
378 			logger.info("[OCSPResponse.getSignatureCertificate]::El certificado no puede ser normalizado para el proveedor criptográfico de Arangi", e);
379 			return null;
380 		}
381 
382 	}
383 	
384 	//-- Métodos privados
385 	
386 	/*
387 	 * Inicializa la respuesta OCSP en base a un stream de lectura
388 	 */
389 	private void initialize (InputStream is) throws MalformedOCSPResponseException {
390 		
391 		//-- Inicializar respuesta
392 		try {
393 			ocspResponse = new OCSPResp (is);
394 			basicOcspResponse = (BasicOCSPResp)ocspResponse.getResponseObject();
395 		} catch (IOException e) {
396 			logger.info ("[OCSPResponse.initialize]::El objeto no es una respuesta OCSP bien formada", e);
397 			throw new MalformedOCSPResponseException ("El objeto no es una respuesta OCSP bien formada", e);
398 		} catch (OCSPException e) {
399 			logger.info ("[OCSPResponse.initialize]::No se encuentra una respuesta básica OCSP en la respuesta OCSP", e);
400 			throw new MalformedOCSPResponseException ("No se encuentra una respuesta básica OCSP en la respuesta OCSP", e);
401 		} catch (ClassCastException e) {
402 			logger.info ("[OCSPResponse.initialize]::El objeto contenido en la respuesta OCSP no es una respuesta básica OCSP y no puede ser tratada por esta clase", e);
403 			throw new MalformedOCSPResponseException ("El objeto contenido en la respuesta OCSP no es una respuesta básica OCSP y no puede ser tratada por esta clase", e);
404 		}
405 
406 		
407 		//-- Inicializar lista de respuestas individuales
408 		this.responses = getResponses (basicOcspResponse);
409 	}
410 	
411 }