View Javadoc

1   /*
2   jGuard is a security framework based on top of jaas (java authentication and authorization security).
3   it is written for web applications, to resolve simply, access control problems.
4   
5   http://sourceforge.net/projects/jguard/
6   
7   Copyright (C) 2004  Charles GAY
8   
9   This library is free software; you can redistribute it and/or
10  modify it under the terms of the GNU Lesser General Public
11  License as published by the Free Software Foundation; either
12  version 2.1 of the License, or (at your option) any later version.
13  
14  This library is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  Lesser General Public License for more details.
18  
19  You should have received a copy of the GNU Lesser General Public
20  License along with this library; if not, write to the Free Software
21  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  
23  
24  jGuard project home page:
25  http://sourceforge.net/projects/jguard/
26  
27  */
28  package net.sf.jguard.ext.authentication.loginmodules;
29  
30  import java.io.BufferedInputStream;
31  import java.io.DataInputStream;
32  import java.io.FileInputStream;
33  import java.io.FileNotFoundException;
34  import java.io.IOException;
35  import java.io.InputStream;
36  import java.net.MalformedURLException;
37  import java.net.URL;
38  import java.net.URLConnection;
39  import java.security.InvalidAlgorithmParameterException;
40  import java.security.KeyStore;
41  import java.security.KeyStoreException;
42  import java.security.NoSuchAlgorithmException;
43  import java.security.Provider;
44  import java.security.PublicKey;
45  import java.security.Security;
46  import java.security.cert.CRL;
47  import java.security.cert.CRLException;
48  import java.security.cert.CertPath;
49  import java.security.cert.CertPathValidator;
50  import java.security.cert.CertPathValidatorException;
51  import java.security.cert.CertStore;
52  import java.security.cert.CertStoreParameters;
53  import java.security.cert.CertificateException;
54  import java.security.cert.CertificateFactory;
55  import java.security.cert.CollectionCertStoreParameters;
56  import java.security.cert.LDAPCertStoreParameters;
57  import java.security.cert.PKIXCertPathValidatorResult;
58  import java.security.cert.PKIXParameters;
59  import java.security.cert.PolicyNode;
60  import java.security.cert.TrustAnchor;
61  import java.security.cert.X509Certificate;
62  import java.util.ArrayList;
63  import java.util.Arrays;
64  import java.util.Collection;
65  import java.util.Date;
66  import java.util.List;
67  import java.util.Map;
68  import java.util.Set;
69  
70  import java.util.logging.Level;
71  import javax.security.auth.Subject;
72  import javax.security.auth.callback.CallbackHandler;
73  import javax.security.auth.login.FailedLoginException;
74  import javax.security.auth.login.LoginException;
75  import javax.security.auth.spi.LoginModule;
76  
77  import net.sf.jguard.core.CoreConstants;
78  import net.sf.jguard.ext.SecurityConstants;
79  import net.sf.jguard.ext.authentication.certificates.CertUtils;
80  
81  import org.bouncycastle.jce.provider.BouncyCastleProvider;
82  import org.slf4j.Logger;
83  import org.slf4j.LoggerFactory;
84  
85  /**
86   * validate certificates: validate their certPath and checks if
87   * some of them are revoked against CRL(Certificate Revocation list).
88   * @author <a href="mailto:diabolo512@users.sourceforge.net ">Charles Gay</a>
89   *
90   */
91  public class CRLLoginModule extends CertificateLoginModule implements LoginModule {
92  
93  	private static final String COLLECTION = "Collection";
94  	private static final String LDAP = "LDAP";
95  	private static final String PKIX = "PKIX";
96  	private static final String X_509 = "X.509";
97  
98  	/** Logger for this class */
99  	private static final Logger logger = LoggerFactory.getLogger(CRLLoginModule.class.getName());
100 	private Set trustAnchors = null;
101 	private String trustedCaCertsDirPath = null;
102 	private CertPath certPath = null;
103 	private boolean debug = false;
104 	private Provider securityProvider = null;
105 	private String certStoreType =CRLLoginModule.LDAP;
106 	private String ldapServerName ="localhost";
107 	private int ldapServerPort =389;
108 	
109 	private Map sharedState;
110 	private String fileCrlPath = null;
111 	private String urlCrlPath = null;
112 	private boolean anyPolicyInhibited = false;
113 	private boolean explicitPolicyRequired = false;
114 	private boolean policyMappingInhibited = false;
115 	private boolean policyQualifierRejected = true;
116 	private boolean revocationEnabled = true;
117 	private String sigProvider=null;
118 	private String keyStorePath;
119 	private String keyStorePassword;
120 	private String keyStoreType;
121 	private static boolean SecurityProviderInitialized = false;
122 	
123 
124 	/**
125 	 * @param subj
126 	 * @param cbkHandler
127 	 * @param state
128 	 * @param options
129 	 * @see javax.security.auth.spi.LoginModule#initialize(javax.security.auth.Subject, javax.security.auth.callback.CallbackHandler, java.util.Map, java.util.Map)
130 	 */
131 	public void initialize(Subject subj, CallbackHandler cbkHandler, Map state,Map options) {
132 		this.subject = subj;
133 		this.callbackHandler = cbkHandler;
134 		this.sharedState = state;
135 
136         if(!SecurityProviderInitialized){
137         	SecurityProviderInitialized = initSecurityProvider();
138         }
139 
140 		if((String)options.get(CoreConstants.DEBUG)!=null){
141 			  debug= Boolean.valueOf((String)options.get(CoreConstants.DEBUG)).booleanValue();
142 		}
143 
144 		if((String)options.get(SecurityConstants.CERT_PATH_ANY_POLICY_INHIBITED)!=null){
145 			anyPolicyInhibited = Boolean.valueOf((String)options.get(SecurityConstants.CERT_PATH_ANY_POLICY_INHIBITED)).booleanValue();
146 		}
147 
148 		if((String)options.get(SecurityConstants.CERT_PATH_EXPLICIT_POLICY_REQUIRED)!=null){
149 			explicitPolicyRequired = Boolean.valueOf((String)options.get(SecurityConstants.CERT_PATH_EXPLICIT_POLICY_REQUIRED)).booleanValue();
150 		}
151 
152 		if((String)options.get(SecurityConstants.CERT_PATH_POLICY_MAPPING_INHIBITED)!=null){
153 			policyMappingInhibited = Boolean.valueOf((String)options.get(SecurityConstants.CERT_PATH_POLICY_MAPPING_INHIBITED)).booleanValue();
154 		}
155 
156 		if((String)options.get(SecurityConstants.CERT_PATH_POLICY_QUALIFIERS_REJECTED)!=null){
157 			policyQualifierRejected = Boolean.valueOf((String)options.get(SecurityConstants.CERT_PATH_POLICY_QUALIFIERS_REJECTED)).booleanValue();
158 		}
159 
160 		if((String)options.get(SecurityConstants.CERT_PATH_REVOCATION_ENABLED)!=null){
161 			revocationEnabled = Boolean.valueOf((String)options.get(SecurityConstants.CERT_PATH_REVOCATION_ENABLED)).booleanValue();
162 		}
163 
164 		if((String)options.get(SecurityConstants.CERT_PATH_SIG_PROVIDER)!=null){
165 			sigProvider =(String)options.get(SecurityConstants.CERT_PATH_SIG_PROVIDER);
166 		}
167 
168 		if((String)options.get(SecurityConstants.CERT_PATH_CRL_PATH)!=null){
169 			 fileCrlPath =(String)options.get(SecurityConstants.CERT_PATH_CRL_PATH);
170 		}
171 
172 		if((String)options.get(SecurityConstants.CERT_PATH_URL_CRL_PATH)!=null){
173 			 urlCrlPath =(String)options.get(SecurityConstants.CERT_PATH_URL_CRL_PATH);
174 		}
175 
176 		if((String)options.get(SecurityConstants.TRUSTED_CA_CERTIFICATES_DIRECTORY_PATH)!=null){
177 			trustedCaCertsDirPath= (String)options.get(SecurityConstants.TRUSTED_CA_CERTIFICATES_DIRECTORY_PATH);
178 			trustAnchors = CertUtils.getTrustedAnchorsFromDirectory(trustedCaCertsDirPath);
179 		}
180 		
181 		if((String)options.get(SecurityConstants.SECURITY_PROVIDER)!=null){
182 			String securityProviderClassName = (String)options.get(SecurityConstants.SECURITY_PROVIDER);
183 			try {
184 				Class securityProviderClass = this.getClass().getClassLoader().loadClass(securityProviderClassName);
185 				securityProvider = (Provider) securityProviderClass.newInstance();
186 
187 			} catch (ClassNotFoundException e) {
188 				logger.warn(e.getMessage());
189 			} catch (InstantiationException e) {
190 				logger.warn(e.getMessage());
191 			} catch (IllegalAccessException e) {
192 				logger.warn(e.getMessage());
193 			}
194 		}else{
195 			securityProvider = new BouncyCastleProvider();
196 		}
197 
198 		if((String)options.get(SecurityConstants.CERT_PATH_CERTSTORE_TYPE)!=null){
199 			  certStoreType = (String)options.get(SecurityConstants.CERT_PATH_CERTSTORE_TYPE);
200 		}
201 
202 		if((String)options.get(SecurityConstants.CERT_PATH_LDAP_SERVER_NAME)!=null){
203 			ldapServerName = (String)options.get(SecurityConstants.CERT_PATH_LDAP_SERVER_NAME);
204 		}
205 
206 		if((String)options.get(SecurityConstants.CERT_PATH_LDAP_SERVER_PORT)!=null){
207 			ldapServerPort = Integer.parseInt((String)options.get(SecurityConstants.CERT_PATH_LDAP_SERVER_PORT));
208 		}
209 		
210 		if((String)options.get(SecurityConstants.JAVAX_NET_SSL_TRUSTSTORE)!=null){
211 			String trustStoreFileName = (String)options.get(SecurityConstants.JAVAX_NET_SSL_TRUSTSTORE);
212 			System.setProperty("javax.net.ssl.trustStore",trustStoreFileName);
213 		}
214 		
215 		if((String)options.get(SecurityConstants.JAVAX_NET_SSL_TRUSTSTORE_PASSWORD)!=null){
216 			String trustStorePassword = (String)options.get(SecurityConstants.JAVAX_NET_SSL_TRUSTSTORE_PASSWORD);
217 			System.setProperty("javax.net.ssl.trustStorePassword",trustStorePassword);
218 		}
219 		
220 		if((String)options.get(SecurityConstants.KEY_STORE_PATH)!=null){
221 			keyStorePath = (String)options.get(SecurityConstants.KEY_STORE_PATH);
222 		}
223 		
224 		if((String)options.get(SecurityConstants.KEY_STORE_PASSWORD)!=null){
225 			keyStorePassword = (String)options.get(SecurityConstants.KEY_STORE_PASSWORD);
226 		}
227 		
228 		if((String)options.get(SecurityConstants.KEY_STORE_TYPE)!=null){
229 			keyStoreType = (String)options.get(SecurityConstants.KEY_STORE_TYPE);
230 		}
231 		
232 		
233 	}
234 
235 	/**
236 	 *
237 	 * @see javax.security.auth.spi.LoginModule#login()
238 	 */
239 	public boolean login() throws LoginException {
240 
241 		boolean login = super.login();
242 		if(!login){
243 			return login;
244 		}
245 		certPath = buildCertPath(certChainToCheck);
246 	    validateCertPath(certPath);
247 	    //like we've already check user credentials present in the certificate
248 		//password check must not be done one more time.
249 	    sharedState.put(SecurityConstants.SKIP_PASSWORD_CHECK, "true");
250 		return true;
251 	}
252 
253 	
254 	/**
255 	 * generate certification Path .
256 	 * @param certs the X509Certificate Array
257 	 * @return certification path
258 	 */
259 	private CertPath buildCertPath(X509Certificate[] certs){
260 		CertificateFactory certFactory = null;
261 		CertPath certPath = null;
262 		try {
263 			certFactory = CertificateFactory.getInstance(CRLLoginModule.X_509,securityProvider);
264 			certPath = certFactory.generateCertPath(Arrays.asList(certs));
265 		} catch (CertificateException e) {
266 			logger.warn(e.getMessage());
267 		}
268 
269 		return certPath;
270 	}
271 
272 
273 	/**
274 	 * validate a certPath.
275 	 * @param certPath
276 	 * @throws LoginException
277 	 */
278 	private void validateCertPath(CertPath certPath) throws LoginException{
279 		CertPathValidator validator = null;
280 		PKIXParameters parameters = null;
281 		PKIXCertPathValidatorResult result = null;
282 		try {
283 			validator = CertPathValidator.getInstance(CRLLoginModule.PKIX,securityProvider);
284 
285 		} catch (NoSuchAlgorithmException e) {
286 			logger.error(" algorithm PKIX is not present "+securityProvider.getName()
287 			+" "+securityProvider.getInfo()+" "+securityProvider.getVersion());
288 		}
289 		try {
290 
291 				parameters = getPKIXParameters();
292 				List certStores = new ArrayList();
293 				CertStore certStore = getCertStore();
294 				certStores.add(certStore);
295 				parameters.setCertStores(certStores);
296 				parameters.setAnyPolicyInhibited(anyPolicyInhibited);
297 				//TODO implement better data handling (more precise)
298 				parameters.setDate(new Date());
299 				//TODO howto implements the CRLSelector => with certStore?
300 				parameters.setExplicitPolicyRequired(explicitPolicyRequired);
301 				parameters.setPolicyMappingInhibited(policyMappingInhibited);
302 				parameters.setPolicyQualifiersRejected(policyQualifierRejected);
303 				parameters.setRevocationEnabled(revocationEnabled);
304 				if(sigProvider!=null){
305 				  parameters.setSigProvider(sigProvider);
306 				}
307 				//TODO define the certSelector
308 				//parameters.setTargetCertConstraints(null);
309 				//
310 				result =(PKIXCertPathValidatorResult)validator.validate(certPath,parameters);
311 				PolicyNode policyTree = result.getPolicyTree();
312 				PublicKey key = result.getPublicKey();
313 				TrustAnchor anchor = result.getTrustAnchor();
314 				if(debug){
315 					if(policyTree!=null){
316 						logger.debug("policyTree depth = "+policyTree.getDepth());
317 						logger.debug("policyTree expected policies = "+policyTree.getExpectedPolicies());
318 						logger.debug("policyTree policy qualifiers = "+policyTree.getPolicyQualifiers());
319 					}
320 					if(key!=null){
321 						logger.debug("public key= "+key.toString());
322 					}
323 					if(anchor!=null){
324 						logger.debug("TrustAnchor ca name= "+anchor.getCAName());
325 						logger.debug("TrustAnchor ca public key = "+anchor.getCAPublicKey());
326 						logger.debug("TrustAnchor name constraints = "+anchor.getNameConstraints());
327 						logger.debug("TrustAnchor trustedCert = "+anchor.getTrustedCert());
328 					}
329 				}
330 		} catch (InvalidAlgorithmParameterException e) {
331 			logger.error(e.getMessage());
332 			throw new FailedLoginException(e.getMessage());
333 		} catch (CertPathValidatorException e) {
334 			logger.error(e.getMessage());
335 			throw new FailedLoginException(e.getMessage());
336 		}
337 	}
338 
339 	private PKIXParameters getPKIXParameters() throws LoginException {
340 		PKIXParameters parameters = null;
341 		if(keyStorePath!=null){
342 			KeyStore keystore;
343 			try {
344 				keystore = CertUtils.getKeyStore(keyStorePath, keyStorePassword, keyStoreType);
345 				parameters = new PKIXParameters(keystore);
346 			} catch (KeyStoreException e) {
347 				throw new LoginException(e.getMessage());
348 			} catch (NoSuchAlgorithmException e) {
349 				throw new LoginException(e.getMessage());
350 			} catch (CertificateException e) {
351 				throw new LoginException(e.getMessage());
352 			} catch (IOException e) {
353 				throw new LoginException(e.getMessage());
354 			} catch (InvalidAlgorithmParameterException e) {
355 				throw new LoginException(e.getMessage());
356 			}
357 			
358 		}else{
359 			try {
360 				parameters = new PKIXParameters(trustAnchors);
361 			} catch (InvalidAlgorithmParameterException e) {
362 				throw new LoginException(e.getMessage());
363 			}
364 		}
365 		return parameters;
366 	}
367 
368 
369 	/**
370 	 * retrieve the certStore.
371 	 * @return certStore
372 	 * @throws LoginException the certStore valueType is invalid
373 	 */
374 	private CertStore getCertStore() throws LoginException{
375 		CertStore certStore = null;
376 
377 		        //build a certStoreParameters object
378 				CertStoreParameters certStoreParams = null;
379 				if(certStoreType.equalsIgnoreCase(CRLLoginModule.LDAP)){
380 					certStoreParams = new LDAPCertStoreParameters(ldapServerName,ldapServerPort);
381 				}else if(certStoreType.equalsIgnoreCase(CRLLoginModule.COLLECTION)){
382 					Collection crlCollection = getCRLAndCertsCollection();
383 					certStoreParams = new CollectionCertStoreParameters(crlCollection);
384 				}else{
385 					throw new LoginException(" invalid 'certStoreType' value : this value should be 'LDAP' or 'Collection' ");
386 				}
387 				try {
388 					//build a CRL certStore
389 					certStore = CertStore.getInstance(certStoreType,certStoreParams,securityProvider);
390 
391 				} catch (NoSuchAlgorithmException e) {
392 					throw new LoginException(e.getMessage());
393 				} catch (InvalidAlgorithmParameterException e) {
394 					throw new LoginException(e.getMessage());
395 				}
396 
397 		return certStore;
398 	}
399 
400 	/**
401 	 * retrieve Certificate Revocation List (CRL) and Certificates.
402 	 * @return collection of CRL and Certificates
403 	 */
404 	private Collection getCRLAndCertsCollection() {
405 		Collection crlAndCerts = new ArrayList();
406 		CertificateFactory certFactory = null;
407 		try {
408 			certFactory = CertificateFactory.getInstance(CRLLoginModule.X_509,securityProvider);
409 		} catch (CertificateException e) {
410 			logger.error( " X509 certificate factory cannot be retrieved with the securityProvider "+
411 					securityProvider.getName()+" "+securityProvider.getInfo()+
412 					" "+securityProvider.getVersion(),e);
413 		}
414 
415 		if(fileCrlPath!=null){
416 		  addCRLFromPath(crlAndCerts, certFactory);
417 		}
418 		if(urlCrlPath!=null){
419 		  addCRLFromURL(crlAndCerts, certFactory);
420 		}
421 		return crlAndCerts;
422 	}
423 
424 	/**
425 	 *
426 	 * add <b>ONE</b> CRL grabbed from a file path to the 'CRLs and Certs' Collection.
427 	 * @param crlAndCerts
428 	 * @param certFactory
429 	 */
430 	private void addCRLFromPath(Collection crlAndCerts, CertificateFactory certFactory) {
431 
432 			InputStream stream = null;
433 			try {
434 				stream = new BufferedInputStream(new FileInputStream(fileCrlPath));
435 			} catch (FileNotFoundException e) {
436 				e.printStackTrace();
437 			}
438 			try {
439 				CRL crl  = certFactory.generateCRL(stream);
440 				crlAndCerts.add(crl);
441 			} catch (CRLException e) {
442 				e.printStackTrace();
443 			}finally{
444 			      try {
445 					stream.close();
446 				} catch (IOException e) {
447 					e.printStackTrace();
448 				}
449 			}
450 	}
451 
452 	/**
453 	 * add <b>ONE</b> CRL grabbed from an URL to the 'CRLs and Certs' Collection.
454 	 * @param crlAndCerts
455 	 * @param certFactory
456 	 */
457 	private void addCRLFromURL(Collection crlAndCerts, CertificateFactory certFactory) {
458 			DataInputStream data = null;
459 			try {
460 				URL url = new URL(urlCrlPath);
461 				URLConnection connection = url.openConnection();
462 				//we retrieve content
463 				connection.setDoInput(true);
464 				//we do not permit to cache content
465 				connection.setUseCaches(false);
466 				data = new DataInputStream(connection.getInputStream());
467 				try {
468 					CRL crl  = certFactory.generateCRL(data);
469 					crlAndCerts.add(crl);
470 				} catch (CRLException e) {
471 					logger.error(" CRL cannot be built with the retrieved data ");
472 					e.printStackTrace();
473 				}
474 			} catch (MalformedURLException e) {
475 				logger.error( " bad uri synthax "+urlCrlPath,e);
476 			} catch (IOException e) {
477 				logger.error( " IOException when we wan to retrieve CRL with data ",e);
478 			}finally{
479 				try {
480 					data.close();
481 				} catch (IOException e) {
482 					logger.error( " IOException when we close the DATAInputStream",e);
483 				}
484 			}
485 	}
486 
487 	/**
488 	 * install BouncyCastleProvider in the secuirty providers
489 	 * stack of the java platform.
490 	 * @return true if installation succeed, false otherwise
491 	 */
492     protected static boolean initSecurityProvider(){
493     	if(Security.getProvider(BouncyCastleProvider.class.getName())==null){
494 			try{
495 			Security.addProvider(new BouncyCastleProvider());
496 			return true;
497 			}catch(SecurityException sex){
498 				logger.error(" jGuard cannot add dynamically the JCE provider required from  \n");
499 				logger.error(" the BOUNCYCASTLE library .this operation is prevented by the SECURITYMANAGER \n");
500 				logger.error(" to use this required provider, you must add an entry to your java.security  \n");
501 				logger.error(" properties file (found in $JAVA_HOME/jre/lib/security/java.security, \n");
502 				logger.error(" where $JAVA_HOME is the location of your JDK/JRE distribution) \n");
503 				logger.error(" security.provider.<n>=org.bouncycastle.jce.provider.BouncyCastleProvider \n");
504 				logger.error("  Where <n> is the preference you want the provider at (1 being the most prefered). ");
505 				return false;
506 			}
507         }else{
508         	return true;
509         }
510     }
511 
512 }