View Javadoc

1   /*
2    * Copyright (c) 2006-2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.danube.auth.jaas.memory;
14  
15  import java.io.IOException;
16  import java.security.NoSuchAlgorithmException;
17  import java.security.Principal;
18  import java.util.Collection;
19  import java.util.HashSet;
20  import java.util.Iterator;
21  import java.util.Map;
22  import java.util.Properties;
23  
24  import javax.security.auth.DestroyFailedException;
25  import javax.security.auth.Destroyable;
26  import javax.security.auth.Subject;
27  import javax.security.auth.callback.Callback;
28  import javax.security.auth.callback.CallbackHandler;
29  import javax.security.auth.callback.ConfirmationCallback;
30  import javax.security.auth.callback.NameCallback;
31  import javax.security.auth.callback.PasswordCallback;
32  import javax.security.auth.callback.TextOutputCallback;
33  import javax.security.auth.callback.UnsupportedCallbackException;
34  import javax.security.auth.login.LoginException;
35  import javax.security.auth.spi.LoginModule;
36  import javax.security.auth.x500.X500PrivateCredential;
37  
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * This is keystore login module. This login module checks keystore's
43   * certificates
44   *
45   * @author Daniel Sendula
46   */
47  public class PropertiesLoginModule implements LoginModule {
48  
49      /** Logger */
50      protected Logger logger = LoggerFactory.getLogger(getClass());
51  
52      /** Subject */
53      protected Subject subject;
54  
55      /** Callback handler */
56      protected CallbackHandler callbackHandler;
57  
58      protected Map<String, ?> sharedState;
59  
60      protected Map<String, ?> options;
61  
62  
63      /** User's password */
64      private char[] userPassword;
65  
66      /** Username */
67      protected String username;
68  
69  
70      /** Uninitialised state */
71      protected static final int UNINITIALIZED = 0;
72  
73      /** Initialised state */
74      protected static final int INITIALIZED = 1;
75  
76      /** User authenticated state */
77      protected static final int AUTHENTICATED = 2;
78  
79      /** Logged in state */
80      protected static final int LOGGED_IN = 3;
81  
82      /** Current state defaulted to uninitialised */
83      protected int status = UNINITIALIZED;
84  
85      /** Principals */
86      private Collection<Principal> principals;
87  
88      /** Public credentials */
89      private java.security.cert.CertPath publicCredentials = null;
90  
91      /** Private credential */
92      private X500PrivateCredential privateCredential;
93  
94      /** Properties */
95      private Properties properties;
96      
97      /**
98       * Default contructor
99       */
100     public PropertiesLoginModule() {
101     }
102 
103     public void setProperties(Properties properties) {
104     	this.properties = properties;
105     }
106     
107     public Properties getProperties() {
108     	return properties;
109     }
110     
111     /**
112      * Init method
113      * @param subject subject
114      * @param callbackHandler handler
115      * @param sharedState shared state
116      * @param options options
117      */
118     public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
119         this.subject = subject;
120         this.callbackHandler = callbackHandler;
121         this.sharedState = sharedState;
122         this.options = options;
123 
124         setProperties((Properties)options.get("properties"));
125         
126         status = INITIALIZED;
127     }
128 
129     /**
130      * Login method
131      * @return <code>true</code> if successful
132      * @throws LoginException
133      */
134     public boolean login() throws LoginException {
135         if (status == LOGGED_IN) {
136             return true;
137         }
138         if ((status == INITIALIZED) || (status == AUTHENTICATED)) {
139             obtainAuthenticationDetails();
140             obtainCertificates();
141             status = AUTHENTICATED;
142             return true;
143         }
144 
145         throw new LoginException("The login module is not initialized");
146     }
147 
148     /**
149      * This method obtains username and password from the party that tries to log in
150      * @throws LoginException
151      */
152     private void obtainAuthenticationDetails() throws LoginException {
153         TextOutputCallback bannerCallback = new TextOutputCallback(TextOutputCallback.INFORMATION, "Please login to keystore");
154         NameCallback aliasCallback = new NameCallback("Keystore alias: ");
155         PasswordCallback privateKeyPasswordCallback = new PasswordCallback("Password: ", false);
156         ConfirmationCallback confirmationCallback = new ConfirmationCallback(ConfirmationCallback.INFORMATION, ConfirmationCallback.OK_CANCEL_OPTION,
157                 ConfirmationCallback.OK);
158         try {
159             callbackHandler.handle(
160                     new Callback[]{
161                             bannerCallback,
162                             aliasCallback,
163                             privateKeyPasswordCallback,
164                             confirmationCallback}
165                 );
166         } catch (IOException e) {
167             throw new LoginException("Exception while getting keystore alias and password: " + e);
168         } catch (UnsupportedCallbackException e) {
169             throw new LoginException("Error: " + e.getCallback().toString() + " is not available to retrieve authentication "
170                     + " information from the user");
171         }
172 
173         int confirmationResult = confirmationCallback.getSelectedIndex();
174         if (confirmationResult == ConfirmationCallback.CANCEL) {
175             throw new LoginException("Login cancelled");
176         }
177 
178         username = aliasCallback.getName();
179 
180         char[] tmpPassword = privateKeyPasswordCallback.getPassword();
181         userPassword = new char[tmpPassword.length];
182         System.arraycopy(tmpPassword, 0, userPassword, 0, tmpPassword.length);
183         for (int i = 0; i < tmpPassword.length; i++) {
184             tmpPassword[0] = ' ';
185         }
186         tmpPassword = null;
187         privateKeyPasswordCallback.clearPassword();
188     }
189 
190     /**
191      * This method obtains certificates
192      * @throws LoginException
193      */
194     private void obtainCertificates() throws LoginException {
195 
196 
197     	String line = properties.getProperty(username);
198     	if (line == null) {
199     		throw new LoginException("Username doesn't exist");
200     	}
201     	String[] roles = line.trim().split(",");
202     	String pass = roles[0];
203     	
204     	boolean checked = false;
205     	if (pass.startsWith("{")) {
206     	    int i = pass.indexOf('}');
207     	    if (i > 0) {
208     	        checked = true;
209     	        String algorithm = pass.substring(1, i);
210     	        //String value = pass.substring(i + 1);
211     	        try {
212     	            String digest = PropertiesModuleService.generateHash(algorithm, new String(userPassword));
213                     
214                     if (!pass.equals(new String(digest))) {
215                         throw new LoginException("Password is wrong");
216                     }
217                     
218                 } catch (NoSuchAlgorithmException e) {
219                     throw new LoginException(e.getMessage());
220                 }
221     	    }
222     	}
223     	if (!checked) {
224             if (!pass.equals(new String(userPassword))) {
225                 throw new LoginException("Password is wrong");
226             }
227     	}
228 
229     	
230     	principals = new HashSet<Principal>();
231     	for (int i = 1; i < roles.length; i++) {
232     		principals.add(new PropertiesPrincipal(roles[i]));
233     	}
234     }
235 
236     /**
237      * Performs commit
238      * @return <code>true</code> if successful
239      * @throws LoginException
240      */
241     public boolean commit() throws LoginException {
242         if (status == LOGGED_IN) {
243             return true;
244         }
245         if (status == AUTHENTICATED) {
246             if (subject.isReadOnly()) {
247                 logoutImpl();
248                 throw new LoginException("Subject is set readonly");
249             } else {
250                 subject.getPrincipals().addAll(principals);
251                 subject.getPublicCredentials().add(publicCredentials);
252                 subject.getPrivateCredentials().add(privateCredential);
253                 status = LOGGED_IN;
254                 return true;
255             }
256         }
257         if (status == INITIALIZED) {
258             logoutImpl();
259             throw new LoginException("Authentication failed");
260         }
261 
262         throw new LoginException("The login module is not initialized");
263     }
264 
265     /**
266      * Aborts login
267      * @return <code>true</code> if successful
268      */
269     public boolean abort() throws LoginException {
270         if ((status == AUTHENTICATED) || (status == LOGGED_IN)) {
271             logoutImpl();
272             return true;
273         }
274 
275         return false;
276     }
277 
278     /**
279      * Logs out
280      * @return <code>true</code> if successful
281      */
282     public boolean logout() throws LoginException {
283         if (status == LOGGED_IN) {
284             logoutImpl();
285             return true;
286         }
287 
288         return false;
289     }
290 
291     /**
292      * Internal log out method
293      * @throws LoginException
294      */
295     private void logoutImpl() throws LoginException {
296         for (int i = 0; i < userPassword.length; i++) {
297             userPassword[i] = '\0';
298         }
299         userPassword = null;
300 
301         if (subject.isReadOnly()) {
302             principals = null;
303             publicCredentials = null;
304             status = INITIALIZED;
305 
306             Iterator<Object> it = subject.getPrivateCredentials().iterator();
307             while (it.hasNext()) {
308                 Object obj = it.next();
309                 if (privateCredential.equals(obj)) {
310                     privateCredential = null;
311                     try {
312                         ((Destroyable) obj).destroy();
313                         break;
314                     } catch (DestroyFailedException dfe) {
315                         throw new LoginException("Unable to destroy private credential, " + obj.getClass().getName() + ": " + dfe.getMessage());
316                     }
317                 }
318             }
319 
320             throw new LoginException("Unable to remove Principal (X500Principal) and public credential from read-only Subject");
321         }
322         
323         
324         if (principals != null) {
325             for (Principal p : principals) {
326             	subject.getPrincipals().remove(p);
327         	}
328             principals = null;
329         }
330         if (publicCredentials != null) {
331             subject.getPublicCredentials().remove(publicCredentials);
332             publicCredentials = null;
333         }
334         if (privateCredential != null) {
335             subject.getPrivateCredentials().remove(privateCredential);
336             privateCredential = null;
337         }
338         status = INITIALIZED;
339     }
340     
341     private static class PropertiesPrincipal implements Principal {
342 
343     	private String name;
344     	
345     	public PropertiesPrincipal(String name) {
346     		this.name = name;
347     	}
348     	
349 		public String getName() {
350 			return name;
351 		}
352     	
353     }
354     
355 }