1 /* 2 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.sun.security.auth.module; 27 28 import javax.security.auth.x500.X500Principal; 29 import java.io.File; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.PushbackInputStream; 33 import java.net.MalformedURLException; 34 import java.net.URL; 35 import java.security.AuthProvider; 36 import java.security.GeneralSecurityException; 37 import java.security.Key; 38 import java.security.KeyStore; 39 import java.security.KeyStoreException; 40 import java.security.NoSuchAlgorithmException; 41 import java.security.NoSuchProviderException; 42 import java.security.Principal; 43 import java.security.PrivateKey; 44 import java.security.Provider; 45 import java.security.UnrecoverableKeyException; 46 import java.security.cert.*; 47 import java.security.cert.X509Certificate; 48 import java.util.Arrays; 49 import java.util.Iterator; 50 import java.util.LinkedList; 51 import java.util.Map; 52 import java.util.ResourceBundle; 53 import javax.security.auth.Destroyable; 54 import javax.security.auth.DestroyFailedException; 55 import javax.security.auth.Subject; 56 import javax.security.auth.x500.*; 57 import javax.security.auth.Subject; 58 import javax.security.auth.x500.*; 59 import javax.security.auth.callback.Callback; 60 import javax.security.auth.callback.CallbackHandler; 61 import javax.security.auth.callback.ConfirmationCallback; 62 import javax.security.auth.callback.NameCallback; 63 import javax.security.auth.callback.PasswordCallback; 64 import javax.security.auth.callback.TextOutputCallback; 65 import javax.security.auth.callback.UnsupportedCallbackException; 66 import javax.security.auth.login.FailedLoginException; 67 import javax.security.auth.login.LoginException; 68 import javax.security.auth.spi.LoginModule; 69 70 import sun.security.util.AuthResources; 71 import sun.security.util.Password; 72 73 /** 74 * Provides a JAAS login module that prompts for a key store alias and 75 * populates the subject with the alias's principal and credentials. Stores 76 * an <code>X500Principal</code> for the subject distinguished name of the 77 * first certificate in the alias's credentials in the subject's principals, 78 * the alias's certificate path in the subject's public credentials, and a 79 * <code>X500PrivateCredential</code> whose certificate is the first 80 * certificate in the alias's certificate path and whose private key is the 81 * alias's private key in the subject's private credentials. <p> 82 * 83 * Recognizes the following options in the configuration file: 84 * <dl> 85 * 86 * <dt> <code>keyStoreURL</code> </dt> 87 * <dd> A URL that specifies the location of the key store. Defaults to 88 * a URL pointing to the .keystore file in the directory specified by the 89 * <code>user.home</code> system property. The input stream from this 90 * URL is passed to the <code>KeyStore.load</code> method. 91 * "NONE" may be specified if a <code>null</code> stream must be 92 * passed to the <code>KeyStore.load</code> method. 93 * "NONE" should be specified if the KeyStore resides 94 * on a hardware token device, for example.</dd> 95 * 96 * <dt> <code>keyStoreType</code> </dt> 97 * <dd> The key store type. If not specified, defaults to the result of 98 * calling <code>KeyStore.getDefaultType()</code>. 99 * If the type is "PKCS11", then keyStoreURL must be "NONE" 100 * and privateKeyPasswordURL must not be specified.</dd> 101 * 102 * <dt> <code>keyStoreProvider</code> </dt> 103 * <dd> The key store provider. If not specified, uses the standard search 104 * order to find the provider. </dd> 105 * 106 * <dt> <code>keyStoreAlias</code> </dt> 107 * <dd> The alias in the key store to login as. Required when no callback 108 * handler is provided. No default value. </dd> 109 * 110 * <dt> <code>keyStorePasswordURL</code> </dt> 111 * <dd> A URL that specifies the location of the key store password. Required 112 * when no callback handler is provided and 113 * <code>protected</code> is false. 114 * No default value. </dd> 115 * 116 * <dt> <code>privateKeyPasswordURL</code> </dt> 117 * <dd> A URL that specifies the location of the specific private key password 118 * needed to access the private key for this alias. 119 * The keystore password 120 * is used if this value is needed and not specified. </dd> 121 * 122 * <dt> <code>protected</code> </dt> 123 * <dd> This value should be set to "true" if the KeyStore 124 * has a separate, protected authentication path 125 * (for example, a dedicated PIN-pad attached to a smart card). 126 * Defaults to "false". If "true" keyStorePasswordURL and 127 * privateKeyPasswordURL must not be specified.</dd> 128 * 129 * </dl> 130 */ 131 public class KeyStoreLoginModule implements LoginModule { 132 133 static final java.util.ResourceBundle rb = 134 java.util.ResourceBundle.getBundle("sun.security.util.AuthResources"); 135 136 /* -- Fields -- */ 137 138 private static final int UNINITIALIZED = 0; 139 private static final int INITIALIZED = 1; 140 private static final int AUTHENTICATED = 2; 141 private static final int LOGGED_IN = 3; 142 143 private static final int PROTECTED_PATH = 0; 144 private static final int TOKEN = 1; 145 private static final int NORMAL = 2; 146 147 private static final String NONE = "NONE"; 148 private static final String P11KEYSTORE = "PKCS11"; 149 150 private static final TextOutputCallback bannerCallback = 151 new TextOutputCallback 152 (TextOutputCallback.INFORMATION, 153 rb.getString("Please.enter.keystore.information")); 154 private final ConfirmationCallback confirmationCallback = 155 new ConfirmationCallback 156 (ConfirmationCallback.INFORMATION, 157 ConfirmationCallback.OK_CANCEL_OPTION, 158 ConfirmationCallback.OK); 159 160 private Subject subject; 161 private CallbackHandler callbackHandler; 162 private Map sharedState; 163 private Map<String, ?> options; 164 165 private char[] keyStorePassword; 166 private char[] privateKeyPassword; 167 private KeyStore keyStore; 168 169 private String keyStoreURL; 170 private String keyStoreType; 171 private String keyStoreProvider; 172 private String keyStoreAlias; 173 private String keyStorePasswordURL; 174 private String privateKeyPasswordURL; 175 private boolean debug; 176 private javax.security.auth.x500.X500Principal principal; 177 private Certificate[] fromKeyStore; 178 private java.security.cert.CertPath certP = null; 179 private X500PrivateCredential privateCredential; 180 private int status = UNINITIALIZED; 181 private boolean nullStream = false; 182 private boolean token = false; 183 private boolean protectedPath = false; 184 185 /* -- Methods -- */ 186 187 /** 188 * Initialize this <code>LoginModule</code>. 189 * 190 * <p> 191 * 192 * @param subject the <code>Subject</code> to be authenticated. <p> 193 * 194 * @param callbackHandler a <code>CallbackHandler</code> for communicating 195 * with the end user (prompting for usernames and 196 * passwords, for example), 197 * which may be <code>null</code>. <p> 198 * 199 * @param sharedState shared <code>LoginModule</code> state. <p> 200 * 201 * @param options options specified in the login 202 * <code>Configuration</code> for this particular 203 * <code>LoginModule</code>. 204 */ 205 206 public void initialize(Subject subject, 207 CallbackHandler callbackHandler, 208 Map<String,?> sharedState, 209 Map<String,?> options) 210 { 211 this.subject = subject; 212 this.callbackHandler = callbackHandler; 213 this.sharedState = sharedState; 214 this.options = options; 215 216 processOptions(); 217 status = INITIALIZED; 218 } 219 220 private void processOptions() { 221 keyStoreURL = (String) options.get("keyStoreURL"); 222 if (keyStoreURL == null) { 223 keyStoreURL = 224 "file:" + 225 System.getProperty("user.home").replace( 226 File.separatorChar, '/') + 227 '/' + ".keystore"; 228 } else if (NONE.equals(keyStoreURL)) { 229 nullStream = true; 230 } 231 keyStoreType = (String) options.get("keyStoreType"); 232 if (keyStoreType == null) { 233 keyStoreType = KeyStore.getDefaultType(); 234 } 235 if (P11KEYSTORE.equalsIgnoreCase(keyStoreType)) { 236 token = true; 237 } 238 239 keyStoreProvider = (String) options.get("keyStoreProvider"); 240 241 keyStoreAlias = (String) options.get("keyStoreAlias"); 242 243 keyStorePasswordURL = (String) options.get("keyStorePasswordURL"); 244 245 privateKeyPasswordURL = (String) options.get("privateKeyPasswordURL"); 246 247 protectedPath = "true".equalsIgnoreCase((String)options.get 248 ("protected")); 249 250 debug = "true".equalsIgnoreCase((String) options.get("debug")); 251 if (debug) { 252 debugPrint(null); 253 debugPrint("keyStoreURL=" + keyStoreURL); 254 debugPrint("keyStoreType=" + keyStoreType); 255 debugPrint("keyStoreProvider=" + keyStoreProvider); 256 debugPrint("keyStoreAlias=" + keyStoreAlias); 257 debugPrint("keyStorePasswordURL=" + keyStorePasswordURL); 258 debugPrint("privateKeyPasswordURL=" + privateKeyPasswordURL); 259 debugPrint("protectedPath=" + protectedPath); 260 debugPrint(null); 261 } 262 } 263 264 /** 265 * Authenticate the user. 266 * 267 * <p> Get the Keystore alias and relevant passwords. 268 * Retrieve the alias's principal and credentials from the Keystore. 269 * 270 * <p> 271 * 272 * @exception FailedLoginException if the authentication fails. <p> 273 * 274 * @return true in all cases (this <code>LoginModule</code> 275 * should not be ignored). 276 */ 277 278 public boolean login() throws LoginException { 279 switch (status) { 280 case UNINITIALIZED: 281 default: 282 throw new LoginException("The login module is not initialized"); 283 case INITIALIZED: 284 case AUTHENTICATED: 285 286 if (token && !nullStream) { 287 throw new LoginException 288 ("if keyStoreType is " + P11KEYSTORE + 289 " then keyStoreURL must be " + NONE); 290 } 291 292 if (token && privateKeyPasswordURL != null) { 293 throw new LoginException 294 ("if keyStoreType is " + P11KEYSTORE + 295 " then privateKeyPasswordURL must not be specified"); 296 } 297 298 if (protectedPath && 299 (keyStorePasswordURL != null || 300 privateKeyPasswordURL != null)) { 301 throw new LoginException 302 ("if protected is true then keyStorePasswordURL and " + 303 "privateKeyPasswordURL must not be specified"); 304 } 305 306 // get relevant alias and password info 307 308 if (protectedPath) { 309 getAliasAndPasswords(PROTECTED_PATH); 310 } else if (token) { 311 getAliasAndPasswords(TOKEN); 312 } else { 313 getAliasAndPasswords(NORMAL); 314 } 315 316 // log into KeyStore to retrieve data, 317 // then clear passwords 318 319 try { 320 getKeyStoreInfo(); 321 } finally { 322 if (privateKeyPassword != null && 323 privateKeyPassword != keyStorePassword) { 324 Arrays.fill(privateKeyPassword, '\0'); 325 privateKeyPassword = null; 326 } 327 if (keyStorePassword != null) { 328 Arrays.fill(keyStorePassword, '\0'); 329 keyStorePassword = null; 330 } 331 } 332 status = AUTHENTICATED; 333 return true; 334 case LOGGED_IN: 335 return true; 336 } 337 } 338 339 /** Get the alias and passwords to use for looking up in the KeyStore. */ 340 private void getAliasAndPasswords(int env) throws LoginException { 341 if (callbackHandler == null) { 342 343 // No callback handler - check for alias and password options 344 345 switch (env) { 346 case PROTECTED_PATH: 347 checkAlias(); 348 break; 349 case TOKEN: 350 checkAlias(); 351 checkStorePass(); 352 break; 353 case NORMAL: 354 checkAlias(); 355 checkStorePass(); 356 checkKeyPass(); 357 break; 358 } 359 360 } else { 361 362 // Callback handler available - prompt for alias and passwords 363 364 NameCallback aliasCallback; 365 if (keyStoreAlias == null || keyStoreAlias.length() == 0) { 366 aliasCallback = new NameCallback( 367 rb.getString("Keystore.alias.")); 368 } else { 369 aliasCallback = 370 new NameCallback(rb.getString("Keystore.alias."), 371 keyStoreAlias); 372 } 373 374 PasswordCallback storePassCallback = null; 375 PasswordCallback keyPassCallback = null; 376 377 switch (env) { 378 case PROTECTED_PATH: 379 break; 380 case NORMAL: 381 keyPassCallback = new PasswordCallback 382 (rb.getString("Private.key.password.optional."), false); 383 // fall thru 384 case TOKEN: 385 storePassCallback = new PasswordCallback 386 (rb.getString("Keystore.password."), false); 387 break; 388 } 389 prompt(aliasCallback, storePassCallback, keyPassCallback); 390 } 391 392 if (debug) { 393 debugPrint("alias=" + keyStoreAlias); 394 } 395 } 396 397 private void checkAlias() throws LoginException { 398 if (keyStoreAlias == null) { 399 throw new LoginException 400 ("Need to specify an alias option to use " + 401 "KeyStoreLoginModule non-interactively."); 402 } 403 } 404 405 private void checkStorePass() throws LoginException { 406 if (keyStorePasswordURL == null) { 407 throw new LoginException 408 ("Need to specify keyStorePasswordURL option to use " + 409 "KeyStoreLoginModule non-interactively."); 410 } 411 InputStream in = null; 412 try { 413 in = new URL(keyStorePasswordURL).openStream(); 414 keyStorePassword = Password.readPassword(in); 415 } catch (IOException e) { 416 LoginException le = new LoginException 417 ("Problem accessing keystore password \"" + 418 keyStorePasswordURL + "\""); 419 le.initCause(e); 420 throw le; 421 } finally { 422 if (in != null) { 423 try { 424 in.close(); 425 } catch (IOException ioe) { 426 LoginException le = new LoginException( 427 "Problem closing the keystore password stream"); 428 le.initCause(ioe); 429 throw le; 430 } 431 } 432 } 433 } 434 435 private void checkKeyPass() throws LoginException { 436 if (privateKeyPasswordURL == null) { 437 privateKeyPassword = keyStorePassword; 438 } else { 439 InputStream in = null; 440 try { 441 in = new URL(privateKeyPasswordURL).openStream(); 442 privateKeyPassword = Password.readPassword(in); 443 } catch (IOException e) { 444 LoginException le = new LoginException 445 ("Problem accessing private key password \"" + 446 privateKeyPasswordURL + "\""); 447 le.initCause(e); 448 throw le; 449 } finally { 450 if (in != null) { 451 try { 452 in.close(); 453 } catch (IOException ioe) { 454 LoginException le = new LoginException( 455 "Problem closing the private key password stream"); 456 le.initCause(ioe); 457 throw le; 458 } 459 } 460 } 461 } 462 } 463 464 private void prompt(NameCallback aliasCallback, 465 PasswordCallback storePassCallback, 466 PasswordCallback keyPassCallback) 467 throws LoginException { 468 469 if (storePassCallback == null) { 470 471 // only prompt for alias 472 473 try { 474 callbackHandler.handle( 475 new Callback[] { 476 bannerCallback, aliasCallback, confirmationCallback 477 }); 478 } catch (IOException e) { 479 LoginException le = new LoginException 480 ("Problem retrieving keystore alias"); 481 le.initCause(e); 482 throw le; 483 } catch (UnsupportedCallbackException e) { 484 throw new LoginException( 485 "Error: " + e.getCallback().toString() + 486 " is not available to retrieve authentication " + 487 " information from the user"); 488 } 489 490 int confirmationResult = confirmationCallback.getSelectedIndex(); 491 492 if (confirmationResult == ConfirmationCallback.CANCEL) { 493 throw new LoginException("Login cancelled"); 494 } 495 496 saveAlias(aliasCallback); 497 498 } else if (keyPassCallback == null) { 499 500 // prompt for alias and key store password 501 502 try { 503 callbackHandler.handle( 504 new Callback[] { 505 bannerCallback, aliasCallback, 506 storePassCallback, confirmationCallback 507 }); 508 } catch (IOException e) { 509 LoginException le = new LoginException 510 ("Problem retrieving keystore alias and password"); 511 le.initCause(e); 512 throw le; 513 } catch (UnsupportedCallbackException e) { 514 throw new LoginException( 515 "Error: " + e.getCallback().toString() + 516 " is not available to retrieve authentication " + 517 " information from the user"); 518 } 519 520 int confirmationResult = confirmationCallback.getSelectedIndex(); 521 522 if (confirmationResult == ConfirmationCallback.CANCEL) { 523 throw new LoginException("Login cancelled"); 524 } 525 526 saveAlias(aliasCallback); 527 saveStorePass(storePassCallback); 528 529 } else { 530 531 // prompt for alias, key store password, and key password 532 533 try { 534 callbackHandler.handle( 535 new Callback[] { 536 bannerCallback, aliasCallback, 537 storePassCallback, keyPassCallback, 538 confirmationCallback 539 }); 540 } catch (IOException e) { 541 LoginException le = new LoginException 542 ("Problem retrieving keystore alias and passwords"); 543 le.initCause(e); 544 throw le; 545 } catch (UnsupportedCallbackException e) { 546 throw new LoginException( 547 "Error: " + e.getCallback().toString() + 548 " is not available to retrieve authentication " + 549 " information from the user"); 550 } 551 552 int confirmationResult = confirmationCallback.getSelectedIndex(); 553 554 if (confirmationResult == ConfirmationCallback.CANCEL) { 555 throw new LoginException("Login cancelled"); 556 } 557 558 saveAlias(aliasCallback); 559 saveStorePass(storePassCallback); 560 saveKeyPass(keyPassCallback); 561 } 562 } 563 564 private void saveAlias(NameCallback cb) { 565 keyStoreAlias = cb.getName(); 566 } 567 568 private void saveStorePass(PasswordCallback c) { 569 keyStorePassword = c.getPassword(); 570 if (keyStorePassword == null) { 571 /* Treat a NULL password as an empty password */ 572 keyStorePassword = new char[0]; 573 } 574 c.clearPassword(); 575 } 576 577 private void saveKeyPass(PasswordCallback c) { 578 privateKeyPassword = c.getPassword(); 579 if (privateKeyPassword == null || privateKeyPassword.length == 0) { 580 /* 581 * Use keystore password if no private key password is 582 * specified. 583 */ 584 privateKeyPassword = keyStorePassword; 585 } 586 c.clearPassword(); 587 } 588 589 /** Get the credentials from the KeyStore. */ 590 private void getKeyStoreInfo() throws LoginException { 591 592 /* Get KeyStore instance */ 593 try { 594 if (keyStoreProvider == null) { 595 keyStore = KeyStore.getInstance(keyStoreType); 596 } else { 597 keyStore = 598 KeyStore.getInstance(keyStoreType, keyStoreProvider); 599 } 600 } catch (KeyStoreException e) { 601 LoginException le = new LoginException 602 ("The specified keystore type was not available"); 603 le.initCause(e); 604 throw le; 605 } catch (NoSuchProviderException e) { 606 LoginException le = new LoginException 607 ("The specified keystore provider was not available"); 608 le.initCause(e); 609 throw le; 610 } 611 612 /* Load KeyStore contents from file */ 613 InputStream in = null; 614 try { 615 if (nullStream) { 616 // if using protected auth path, keyStorePassword will be null 617 keyStore.load(null, keyStorePassword); 618 } else { 619 in = new URL(keyStoreURL).openStream(); 620 keyStore.load(in, keyStorePassword); 621 } 622 } catch (MalformedURLException e) { 623 LoginException le = new LoginException 624 ("Incorrect keyStoreURL option"); 625 le.initCause(e); 626 throw le; 627 } catch (GeneralSecurityException e) { 628 LoginException le = new LoginException 629 ("Error initializing keystore"); 630 le.initCause(e); 631 throw le; 632 } catch (IOException e) { 633 LoginException le = new LoginException 634 ("Error initializing keystore"); 635 le.initCause(e); 636 throw le; 637 } finally { 638 if (in != null) { 639 try { 640 in.close(); 641 } catch (IOException ioe) { 642 LoginException le = new LoginException 643 ("Error initializing keystore"); 644 le.initCause(ioe); 645 throw le; 646 } 647 } 648 } 649 650 /* Get certificate chain and create a certificate path */ 651 try { 652 fromKeyStore = 653 keyStore.getCertificateChain(keyStoreAlias); 654 if (fromKeyStore == null 655 || fromKeyStore.length == 0 656 || !(fromKeyStore[0] instanceof X509Certificate)) 657 { 658 throw new FailedLoginException( 659 "Unable to find X.509 certificate chain in keystore"); 660 } else { 661 LinkedList<Certificate> certList = new LinkedList<>(); 662 for (int i=0; i < fromKeyStore.length; i++) { 663 certList.add(fromKeyStore[i]); 664 } 665 CertificateFactory certF= 666 CertificateFactory.getInstance("X.509"); 667 certP = 668 certF.generateCertPath(certList); 669 } 670 } catch (KeyStoreException e) { 671 LoginException le = new LoginException("Error using keystore"); 672 le.initCause(e); 673 throw le; 674 } catch (CertificateException ce) { 675 LoginException le = new LoginException 676 ("Error: X.509 Certificate type unavailable"); 677 le.initCause(ce); 678 throw le; 679 } 680 681 /* Get principal and keys */ 682 try { 683 X509Certificate certificate = (X509Certificate)fromKeyStore[0]; 684 principal = new javax.security.auth.x500.X500Principal 685 (certificate.getSubjectDN().getName()); 686 687 // if token, privateKeyPassword will be null 688 Key privateKey = keyStore.getKey(keyStoreAlias, privateKeyPassword); 689 if (privateKey == null 690 || !(privateKey instanceof PrivateKey)) 691 { 692 throw new FailedLoginException( 693 "Unable to recover key from keystore"); 694 } 695 696 privateCredential = new X500PrivateCredential( 697 certificate, (PrivateKey) privateKey, keyStoreAlias); 698 } catch (KeyStoreException e) { 699 LoginException le = new LoginException("Error using keystore"); 700 le.initCause(e); 701 throw le; 702 } catch (NoSuchAlgorithmException e) { 703 LoginException le = new LoginException("Error using keystore"); 704 le.initCause(e); 705 throw le; 706 } catch (UnrecoverableKeyException e) { 707 FailedLoginException fle = new FailedLoginException 708 ("Unable to recover key from keystore"); 709 fle.initCause(e); 710 throw fle; 711 } 712 if (debug) { 713 debugPrint("principal=" + principal + 714 "\n certificate=" 715 + privateCredential.getCertificate() + 716 "\n alias =" + privateCredential.getAlias()); 717 } 718 } 719 720 /** 721 * Abstract method to commit the authentication process (phase 2). 722 * 723 * <p> This method is called if the LoginContext's 724 * overall authentication succeeded 725 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules 726 * succeeded). 727 * 728 * <p> If this LoginModule's own authentication attempt 729 * succeeded (checked by retrieving the private state saved by the 730 * <code>login</code> method), then this method associates a 731 * <code>X500Principal</code> for the subject distinguished name of the 732 * first certificate in the alias's credentials in the subject's 733 * principals,the alias's certificate path in the subject's public 734 * credentials, and a<code>X500PrivateCredential</code> whose certificate 735 * is the first certificate in the alias's certificate path and whose 736 * private key is the alias's private key in the subject's private 737 * credentials. If this LoginModule's own 738 * authentication attempted failed, then this method removes 739 * any state that was originally saved. 740 * 741 * <p> 742 * 743 * @exception LoginException if the commit fails 744 * 745 * @return true if this LoginModule's own login and commit 746 * attempts succeeded, or false otherwise. 747 */ 748 749 public boolean commit() throws LoginException { 750 switch (status) { 751 case UNINITIALIZED: 752 default: 753 throw new LoginException("The login module is not initialized"); 754 case INITIALIZED: 755 logoutInternal(); 756 throw new LoginException("Authentication failed"); 757 case AUTHENTICATED: 758 if (commitInternal()) { 759 return true; 760 } else { 761 logoutInternal(); 762 throw new LoginException("Unable to retrieve certificates"); 763 } 764 case LOGGED_IN: 765 return true; 766 } 767 } 768 769 private boolean commitInternal() throws LoginException { 770 /* If the subject is not readonly add to the principal and credentials 771 * set; otherwise just return true 772 */ 773 if (subject.isReadOnly()) { 774 throw new LoginException ("Subject is set readonly"); 775 } else { 776 subject.getPrincipals().add(principal); 777 subject.getPublicCredentials().add(certP); 778 subject.getPrivateCredentials().add(privateCredential); 779 status = LOGGED_IN; 780 return true; 781 } 782 } 783 784 /** 785 * <p> This method is called if the LoginContext's 786 * overall authentication failed. 787 * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules 788 * did not succeed). 789 * 790 * <p> If this LoginModule's own authentication attempt 791 * succeeded (checked by retrieving the private state saved by the 792 * <code>login</code> and <code>commit</code> methods), 793 * then this method cleans up any state that was originally saved. 794 * 795 * <p> If the loaded KeyStore's provider extends 796 * <code>java.security.AuthProvider</code>, 797 * then the provider's <code>logout</code> method is invoked. 798 * 799 * <p> 800 * 801 * @exception LoginException if the abort fails. 802 * 803 * @return false if this LoginModule's own login and/or commit attempts 804 * failed, and true otherwise. 805 */ 806 807 public boolean abort() throws LoginException { 808 switch (status) { 809 case UNINITIALIZED: 810 default: 811 return false; 812 case INITIALIZED: 813 return false; 814 case AUTHENTICATED: 815 logoutInternal(); 816 return true; 817 case LOGGED_IN: 818 logoutInternal(); 819 return true; 820 } 821 } 822 /** 823 * Logout a user. 824 * 825 * <p> This method removes the Principals, public credentials and the 826 * private credentials that were added by the <code>commit</code> method. 827 * 828 * <p> If the loaded KeyStore's provider extends 829 * <code>java.security.AuthProvider</code>, 830 * then the provider's <code>logout</code> method is invoked. 831 * 832 * <p> 833 * 834 * @exception LoginException if the logout fails. 835 * 836 * @return true in all cases since this <code>LoginModule</code> 837 * should not be ignored. 838 */ 839 840 public boolean logout() throws LoginException { 841 if (debug) 842 debugPrint("Entering logout " + status); 843 switch (status) { 844 case UNINITIALIZED: 845 throw new LoginException 846 ("The login module is not initialized"); 847 case INITIALIZED: 848 case AUTHENTICATED: 849 default: 850 // impossible for LoginModule to be in AUTHENTICATED 851 // state 852 // assert status != AUTHENTICATED; 853 return false; 854 case LOGGED_IN: 855 logoutInternal(); 856 return true; 857 } 858 } 859 860 private void logoutInternal() throws LoginException { 861 if (debug) { 862 debugPrint("Entering logoutInternal"); 863 } 864 865 // assumption is that KeyStore.load did a login - 866 // perform explicit logout if possible 867 LoginException logoutException = null; 868 Provider provider = keyStore.getProvider(); 869 if (provider instanceof AuthProvider) { 870 AuthProvider ap = (AuthProvider)provider; 871 try { 872 ap.logout(); 873 if (debug) { 874 debugPrint("logged out of KeyStore AuthProvider"); 875 } 876 } catch (LoginException le) { 877 // save but continue below 878 logoutException = le; 879 } 880 } 881 882 if (subject.isReadOnly()) { 883 // attempt to destroy the private credential 884 // even if the Subject is read-only 885 principal = null; 886 certP = null; 887 status = INITIALIZED; 888 // destroy the private credential 889 Iterator<Object> it = subject.getPrivateCredentials().iterator(); 890 while (it.hasNext()) { 891 Object obj = it.next(); 892 if (privateCredential.equals(obj)) { 893 privateCredential = null; 894 try { 895 ((Destroyable)obj).destroy(); 896 if (debug) 897 debugPrint("Destroyed private credential, " + 898 obj.getClass().getName()); 899 break; 900 } catch (DestroyFailedException dfe) { 901 LoginException le = new LoginException 902 ("Unable to destroy private credential, " 903 + obj.getClass().getName()); 904 le.initCause(dfe); 905 throw le; 906 } 907 } 908 } 909 910 // throw an exception because we can not remove 911 // the principal and public credential from this 912 // read-only Subject 913 throw new LoginException 914 ("Unable to remove Principal (" 915 + "X500Principal " 916 + ") and public credential (certificatepath) " 917 + "from read-only Subject"); 918 } 919 if (principal != null) { 920 subject.getPrincipals().remove(principal); 921 principal = null; 922 } 923 if (certP != null) { 924 subject.getPublicCredentials().remove(certP); 925 certP = null; 926 } 927 if (privateCredential != null) { 928 subject.getPrivateCredentials().remove(privateCredential); 929 privateCredential = null; 930 } 931 932 // throw pending logout exception if there is one 933 if (logoutException != null) { 934 throw logoutException; 935 } 936 status = INITIALIZED; 937 } 938 939 private void debugPrint(String message) { 940 // we should switch to logging API 941 if (message == null) { 942 System.err.println(); 943 } else { 944 System.err.println("Debug KeyStoreLoginModule: " + message); 945 } 946 } 947 }