1 7 8 package com.sun.security.auth.module; 9 10 import javax.security.auth.x500.X500Principal ; 11 import java.io.File ; 12 import java.io.IOException ; 13 import java.io.InputStream ; 14 import java.io.PushbackInputStream ; 15 import java.net.MalformedURLException ; 16 import java.net.URL ; 17 import java.security.AuthProvider ; 18 import java.security.GeneralSecurityException ; 19 import java.security.Key ; 20 import java.security.KeyStore ; 21 import java.security.KeyStoreException ; 22 import java.security.NoSuchAlgorithmException ; 23 import java.security.NoSuchProviderException ; 24 import java.security.Principal ; 25 import java.security.PrivateKey ; 26 import java.security.Provider ; 27 import java.security.UnrecoverableKeyException ; 28 import java.security.cert.*; 29 import java.security.cert.X509Certificate ; 30 import java.util.Arrays ; 31 import java.util.Iterator ; 32 import java.util.LinkedList ; 33 import java.util.Map ; 34 import java.util.ResourceBundle ; 35 import javax.security.auth.Destroyable ; 36 import javax.security.auth.DestroyFailedException ; 37 import javax.security.auth.Subject ; 38 import javax.security.auth.x500.*; 39 import javax.security.auth.Subject ; 40 import javax.security.auth.x500.*; 41 import javax.security.auth.callback.Callback ; 42 import javax.security.auth.callback.CallbackHandler ; 43 import javax.security.auth.callback.ConfirmationCallback ; 44 import javax.security.auth.callback.NameCallback ; 45 import javax.security.auth.callback.PasswordCallback ; 46 import javax.security.auth.callback.TextOutputCallback ; 47 import javax.security.auth.callback.UnsupportedCallbackException ; 48 import javax.security.auth.login.FailedLoginException ; 49 import javax.security.auth.login.LoginException ; 50 import javax.security.auth.spi.LoginModule ; 51 52 import sun.security.util.AuthResources; 53 import sun.security.util.Password; 54 55 113 public class KeyStoreLoginModule implements LoginModule { 114 115 static final java.util.ResourceBundle rb = 116 java.util.ResourceBundle.getBundle("sun.security.util.AuthResources"); 117 118 119 120 private static final int UNINITIALIZED = 0; 121 private static final int INITIALIZED = 1; 122 private static final int AUTHENTICATED = 2; 123 private static final int LOGGED_IN = 3; 124 125 private static final int PROTECTED_PATH = 0; 126 private static final int TOKEN = 1; 127 private static final int NORMAL = 2; 128 129 private static final String NONE = "NONE"; 130 private static final String P11KEYSTORE = "PKCS11"; 131 132 private static final TextOutputCallback bannerCallback = 133 new TextOutputCallback 134 (TextOutputCallback.INFORMATION, 135 rb.getString("Please enter keystore information")); 136 private final ConfirmationCallback confirmationCallback = 137 new ConfirmationCallback 138 (ConfirmationCallback.INFORMATION, 139 ConfirmationCallback.OK_CANCEL_OPTION, 140 ConfirmationCallback.OK); 141 142 private Subject subject; 143 private CallbackHandler callbackHandler; 144 private Map sharedState; 145 private Map options; 146 147 private char[] keyStorePassword; 148 private char[] privateKeyPassword; 149 private KeyStore keyStore; 150 151 private String keyStoreURL; 152 private String keyStoreType; 153 private String keyStoreProvider; 154 private String keyStoreAlias; 155 private String keyStorePasswordURL; 156 private String privateKeyPasswordURL; 157 private boolean debug; 158 private javax.security.auth.x500.X500Principal principal; 159 private Certificate [] fromKeyStore; 160 private java.security.cert.CertPath certP = null; 161 private X500PrivateCredential privateCredential; 162 private int status = UNINITIALIZED; 163 private boolean nullStream = false; 164 private boolean token = false; 165 private boolean protectedPath = false; 166 167 168 169 187 188 public void initialize(Subject subject, 189 CallbackHandler callbackHandler, 190 Map <String ,?> sharedState, 191 Map <String ,?> options) 192 { 193 this.subject = subject; 194 this.callbackHandler = callbackHandler; 195 this.sharedState = sharedState; 196 this.options = options; 197 198 processOptions(); 199 status = INITIALIZED; 200 } 201 202 private void processOptions() { 203 keyStoreURL = (String ) options.get("keyStoreURL"); 204 if (keyStoreURL == null) { 205 keyStoreURL = 206 "file:" + 207 System.getProperty("user.home").replace( 208 File.separatorChar, '/') + 209 '/' + ".keystore"; 210 } else if (NONE.equals(keyStoreURL)) { 211 nullStream = true; 212 } 213 keyStoreType = (String ) options.get("keyStoreType"); 214 if (keyStoreType == null) { 215 keyStoreType = KeyStore.getDefaultType(); 216 } 217 if (P11KEYSTORE.equalsIgnoreCase(keyStoreType)) { 218 token = true; 219 } 220 221 keyStoreProvider = (String ) options.get("keyStoreProvider"); 222 223 keyStoreAlias = (String ) options.get("keyStoreAlias"); 224 225 keyStorePasswordURL = (String ) options.get("keyStorePasswordURL"); 226 227 privateKeyPasswordURL = (String ) options.get("privateKeyPasswordURL"); 228 229 protectedPath = "true".equalsIgnoreCase((String )options.get 230 ("protected")); 231 232 debug = "true".equalsIgnoreCase((String ) options.get("debug")); 233 if (debug) { 234 debugPrint(null); 235 debugPrint("keyStoreURL=" + keyStoreURL); 236 debugPrint("keyStoreType=" + keyStoreType); 237 debugPrint("keyStoreProvider=" + keyStoreProvider); 238 debugPrint("keyStoreAlias=" + keyStoreAlias); 239 debugPrint("keyStorePasswordURL=" + keyStorePasswordURL); 240 debugPrint("privateKeyPasswordURL=" + privateKeyPasswordURL); 241 debugPrint("protectedPath=" + protectedPath); 242 debugPrint(null); 243 } 244 } 245 246 259 260 public boolean login() throws LoginException { 261 switch (status) { 262 case UNINITIALIZED: 263 default: 264 throw new LoginException ("The login module is not initialized"); 265 case INITIALIZED: 266 case AUTHENTICATED: 267 268 if (token && !nullStream) { 269 throw new LoginException 270 ("if keyStoreType is " + P11KEYSTORE + 271 " then keyStoreURL must be " + NONE); 272 } 273 274 if (token && privateKeyPasswordURL != null) { 275 throw new LoginException 276 ("if keyStoreType is " + P11KEYSTORE + 277 " then privateKeyPasswordURL must not be specified"); 278 } 279 280 if (protectedPath && 281 (keyStorePasswordURL != null || 282 privateKeyPasswordURL != null)) { 283 throw new LoginException 284 ("if protected is true then keyStorePasswordURL and " + 285 "privateKeyPasswordURL must not be specified"); 286 } 287 288 290 if (protectedPath) { 291 getAliasAndPasswords(PROTECTED_PATH); 292 } else if (token) { 293 getAliasAndPasswords(TOKEN); 294 } else { 295 getAliasAndPasswords(NORMAL); 296 } 297 298 301 try { 302 getKeyStoreInfo(); 303 } finally { 304 if (privateKeyPassword != null && 305 privateKeyPassword != keyStorePassword) { 306 Arrays.fill(privateKeyPassword, '\0'); 307 privateKeyPassword = null; 308 } 309 if (keyStorePassword != null) { 310 Arrays.fill(keyStorePassword, '\0'); 311 keyStorePassword = null; 312 } 313 } 314 status = AUTHENTICATED; 315 return true; 316 case LOGGED_IN: 317 return true; 318 } 319 } 320 321 322 private void getAliasAndPasswords(int env) throws LoginException { 323 if (callbackHandler == null) { 324 325 327 switch (env) { 328 case PROTECTED_PATH: 329 checkAlias(); 330 break; 331 case TOKEN: 332 checkAlias(); 333 checkStorePass(); 334 break; 335 case NORMAL: 336 checkAlias(); 337 checkStorePass(); 338 checkKeyPass(); 339 break; 340 } 341 342 } else { 343 344 346 NameCallback aliasCallback; 347 if (keyStoreAlias == null || keyStoreAlias.length() == 0) { 348 aliasCallback = new NameCallback ( 349 rb.getString("Keystore alias: ")); 350 } else { 351 aliasCallback = 352 new NameCallback (rb.getString("Keystore alias: "), 353 keyStoreAlias); 354 } 355 356 PasswordCallback storePassCallback = null; 357 PasswordCallback keyPassCallback = null; 358 359 switch (env) { 360 case PROTECTED_PATH: 361 break; 362 case NORMAL: 363 keyPassCallback = new PasswordCallback 364 (rb.getString("Private key password (optional): "), false); 365 case TOKEN: 367 storePassCallback = new PasswordCallback 368 (rb.getString("Keystore password: "), false); 369 break; 370 } 371 prompt(aliasCallback, storePassCallback, keyPassCallback); 372 } 373 374 if (debug) { 375 debugPrint("alias=" + keyStoreAlias); 376 } 377 } 378 379 private void checkAlias() throws LoginException { 380 if (keyStoreAlias == null) { 381 throw new LoginException 382 ("Need to specify an alias option to use " + 383 "KeyStoreLoginModule non-interactively."); 384 } 385 } 386 387 private void checkStorePass() throws LoginException { 388 if (keyStorePasswordURL == null) { 389 throw new LoginException 390 ("Need to specify keyStorePasswordURL option to use " + 391 "KeyStoreLoginModule non-interactively."); 392 } 393 try { 394 InputStream in = new URL (keyStorePasswordURL).openStream(); 395 keyStorePassword = Password.readPassword(in); 396 in.close(); 397 } catch (IOException e) { 398 LoginException le = new LoginException 399 ("Problem accessing keystore password \"" + 400 keyStorePasswordURL + "\""); 401 le.initCause(e); 402 throw le; 403 } 404 } 405 406 private void checkKeyPass() throws LoginException { 407 if (privateKeyPasswordURL == null) { 408 privateKeyPassword = keyStorePassword; 409 } else { 410 try { 411 InputStream in = new URL (privateKeyPasswordURL).openStream(); 412 privateKeyPassword = Password.readPassword(in); 413 in.close(); 414 } catch (IOException e) { 415 LoginException le = new LoginException 416 ("Problem accessing private key password \"" + 417 privateKeyPasswordURL + "\""); 418 le.initCause(e); 419 throw le; 420 } 421 } 422 } 423 424 private void prompt(NameCallback aliasCallback, 425 PasswordCallback storePassCallback, 426 PasswordCallback keyPassCallback) 427 throws LoginException { 428 429 if (storePassCallback == null) { 430 431 433 try { 434 callbackHandler.handle( 435 new Callback [] { 436 bannerCallback, aliasCallback, confirmationCallback 437 }); 438 } catch (IOException e) { 439 LoginException le = new LoginException 440 ("Problem retrieving keystore alias"); 441 le.initCause(e); 442 throw le; 443 } catch (UnsupportedCallbackException e) { 444 throw new LoginException ( 445 "Error: " + e.getCallback().toString() + 446 " is not available to retrieve authentication " + 447 " information from the user"); 448 } 449 450 int confirmationResult = confirmationCallback.getSelectedIndex(); 451 452 if (confirmationResult == ConfirmationCallback.CANCEL) { 453 throw new LoginException ("Login cancelled"); 454 } 455 456 saveAlias(aliasCallback); 457 458 } else if (keyPassCallback == null) { 459 460 462 try { 463 callbackHandler.handle( 464 new Callback [] { 465 bannerCallback, aliasCallback, 466 storePassCallback, confirmationCallback 467 }); 468 } catch (IOException e) { 469 LoginException le = new LoginException 470 ("Problem retrieving keystore alias and password"); 471 le.initCause(e); 472 throw le; 473 } catch (UnsupportedCallbackException e) { 474 throw new LoginException ( 475 "Error: " + e.getCallback().toString() + 476 " is not available to retrieve authentication " + 477 " information from the user"); 478 } 479 480 int confirmationResult = confirmationCallback.getSelectedIndex(); 481 482 if (confirmationResult == ConfirmationCallback.CANCEL) { 483 throw new LoginException ("Login cancelled"); 484 } 485 486 saveAlias(aliasCallback); 487 saveStorePass(storePassCallback); 488 489 } else { 490 491 493 try { 494 callbackHandler.handle( 495 new Callback [] { 496 bannerCallback, aliasCallback, 497 storePassCallback, keyPassCallback, 498 confirmationCallback 499 }); 500 } catch (IOException e) { 501 LoginException le = new LoginException 502 ("Problem retrieving keystore alias and passwords"); 503 le.initCause(e); 504 throw le; 505 } catch (UnsupportedCallbackException e) { 506 throw new LoginException ( 507 "Error: " + e.getCallback().toString() + 508 " is not available to retrieve authentication " + 509 " information from the user"); 510 } 511 512 int confirmationResult = confirmationCallback.getSelectedIndex(); 513 514 if (confirmationResult == ConfirmationCallback.CANCEL) { 515 throw new LoginException ("Login cancelled"); 516 } 517 518 saveAlias(aliasCallback); 519 saveStorePass(storePassCallback); 520 saveKeyPass(keyPassCallback); 521 } 522 } 523 524 private void saveAlias(NameCallback cb) { 525 keyStoreAlias = cb.getName(); 526 } 527 528 private void saveStorePass(PasswordCallback c) { 529 keyStorePassword = c.getPassword(); 530 if (keyStorePassword == null) { 531 532 keyStorePassword = new char[0]; 533 } 534 c.clearPassword(); 535 } 536 537 private void saveKeyPass(PasswordCallback c) { 538 privateKeyPassword = c.getPassword(); 539 if (privateKeyPassword == null || privateKeyPassword.length == 0) { 540 544 privateKeyPassword = keyStorePassword; 545 } 546 c.clearPassword(); 547 } 548 549 550 private void getKeyStoreInfo() throws LoginException { 551 552 553 try { 554 if (keyStoreProvider == null) { 555 keyStore = KeyStore.getInstance(keyStoreType); 556 } else { 557 keyStore = 558 KeyStore.getInstance(keyStoreType, keyStoreProvider); 559 } 560 } catch (KeyStoreException e) { 561 LoginException le = new LoginException 562 ("The specified keystore type was not available"); 563 le.initCause(e); 564 throw le; 565 } catch (NoSuchProviderException e) { 566 LoginException le = new LoginException 567 ("The specified keystore provider was not available"); 568 le.initCause(e); 569 throw le; 570 } 571 572 573 try { 574 if (nullStream) { 575 keyStore.load(null, keyStorePassword); 577 } else { 578 InputStream in = new URL (keyStoreURL).openStream(); 579 keyStore.load(in, keyStorePassword); 580 in.close(); 581 } 582 } catch (MalformedURLException e) { 583 LoginException le = new LoginException 584 ("Incorrect keyStoreURL option"); 585 le.initCause(e); 586 throw le; 587 } catch (GeneralSecurityException e) { 588 LoginException le = new LoginException 589 ("Error initializing keystore"); 590 le.initCause(e); 591 throw le; 592 } catch (IOException e) { 593 LoginException le = new LoginException 594 ("Error initializing keystore"); 595 le.initCause(e); 596 throw le; 597 } 598 599 600 try { 601 fromKeyStore = 602 keyStore.getCertificateChain(keyStoreAlias); 603 if (fromKeyStore == null 604 || fromKeyStore.length == 0 605 || !(fromKeyStore[0] instanceof X509Certificate )) 606 { 607 throw new FailedLoginException ( 608 "Unable to find X.509 certificate chain in keystore"); 609 } else { 610 LinkedList certList = new LinkedList (); 611 for (int i=0; i < fromKeyStore.length; i++) { 612 certList.add(fromKeyStore[i]); 613 } 614 CertificateFactory certF= 615 CertificateFactory.getInstance("X.509"); 616 certP = 617 certF.generateCertPath(certList); 618 } 619 } catch (KeyStoreException e) { 620 LoginException le = new LoginException ("Error using keystore"); 621 le.initCause(e); 622 throw le; 623 } catch (CertificateException ce) { 624 LoginException le = new LoginException 625 ("Error: X.509 Certificate type unavailable"); 626 le.initCause(ce); 627 throw le; 628 } 629 630 631 try { 632 X509Certificate certificate = (X509Certificate )fromKeyStore[0]; 633 principal = new javax.security.auth.x500.X500Principal 634 (certificate.getSubjectDN().getName()); 635 636 Key privateKey = keyStore.getKey(keyStoreAlias, privateKeyPassword); 638 if (privateKey == null 639 || !(privateKey instanceof PrivateKey )) 640 { 641 throw new FailedLoginException ( 642 "Unable to recover key from keystore"); 643 } 644 645 privateCredential = new X500PrivateCredential( 646 certificate, (PrivateKey ) privateKey, keyStoreAlias); 647 } catch (KeyStoreException e) { 648 LoginException le = new LoginException ("Error using keystore"); 649 le.initCause(e); 650 throw le; 651 } catch (NoSuchAlgorithmException e) { 652 LoginException le = new LoginException ("Error using keystore"); 653 le.initCause(e); 654 throw le; 655 } catch (UnrecoverableKeyException e) { 656 FailedLoginException fle = new FailedLoginException 657 ("Unable to recover key from keystore"); 658 fle.initCause(e); 659 throw fle; 660 } 661 if (debug) { 662 debugPrint("principal=" + principal + 663 "\n certificate=" 664 + privateCredential.getCertificate() + 665 "\n alias =" + privateCredential.getAlias()); 666 } 667 } 668 669 697 698 public boolean commit() throws LoginException { 699 switch (status) { 700 case UNINITIALIZED: 701 default: 702 throw new LoginException ("The login module is not initialized"); 703 case INITIALIZED: 704 logoutInternal(); 705 throw new LoginException ("Authentication failed"); 706 case AUTHENTICATED: 707 if (commitInternal()) { 708 return true; 709 } else { 710 logoutInternal(); 711 throw new LoginException ("Unable to retrieve certificates"); 712 } 713 case LOGGED_IN: 714 return true; 715 } 716 } 717 718 private boolean commitInternal() throws LoginException { 719 722 if (subject.isReadOnly()) { 723 throw new LoginException ("Subject is set readonly"); 724 } else { 725 subject.getPrincipals().add(principal); 726 subject.getPublicCredentials().add(certP); 727 subject.getPrivateCredentials().add(privateCredential); 728 status = LOGGED_IN; 729 return true; 730 } 731 } 732 733 755 756 public boolean abort() throws LoginException { 757 switch (status) { 758 case UNINITIALIZED: 759 default: 760 return false; 761 case INITIALIZED: 762 return false; 763 case AUTHENTICATED: 764 logoutInternal(); 765 return true; 766 case LOGGED_IN: 767 logoutInternal(); 768 return true; 769 } 770 } 771 788 789 public boolean logout() throws LoginException { 790 if (debug) 791 debugPrint("Entering logout " + status); 792 switch (status) { 793 case UNINITIALIZED: 794 throw new LoginException 795 ("The login module is not initialized"); 796 case INITIALIZED: 797 case AUTHENTICATED: 798 default: 799 return false; 803 case LOGGED_IN: 804 logoutInternal(); 805 return true; 806 } 807 } 808 809 private void logoutInternal() throws LoginException { 810 if (debug) { 811 debugPrint("Entering logoutInternal"); 812 } 813 814 LoginException logoutException = null; 817 Provider provider = keyStore.getProvider(); 818 if (provider instanceof AuthProvider ) { 819 AuthProvider ap = (AuthProvider )provider; 820 try { 821 ap.logout(); 822 if (debug) { 823 debugPrint("logged out of KeyStore AuthProvider"); 824 } 825 } catch (LoginException le) { 826 logoutException = le; 828 } 829 } 830 831 if (subject.isReadOnly()) { 832 principal = null; 835 certP = null; 836 status = INITIALIZED; 837 Iterator it = subject.getPrivateCredentials().iterator(); 839 while (it.hasNext()) { 840 Object obj = it.next(); 841 if (privateCredential.equals(obj)) { 842 privateCredential = null; 843 try { 844 ((Destroyable )obj).destroy(); 845 if (debug) 846 debugPrint("Destroyed private credential, " + 847 obj.getClass().getName()); 848 break; 849 } catch (DestroyFailedException dfe) { 850 LoginException le = new LoginException 851 ("Unable to destroy private credential, " 852 + obj.getClass().getName()); 853 le.initCause(dfe); 854 throw le; 855 } 856 } 857 } 858 859 throw new LoginException 863 ("Unable to remove Principal (" 864 + "X500Principal " 865 + ") and public credential (certificatepath) " 866 + "from read-only Subject"); 867 } 868 if (principal != null) { 869 subject.getPrincipals().remove(principal); 870 principal = null; 871 } 872 if (certP != null) { 873 subject.getPublicCredentials().remove(certP); 874 certP = null; 875 } 876 if (privateCredential != null) { 877 subject.getPrivateCredentials().remove(privateCredential); 878 privateCredential = null; 879 } 880 881 if (logoutException != null) { 883 throw logoutException; 884 } 885 status = INITIALIZED; 886 } 887 888 private void debugPrint(String message) { 889 if (message == null) { 891 System.err.println(); 892 } else { 893 System.err.println("Debug KeyStoreLoginModule: " + message); 894 } 895 } 896 } 897 | Popular Tags |