1
2
3
4
5
6
7
8
9
10
11
12
13 package org.abstracthorizon.danube.auth.jaas.keystore;
14
15 import java.io.IOException;
16 import java.io.InputStream;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.security.GeneralSecurityException;
20 import java.security.Key;
21 import java.security.KeyStore;
22 import java.security.KeyStoreException;
23 import java.security.NoSuchAlgorithmException;
24 import java.security.NoSuchProviderException;
25 import java.security.PrivateKey;
26 import java.security.UnrecoverableKeyException;
27 import java.security.cert.Certificate;
28 import java.security.cert.CertificateException;
29 import java.security.cert.CertificateFactory;
30 import java.security.cert.X509Certificate;
31 import java.util.Iterator;
32 import java.util.LinkedList;
33 import java.util.Map;
34 import javax.security.auth.DestroyFailedException;
35 import javax.security.auth.Destroyable;
36 import javax.security.auth.Subject;
37 import javax.security.auth.callback.Callback;
38 import javax.security.auth.callback.CallbackHandler;
39 import javax.security.auth.callback.ConfirmationCallback;
40 import javax.security.auth.callback.NameCallback;
41 import javax.security.auth.callback.PasswordCallback;
42 import javax.security.auth.callback.TextOutputCallback;
43 import javax.security.auth.callback.UnsupportedCallbackException;
44 import javax.security.auth.login.FailedLoginException;
45 import javax.security.auth.login.LoginException;
46 import javax.security.auth.spi.LoginModule;
47 import javax.security.auth.x500.X500PrivateCredential;
48
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52
53
54
55
56
57
58 public class KeyStoreLoginModule implements LoginModule {
59
60
61 protected Logger logger = LoggerFactory.getLogger(getClass());
62
63
64 protected Subject subject;
65
66
67 protected CallbackHandler callbackHandler;
68
69 protected Map<String, ?> sharedState;
70
71 protected Map<String, ?> options;
72
73
74 private String keyStoreURL;
75
76
77 private char[] keyStorePassword;
78
79
80 private char[] userPassword;
81
82
83 protected String username;
84
85
86 private String keyStoreType;
87
88
89 private String keyStoreProvider;
90
91
92 protected static final int UNINITIALIZED = 0;
93
94
95 protected static final int INITIALIZED = 1;
96
97
98 protected static final int AUTHENTICATED = 2;
99
100
101 protected static final int LOGGED_IN = 3;
102
103
104 protected int status = UNINITIALIZED;
105
106
107 private javax.security.auth.x500.X500Principal principal;
108
109
110 private Certificate[] fromKeystore;
111
112
113 private java.security.cert.CertPath publicCredentials = null;
114
115
116 private X500PrivateCredential privateCredential;
117
118
119
120
121 public KeyStoreLoginModule() {
122 }
123
124
125
126
127
128 public void setKeystorePassword(String password) {
129 this.keyStorePassword = password.toCharArray();
130 }
131
132
133
134
135
136 public void setKeystoreURL(String url) {
137 this.keyStoreURL = url;
138 }
139
140
141
142
143
144 public String getKeystoreURL() {
145 return keyStoreURL;
146 }
147
148
149
150
151
152
153
154
155 public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
156 this.subject = subject;
157 this.callbackHandler = callbackHandler;
158 this.sharedState = sharedState;
159 this.options = options;
160
161 keyStoreType = (String)options.get("keyStoreType");
162 if (keyStoreType == null) {
163 keyStoreType = KeyStore.getDefaultType();
164 }
165 keyStoreProvider = (String)options.get("keyStoreProvider");
166
167 String pass = (String)options.get("keyStorePassword");
168 if (pass != null) {
169 keyStorePassword = pass.toCharArray();
170 } else {
171 keyStorePassword = new char[0];
172 }
173
174 keyStoreURL = (String) options.get("keyStoreURL");
175
176 status = INITIALIZED;
177 }
178
179
180
181
182
183
184 public boolean login() throws LoginException {
185 if (status == LOGGED_IN) {
186 return true;
187 }
188 if ((status == INITIALIZED) || (status == AUTHENTICATED)) {
189 obtainAuthenticationDetails();
190 getKeyStoreInfo();
191 status = AUTHENTICATED;
192 return true;
193 }
194
195 throw new LoginException("The login module is not initialized");
196 }
197
198
199
200
201
202 private void obtainAuthenticationDetails() throws LoginException {
203 TextOutputCallback bannerCallback = new TextOutputCallback(TextOutputCallback.INFORMATION, "Please login to keystore");
204 NameCallback aliasCallback = new NameCallback("Keystore alias: ");
205 PasswordCallback privateKeyPasswordCallback = new PasswordCallback("Password: ", false);
206 ConfirmationCallback confirmationCallback = new ConfirmationCallback(ConfirmationCallback.INFORMATION, ConfirmationCallback.OK_CANCEL_OPTION,
207 ConfirmationCallback.OK);
208 try {
209 callbackHandler.handle(
210 new Callback[]{
211 bannerCallback,
212 aliasCallback,
213 privateKeyPasswordCallback,
214 confirmationCallback}
215 );
216 } catch (IOException e) {
217 throw new LoginException("Exception while getting keystore alias and password: " + e);
218 } catch (UnsupportedCallbackException e) {
219 throw new LoginException("Error: " + e.getCallback().toString() + " is not available to retrieve authentication "
220 + " information from the user");
221 }
222
223 int confirmationResult = confirmationCallback.getSelectedIndex();
224 if (confirmationResult == ConfirmationCallback.CANCEL) {
225 throw new LoginException("Login cancelled");
226 }
227
228 username = aliasCallback.getName();
229
230 char[] tmpPassword = privateKeyPasswordCallback.getPassword();
231 userPassword = new char[tmpPassword.length];
232 System.arraycopy(tmpPassword, 0, userPassword, 0, tmpPassword.length);
233 for (int i = 0; i < tmpPassword.length; i++)
234 tmpPassword[0] = ' ';
235 tmpPassword = null;
236 privateKeyPasswordCallback.clearPassword();
237 }
238
239
240
241
242
243 private void getKeyStoreInfo() throws LoginException {
244
245 KeyStore keystore;
246 try {
247 if ((keyStoreProvider == null) || (keyStoreProvider.length() == 0)) {
248 keystore = KeyStore.getInstance(keyStoreType);
249 } else {
250 keystore = KeyStore.getInstance(keyStoreType, keyStoreProvider);
251 }
252
253
254 logger.debug("Loading keystore from " + keyStoreURL.toString());
255 InputStream in = new URL(keyStoreURL).openStream();
256 try {
257 keystore.load(in, keyStorePassword);
258 } finally {
259 in.close();
260 }
261 } catch (KeyStoreException e) {
262 throw new LoginException("The keystore type is not available: " + e);
263 } catch (NoSuchProviderException e) {
264 throw new LoginException("The keystore provider is not available: " + e);
265 } catch (MalformedURLException e) {
266 throw new LoginException("Malformed keystoreURL option: " + e);
267 } catch (GeneralSecurityException e) {
268 throw new LoginException(e.getMessage());
269 } catch (IOException e) {
270 throw new LoginException("IOException: " + e);
271 }
272
273 try {
274 fromKeystore = keystore.getCertificateChain(username);
275 if (fromKeystore == null || fromKeystore.length == 0 || !(fromKeystore[0] instanceof X509Certificate)) {
276 throw new FailedLoginException("Unable to find X.509 certificate chain for " + username + " in keystore");
277 } else {
278 LinkedList<Certificate> certList = new LinkedList<Certificate>();
279 for (int i = 0; i < fromKeystore.length; i++) {
280 certList.add(fromKeystore[i]);
281 }
282 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
283 publicCredentials = certificateFactory.generateCertPath(certList);
284 }
285 } catch (KeyStoreException e) {
286 throw new LoginException(e.getMessage());
287 } catch (CertificateException e) {
288 throw new LoginException("X.509 Certificate unavailable: " + e);
289 }
290
291 try {
292 X509Certificate certificate = (X509Certificate)fromKeystore[0];
293 principal = new javax.security.auth.x500.X500Principal(certificate.getSubjectDN().getName());
294 Key privateKey = keystore.getKey(username, userPassword);
295 if (privateKey == null || !(privateKey instanceof PrivateKey)) {
296 throw new FailedLoginException("Unable to recover key from keystore");
297 }
298 privateCredential = new X500PrivateCredential(certificate, (PrivateKey)privateKey, username);
299 } catch (KeyStoreException e) {
300 throw new LoginException(e.getMessage());
301 } catch (NoSuchAlgorithmException e) {
302 throw new LoginException("No such algorithm: " + e);
303 } catch (UnrecoverableKeyException e) {
304 throw new FailedLoginException("Unable to recover key from keystore: " + e);
305 }
306 }
307
308
309
310
311
312
313 public boolean commit() throws LoginException {
314 if (status == LOGGED_IN) {
315 return true;
316 }
317 if (status == AUTHENTICATED) {
318 if (subject.isReadOnly()) {
319 logoutImpl();
320 throw new LoginException("Subject is set readonly");
321 } else {
322 subject.getPrincipals().add(principal);
323 subject.getPublicCredentials().add(publicCredentials);
324 subject.getPrivateCredentials().add(privateCredential);
325 status = LOGGED_IN;
326 return true;
327 }
328 }
329 if (status == INITIALIZED) {
330 logoutImpl();
331 throw new LoginException("Authentication failed");
332 }
333
334 throw new LoginException("The login module is not initialized");
335 }
336
337
338
339
340
341 public boolean abort() throws LoginException {
342 if ((status == AUTHENTICATED) || (status == LOGGED_IN)) {
343 logoutImpl();
344 return true;
345 }
346
347 return false;
348 }
349
350
351
352
353
354 public boolean logout() throws LoginException {
355 if (status == LOGGED_IN) {
356 logoutImpl();
357 return true;
358 }
359
360 return false;
361 }
362
363
364
365
366
367 private void logoutImpl() throws LoginException {
368 for (int i = 0; i < userPassword.length; i++) {
369 userPassword[i] = '\0';
370 }
371 userPassword = null;
372
373 if (subject.isReadOnly()) {
374 principal = null;
375 publicCredentials = null;
376 status = INITIALIZED;
377
378 Iterator<Object> it = subject.getPrivateCredentials().iterator();
379 while (it.hasNext()) {
380 Object obj = it.next();
381 if (privateCredential.equals(obj)) {
382 privateCredential = null;
383 try {
384 ((Destroyable) obj).destroy();
385 break;
386 } catch (DestroyFailedException dfe) {
387 throw new LoginException("Unable to destroy private credential, " + obj.getClass().getName() + ": " + dfe.getMessage());
388 }
389 }
390 }
391
392 throw new LoginException("Unable to remove Principal (X500Principal) and public credential from read-only Subject");
393 }
394 if (principal != null) {
395 subject.getPrincipals().remove(principal);
396 principal = null;
397 }
398 if (publicCredentials != null) {
399 subject.getPublicCredentials().remove(publicCredentials);
400 publicCredentials = null;
401 }
402 if (privateCredential != null) {
403 subject.getPrivateCredentials().remove(privateCredential);
404 privateCredential = null;
405 }
406 status = INITIALIZED;
407 }
408 }