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 }