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.certificate.validation;
22  
23  import java.util.Arrays;
24  import java.util.Date;
25  import java.util.HashMap;
26  import java.util.Map;
27  import java.util.Properties;
28  
29  import org.apache.log4j.Logger;
30  import org.bouncycastle.asn1.ASN1EncodableVector;
31  import org.bouncycastle.asn1.ASN1Enumerated;
32  import org.bouncycastle.asn1.ASN1InputStream;
33  import org.bouncycastle.asn1.ASN1ObjectIdentifier;
34  import org.bouncycastle.asn1.DEROctetString;
35  import org.bouncycastle.asn1.DERSequence;
36  import org.bouncycastle.asn1.DERTaggedObject;
37  import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
38  import org.bouncycastle.cert.ocsp.BasicOCSPResp;
39  import org.bouncycastle.cert.ocsp.CertificateStatus;
40  import org.bouncycastle.cert.ocsp.OCSPResp;
41  import org.bouncycastle.cert.ocsp.RevokedStatus;
42  
43  import es.accv.arangi.base.certificate.Certificate;
44  import es.accv.arangi.base.certificate.validation.CertificateValidationService;
45  import es.accv.arangi.base.certificate.validation.CertificateValidationServiceResult;
46  import es.accv.arangi.base.certificate.validation.OCSPResponse;
47  import es.accv.arangi.base.exception.certificate.NormalizeCertificateException;
48  import es.accv.arangi.base.exception.certificate.validation.ServiceException;
49  import es.accv.arangi.base.exception.certificate.validation.ServiceNotFoundException;
50  import es.accv.arangi.base.util.Util;
51  import es.accv.arangi.base.util.validation.ValidationResult;
52  import es.gob.afirma.afirma5ServiceInvoker.Afirma5ServiceInvokerContent;
53  import es.gob.afirma.afirma5ServiceInvoker.Afirma5ServiceInvokerFacade;
54  import es.gob.afirma.transformers.TransformersConstants;
55  import es.gob.afirma.transformers.TransformersException;
56  import es.gob.afirma.transformers.TransformersFacade;
57  import es.gob.afirma.utils.DSSConstants.DSSTagsRequest;
58  import es.gob.afirma.utils.DSSConstants.ReportDetailLevel;
59  import es.gob.afirma.utils.DSSConstants.ResultProcessIds;
60  import es.gob.afirma.utils.GeneralConstants;
61  
62  /**
63   * Clase que implementa la validación de certificados mediante llamadas 
64   * a los servicios web DSS de &#64;Firma6.
65   * 
66   * @author <a href="mailto:jgutierrez@accv.es">José Manuel Gutiérrez Núñez</a>
67   *
68   */
69  public class AFirma6CertificateValidationService implements CertificateValidationService {
70  	
71  	/*
72  	 * Minor result: el certificado no está entre los tratados
73  	 */
74  	public static final String AFIRMA_MINOR_RESULT_CERTIFICATE_NOT_SUPPORTED = "urn:afirma:dss:1.0:profile:XSS:resultminor:Certificate:NotSupported";
75  	
76  	/*
77  	 * Minor result: el certificado está revocado
78  	 */
79  	public static final String AFIRMA_MINOR_RESULT_CERTIFICATE_REVOKED = "urn:oasis:names:tc:dss:1.0:profiles:XSS:resultminor:invalid:certificate:Revoked";
80  	
81  	/*
82  	 * Constantes para la clasificación de certificados de @Firma
83  	 */
84  	private static final int AFIRMA_CLASIFICACION_PERSONA_FISICA	= 0;
85  	private static final int AFIRMA_CLASIFICACION_PERSONA_JURIDICA	= 1;
86  	private static final int AFIRMA_CLASIFICACION_EMPLEADO_PUBLICO	= 5;
87  	private static final int AFIRMA_CLASIFICACION_ENTIDAD			= 6;
88  	private static final int AFIRMA_CLASIFICACION_CUALIFICADO_SELLO	= 8;
89  	private static final int AFIRMA_CLASIFICACION_AUTENTICACION		= 9;
90  	private static final int AFIRMA_CLASIFICACION_REPRESENTANTE_CPJ	= 11;
91  	private static final int AFIRMA_CLASIFICACION_REPRESENTANTE_SPJ	= 12;
92  
93  	/*
94  	 * Logger de la clase
95  	 */
96  	Logger logger = Logger.getLogger(AFirma6CertificateValidationService.class);
97  	
98  	/*
99  	 * URL de acceso a los servicios web de &#64;Firma
100 	 */
101 	private String url;
102 	
103 	/*
104 	 * Identificador de la aplicación en &#64;Firma
105 	 */
106 	private String idAplicacion;
107 	
108 	/*
109 	 * Usuario 
110 	 */
111 	private String user;
112 	
113 	/*
114 	 * Contraseña
115 	 */
116 	private String password;
117 	
118 	/*
119 	 * Fichero de acceso a almacén de claves (WSS4J)
120 	 */
121 	private String configuracionWSS4J;
122 	
123 	//-- Constructores	
124 	
125 	/**
126 	 * Constructor por defecto: si se usa este constructor será necesario inicializar
127 	 * el objeto.
128 	 */
129 	public AFirma6CertificateValidationService() {
130 		super();
131 	}
132 	
133 	/**
134 	 * Constructor en el que pasar la información necesaria para crear 
135 	 * este objeto.
136 	 * 
137 	 * @param url URL al servico web de &#64;Firma. Los posibles valores se pueden
138 	 * 	encontrar en los campos estáticos de esta clase PRODUCTION_URL y
139 	 *  TEST_URL.
140 	 * @param idAplicacion ID de su aplicación. Este valor se le entregó en
141 	 * 	el momento en que su aplicación fue dada de alta en la plataforma
142 	 * @param user Nombre de usuario para el caso en que se deba realizar la
143 	 * 	llamada securizada mediante usuario y contraseña.
144 	 * @param password Contraseña para el caso en que se deba realizar la
145 	 * 	llamada securizada mediante usuario y contraseña.
146 	 * @param configuracionWSS4J Fichero que contiene la información de acceso al
147 	 * 	certificado que firmará las peticiones que se envían a @Firma 
148 	 */
149 	public AFirma6CertificateValidationService(String url, String idAplicacion, String user,
150 			String password, String configuracionWSS4J) {
151 		super();
152 		initialize(url, idAplicacion, user, password, configuracionWSS4J);
153 	}
154 
155 	/**
156 	 * Constructor en el que pasar la información necesaria para crear 
157 	 * este objeto.
158 	 * 
159 	 * @param parameters Parametros necesarios para la inicialización
160 	 */
161 	public AFirma6CertificateValidationService(AFirma6CertificateValidationParameters parameters) {
162 		super();
163 		initialize(parameters.getAFirma6URL(), parameters.getAFirma6IdAplicacion(), parameters.getAFirma6User(), 
164 				parameters.getAFirma6Password(), parameters.getAFirma6ConfiguracionWSS4J());
165 	}
166 	/**
167 	 * Inicializa el objeto
168 	 * 
169 	 * @param url URL al servico web de &#64;Firma. Los posibles valores se pueden
170 	 * 	encontrar en los campos estáticos de esta clase PRODUCTION_URL y
171 	 *  TEST_URL.
172 	 * @param idAplicacion ID de su aplicación. Este valor se le entregó en
173 	 * 	el momento en que su aplicación fue dada de alta en la plataforma
174 	 * @param user Nombre de usuario para el caso en que se deba realizar la
175 	 * 	llamada securizada mediante usuario y contraseña.
176 	 * @param password Contraseña para el caso en que se deba realizar la
177 	 * 	llamada securizada mediante usuario y contraseña.
178 	 * @param configuracionWSS4J Fichero que contiene la información de acceso al
179 	 * 	certificado que firmará las peticiones que se envían a @Firma 
180 	 */
181 	public void initialize (String url, String idAplicacion, String user, String password, String configuracionWSS4J) {
182 		this.idAplicacion = idAplicacion;
183 		this.user = user;
184 		this.password = password;
185 		this.configuracionWSS4J = configuracionWSS4J;
186 		
187 		if (url.indexOf("//") > -1) {
188 			url = url.substring(url.indexOf("//") + 2);
189 			if (url.indexOf("/") > -1) {
190 				url = url.substring(0, url.indexOf("/"));
191 			}
192 		}
193 		this.url = url;
194 	}
195 
196 	/**
197 	 * Valida un certificado mediante una llamada a un servicio externo.
198 	 * 
199 	 * @param certificate Certificado a validar
200 	 * @param extraParams Parámetros extra por si fueran necesarios para 
201 	 * 	realizar la validación
202 	 * @return Objeto con el resultado y, si el servicio web lo permite, los
203 	 * 	campos más significativos del certificado.
204 	 * @throws ServiceNotFoundException El servicio no se encuentra disponible
205 	 * @throws ServiceException La llamada al servicio devuelve un error
206 	 */
207 	public CertificateValidationServiceResult validate(Certificate certificate, 
208 			Map<String, Object> extraParams) throws ServiceNotFoundException, ServiceException {
209 		
210 		logger.debug("[AFirmaCertificateValidationService.validate]::Entrada::" + Arrays.asList(new Object[] { certificate, extraParams }));
211 		
212 		//-- Pasar los parámetros a un properties
213 		ArangiServiceContent serviceContent = new ArangiServiceContent(url, user, password, configuracionWSS4J);
214 		
215 		Map<String, Object> inParams = new HashMap<String, Object>();
216 
217 		inParams.put(DSSTagsRequest.CLAIMED_IDENTITY, this.idAplicacion);
218 		inParams.put(DSSTagsRequest.INCLUDE_CERTIFICATE, "true");
219 		inParams.put(DSSTagsRequest.INCLUDE_REVOCATION, "true");
220 		inParams.put(DSSTagsRequest.REPORT_DETAIL_LEVEL, ReportDetailLevel.ALL_DETAILS);
221 		inParams.put(DSSTagsRequest.CHECK_CERTIFICATE_STATUS, "true");
222 		inParams.put(DSSTagsRequest.RETURN_READABLE_CERT_INFO, "");
223 		try {
224 			inParams.put(DSSTagsRequest.X509_CERTIFICATE, Util.encodeBase64(certificate.toDER()));
225 		} catch (NormalizeCertificateException e) {
226 			//-- El certificado ya se normalizó al entrar, no se dará el error
227 			logger.info ("[AFirmaCertificateValidationService.validate]", e);
228 		}
229 		
230 		String xmlInput;
231 		try {
232 			xmlInput = TransformersFacade.getInstance().generateXml(inParams, GeneralConstants.DSS_AFIRMA_VERIFY_CERTIFICATE_REQUEST, GeneralConstants.DSS_AFIRMA_VERIFY_METHOD, TransformersConstants.VERSION_10);
233 		} catch (TransformersException e) {
234 			logger.info("No se puede crear la petición para @Firma", e);
235 			throw new ServiceException("No se puede crear la petición para @Firma", e);
236 		}
237 		String xmlOutput;
238 		try {
239 			xmlOutput = Afirma5ServiceInvokerFacade.getInstance().invokeService(xmlInput, GeneralConstants.DSS_AFIRMA_VERIFY_CERTIFICATE_REQUEST, GeneralConstants.DSS_AFIRMA_VERIFY_METHOD, this.idAplicacion, serviceContent);
240 		} catch (Exception e) {
241 			logger.info("No se puede obtener la respuesta de @Firma", e);
242 			throw new ServiceNotFoundException("No se puede obtener la respuesta de @Firma", e);
243 		}
244 		
245 		Map<String, Object> propertiesResult;
246 		try {
247 			propertiesResult = TransformersFacade.getInstance().parseResponse(xmlOutput, GeneralConstants.DSS_AFIRMA_VERIFY_CERTIFICATE_REQUEST, GeneralConstants.DSS_AFIRMA_VERIFY_METHOD, TransformersConstants.VERSION_10);
248 		} catch (TransformersException e) {
249 			logger.info("No se puede parsear la respuesta de @Firma", e);
250 			throw new ServiceException("No se puede parsear la respuesta de @Firma", e);
251 		}
252 
253 		//validamos si el resultado ha sido satisfactorio
254 		String valorResultado = (String) propertiesResult.get(TransformersFacade.getInstance().getParserParameterValue("ResultMayor"));
255 		String valorResultadoMenor = (String) propertiesResult.get(TransformersFacade.getInstance().getParserParameterValue("ResultMinor"));
256 		
257 		//-- Comprobar si la respuesta es válida
258 		int resultado = ValidationResult.RESULT_VALID;
259 		Date fechaRevocacion = null;
260 		BasicOCSPResp respuestaOCSP = null;
261 		int motivoRevocacion = -1;
262 		
263 		HashMap<String,Object>[] hmCertVal = (HashMap<String,Object>[]) propertiesResult.get("dss:OptionalOutputs/vr:CertificatePathValidity/vr:PathValidityDetail/vr:CertificateValidity");
264 		HashMap<String,Object> camposCertificado = (HashMap<String,Object>) propertiesResult.get("dss:OptionalOutputs/afxp:ReadableCertificateInfo");
265 		
266 		//-- Obtener la respuesta OCSP
267 		if (camposCertificado != null && hmCertVal != null) {
268 			String numeroSerie = (String) camposCertificado.get("numeroSerie");
269 			String issuer = (String) camposCertificado.get("idEmisor");
270 			for (HashMap<String,Object> certVal : hmCertVal) {
271 				if (certVal.get("dss:OptionalOutputs/vr:CertificatePathValidity/vr:PathValidityDetail/vr:CertificateValidity/vr:CertificateIdentifier/ds:X509IssuerName").equals(issuer) &&
272 						certVal.get("dss:OptionalOutputs/vr:CertificatePathValidity/vr:PathValidityDetail/vr:CertificateValidity/vr:CertificateIdentifier/ds:X509SerialNumber").equals(numeroSerie)) {
273 					String ocspResponseB64 = (String) certVal.get("dss:OptionalOutputs/vr:CertificatePathValidity/vr:PathValidityDetail/vr:CertificateValidity/vr:CertificateStatus/vr:RevocationEvidence/vr:OCSPValidity/OCSPValue");
274 					if (ocspResponseB64 != null) {
275 						try {
276 							ASN1InputStream inp = new ASN1InputStream(Util.decodeBase64(ocspResponseB64));
277 							BasicOCSPResponse basicResp = BasicOCSPResponse.getInstance(inp.readObject());
278 							respuestaOCSP = new BasicOCSPResp(basicResp);
279 							CertificateStatus status = respuestaOCSP.getResponses()[0].getCertStatus();
280 							if (status instanceof RevokedStatus) {
281 								RevokedStatus revokedStatus = (RevokedStatus) status;
282 								fechaRevocacion = revokedStatus.getRevocationTime();
283 								if (!revokedStatus.hasRevocationReason()) {
284 									motivoRevocacion = -1;
285 								} else {
286 									motivoRevocacion = revokedStatus.getRevocationReason();
287 								}
288 							}
289 						} catch (Exception e) {
290 							logger.info("No se ha podido leer la respuesta OCSP", e);
291 						}
292 					} else {
293 						logger.info("No existe la respuesta OCSP");
294 					}
295 				}
296 			}
297 		}
298 		
299 		if (valorResultado.equals(ResultProcessIds.SUCESS)) {
300 			if (valorResultadoMenor.equals(AFIRMA_MINOR_RESULT_CERTIFICATE_REVOKED)) {
301 				resultado = ValidationResult.RESULT_CERTIFICATE_REVOKED;
302 			}
303 		} else {
304 			logger.info("Resultado: " + propertiesResult.get(TransformersFacade.getInstance().getParserParameterValue("ResultMessage")));
305 			if (valorResultado.equals(ResultProcessIds.REQUESTER_ERROR) && valorResultadoMenor.equals(AFIRMA_MINOR_RESULT_CERTIFICATE_NOT_SUPPORTED)) {
306 				resultado = ValidationResult.RESULT_CERTIFICATE_NOT_BELONGS_TRUSTED_CAS;
307 			} else {
308 				resultado = ValidationResult.RESULT_INVALID;
309 			}
310 		}
311 		
312 		//-- Devolver resultado
313 		CertificateValidationServiceResult certResult = new CertificateValidationServiceResult(resultado, camposCertificado);
314 		if (fechaRevocacion != null) {
315 			certResult.setRevocationDate(fechaRevocacion);
316 			certResult.setRevocationReason(motivoRevocacion);
317 		}
318 		if (respuestaOCSP != null) {
319 			try {
320 				ASN1EncodableVector v1 = new ASN1EncodableVector();
321 				v1.add(new ASN1Enumerated(0));
322 				
323 				ASN1EncodableVector v2 = new ASN1EncodableVector();
324 				ASN1ObjectIdentifier id = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1.1");
325 				v2.add(id);
326 				v2.add(new DEROctetString(DERSequence.fromByteArray(respuestaOCSP.getEncoded())));
327 				DERTaggedObject tagged = new DERTaggedObject(true, 0, new DERSequence(v2));
328 				v1.add(tagged);
329 				DERSequence seq1 = new DERSequence(v1);
330 				
331 				OCSPResp r = new OCSPResp(seq1.getEncoded());
332 				OCSPResponse ocspResponse = new OCSPResponse(r);
333 				certResult.setOcspResponse(ocspResponse);
334 			} catch (Exception e) {
335 				logger.info("No se ha podido construir la respuesta OCSP");
336 			}
337 		}
338 		
339 		//-- Certificado (Obtener la clasificación)
340 		if (propertiesResult.get("dss:OptionalOutputs/vr:VerificationReport/vr:IndividualSignatureReport") != null) {
341 			Map<String, Object> individualSignatureReport = ((Map<String, Object>[]) propertiesResult.get("dss:OptionalOutputs/vr:VerificationReport/vr:IndividualSignatureReport"))[0];
342 			if (individualSignatureReport != null) {
343 				Map<String, Object> certificateInfo = (Map<String, Object>) individualSignatureReport.get("dss:OptionalOutputs/vr:VerificationReport/vr:IndividualSignatureReport/vr:Details/afxp:ReadableCertificateInfo");
344 				if (certificateInfo != null) {
345 					try {
346 						certResult.setCertificateCategory(Integer.parseInt((String)certificateInfo.get("clasificacion")));
347 					} catch (Exception e) {
348 						logger.info("La clasificación de certificado devuelta por @Firma no es un entero: " + certificateInfo.get("clasificacion"));
349 					}
350 				}
351 			}
352 		} else if (camposCertificado.get("clasificacion") != null) {
353 			try {
354 				certResult.setCertificateCategory(Integer.parseInt((String)camposCertificado.get("clasificacion")));
355 			} catch (Exception e) {
356 				logger.info("La clasificación de certificado devuelta por @Firma no es un entero: " + camposCertificado.get("clasificacion"));
357 			}
358 		}
359 		
360 		return certResult;
361 	}
362 	
363 	/*
364 	 * Indica si el certificado es de entidad a partir de la clasificación de @Firma
365 	 */
366 	public static boolean isEntidad(int clasificacion) {
367 		return clasificacion == AFIRMA_CLASIFICACION_PERSONA_JURIDICA || 
368 				clasificacion == AFIRMA_CLASIFICACION_ENTIDAD || 
369 				clasificacion == AFIRMA_CLASIFICACION_CUALIFICADO_SELLO || 
370 				clasificacion == AFIRMA_CLASIFICACION_REPRESENTANTE_CPJ || 
371 				clasificacion == AFIRMA_CLASIFICACION_REPRESENTANTE_SPJ;
372 	}
373 
374 	/*
375 	 * Indica si el certificado es persona física a partir de la clasificación de @Firma
376 	 */
377 	public static boolean isPersonaFisica(int clasificacion) {
378 		return clasificacion == AFIRMA_CLASIFICACION_PERSONA_FISICA || 
379 				clasificacion == AFIRMA_CLASIFICACION_EMPLEADO_PUBLICO || 
380 				clasificacion == AFIRMA_CLASIFICACION_AUTENTICACION || 
381 				clasificacion == AFIRMA_CLASIFICACION_REPRESENTANTE_CPJ || 
382 				clasificacion == AFIRMA_CLASIFICACION_REPRESENTANTE_SPJ;
383 	}
384 
385 
386 	
387 	//-- Clases privadas
388 	
389 	/**
390 	 * Clase que requiere ACCVIntegra para obtener los parámetros con los que se realizará
391 	 * la llamada.
392 	 */
393 	public class ArangiServiceContent implements Afirma5ServiceInvokerContent {
394 		
395 		Properties afirmaProperties;
396 		
397 		public ArangiServiceContent(String url, String user, String password,
398 				String configuracionWSS4J) {
399 			afirmaProperties = new Properties();
400 			afirmaProperties.put("com.certificatesCache.use","true");
401 			afirmaProperties.put("com.certificatesCache.entries","2");
402 			afirmaProperties.put("com.certificatesCache.lifeTime","120");
403 			afirmaProperties.put("secureMode", configuracionWSS4J==null?"false":"true");
404 			afirmaProperties.put("endPoint", url);
405 			afirmaProperties.put("servicePath", "afirmaws/services");
406 			afirmaProperties.put("callTimeout", "20000");
407 			if (configuracionWSS4J!=null) {
408 				afirmaProperties.put("authorizationMethod", "BinarySecurityToken");
409 				afirmaProperties.put("authorizationMethod.signaturePropFile", configuracionWSS4J);
410 			} else if (user != null && password != null) {
411 				afirmaProperties.put("authorizationMethod", "UsernameToken");
412 				afirmaProperties.put("authorizationMethod.user", user);
413 				afirmaProperties.put("authorizationMethod.password", password);
414 			} else {
415 				afirmaProperties.put("authorizationMethod", "none");
416 			}
417 			afirmaProperties.put("response.validate", "false");
418 			afirmaProperties.put("response.certificateAlias", "DefaultFirma");
419 		}
420 
421 		public long getLastModified() {
422 			return new Date().getTime();
423 		}
424 
425 		public Properties getProperties() throws Exception {
426 			return afirmaProperties;
427 		}
428 		
429 	}
430 
431 }