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