001/* 002 * Copyright 2009-2023 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-2023 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2009-2023 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk; 037 038 039 040import java.io.File; 041import java.io.FileWriter; 042import java.io.PrintWriter; 043import java.security.MessageDigest; 044import java.security.PrivilegedExceptionAction; 045import java.security.cert.Certificate; 046import java.security.cert.X509Certificate; 047import java.util.ArrayList; 048import java.util.HashMap; 049import java.util.List; 050import java.util.Set; 051import java.util.concurrent.atomic.AtomicReference; 052import java.util.logging.Level; 053import javax.net.ssl.SSLSession; 054import javax.security.auth.Subject; 055import javax.security.auth.callback.Callback; 056import javax.security.auth.callback.CallbackHandler; 057import javax.security.auth.callback.NameCallback; 058import javax.security.auth.callback.PasswordCallback; 059import javax.security.auth.callback.UnsupportedCallbackException; 060import javax.security.auth.login.Configuration; 061import javax.security.auth.login.LoginContext; 062import javax.security.auth.x500.X500Principal; 063import javax.security.sasl.RealmCallback; 064import javax.security.sasl.Sasl; 065import javax.security.sasl.SaslClient; 066 067import com.unboundid.asn1.ASN1OctetString; 068import com.unboundid.util.ByteStringBuffer; 069import com.unboundid.util.CryptoHelper; 070import com.unboundid.util.Debug; 071import com.unboundid.util.DebugType; 072import com.unboundid.util.InternalUseOnly; 073import com.unboundid.util.NotMutable; 074import com.unboundid.util.NotNull; 075import com.unboundid.util.Nullable; 076import com.unboundid.util.StaticUtils; 077import com.unboundid.util.ThreadSafety; 078import com.unboundid.util.ThreadSafetyLevel; 079import com.unboundid.util.Validator; 080 081import static com.unboundid.ldap.sdk.LDAPMessages.*; 082 083 084 085/** 086 * This class provides a SASL GSSAPI bind request implementation as described in 087 * <A HREF="http://www.ietf.org/rfc/rfc4752.txt">RFC 4752</A>. It provides the 088 * ability to authenticate to a directory server using Kerberos V, which can 089 * serve as a kind of single sign-on mechanism that may be shared across 090 * client applications that support Kerberos. 091 * <BR><BR> 092 * This class uses the Java Authentication and Authorization Service (JAAS) 093 * behind the scenes to perform all Kerberos processing. This framework 094 * requires a configuration file to indicate the underlying mechanism to be 095 * used. It is possible for clients to explicitly specify the path to the 096 * configuration file that should be used, but if none is given then a default 097 * file will be created and used. This default file should be sufficient for 098 * Sun-provided JVMs, but a custom file may be required for JVMs provided by 099 * other vendors. 100 * <BR><BR> 101 * Elements included in a GSSAPI bind request include: 102 * <UL> 103 * <LI>Authentication ID -- A string which identifies the user that is 104 * attempting to authenticate. It should be the user's Kerberos 105 * principal.</LI> 106 * <LI>Authorization ID -- An optional string which specifies an alternate 107 * authorization identity that should be used for subsequent operations 108 * requested on the connection. Like the authentication ID, the 109 * authorization ID should be a Kerberos principal.</LI> 110 * <LI>KDC Address -- An optional string which specifies the IP address or 111 * resolvable name for the Kerberos key distribution center. If this is 112 * not provided, an attempt will be made to determine the appropriate 113 * value from the system configuration.</LI> 114 * <LI>Realm -- An optional string which specifies the realm into which the 115 * user should authenticate. If this is not provided, an attempt will be 116 * made to determine the appropriate value from the system 117 * configuration</LI> 118 * <LI>Password -- The clear-text password for the target user in the Kerberos 119 * realm.</LI> 120 * </UL> 121 * <H2>Example</H2> 122 * The following example demonstrates the process for performing a GSSAPI bind 123 * against a directory server with a username of "john.doe" and a password 124 * of "password": 125 * <PRE> 126 * GSSAPIBindRequestProperties gssapiProperties = 127 * new GSSAPIBindRequestProperties("john.doe@EXAMPLE.COM", "password"); 128 * gssapiProperties.setKDCAddress("kdc.example.com"); 129 * gssapiProperties.setRealm("EXAMPLE.COM"); 130 * 131 * GSSAPIBindRequest bindRequest = 132 * new GSSAPIBindRequest(gssapiProperties); 133 * BindResult bindResult; 134 * try 135 * { 136 * bindResult = connection.bind(bindRequest); 137 * // If we get here, then the bind was successful. 138 * } 139 * catch (LDAPException le) 140 * { 141 * // The bind failed for some reason. 142 * bindResult = new BindResult(le.toLDAPResult()); 143 * ResultCode resultCode = le.getResultCode(); 144 * String errorMessageFromServer = le.getDiagnosticMessage(); 145 * } 146 * </PRE> 147 */ 148@NotMutable() 149@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 150public final class GSSAPIBindRequest 151 extends SASLBindRequest 152 implements CallbackHandler, PrivilegedExceptionAction<Object> 153{ 154 /** 155 * The name for the GSSAPI SASL mechanism. 156 */ 157 @NotNull public static final String GSSAPI_MECHANISM_NAME = "GSSAPI"; 158 159 160 161 /** 162 * The name of the configuration property used to specify the address of the 163 * Kerberos key distribution center. 164 */ 165 @NotNull private static final String PROPERTY_KDC_ADDRESS = 166 "java.security.krb5.kdc"; 167 168 169 170 /** 171 * The name of the configuration property used to specify the Kerberos realm. 172 */ 173 @NotNull private static final String PROPERTY_REALM = 174 "java.security.krb5.realm"; 175 176 177 178 /** 179 * The name of the configuration property used to specify the path to the JAAS 180 * configuration file. 181 */ 182 @NotNull private static final String PROPERTY_CONFIG_FILE = 183 "java.security.auth.login.config"; 184 185 186 187 /** 188 * The name of the configuration property used to indicate whether credentials 189 * can come from somewhere other than the location specified in the JAAS 190 * configuration file. 191 */ 192 @NotNull private static final String PROPERTY_SUBJECT_CREDS_ONLY = 193 "javax.security.auth.useSubjectCredsOnly"; 194 195 196 197 /** 198 * The name of a SASL server property that may be used to provide a TLS 199 * channel binding token. 200 */ 201 @NotNull private static final String PROPERTY_CHANNEL_BINDING_DATA = 202 "jdk.internal.sasl.tlschannelbinding"; 203 204 205 206 /** 207 * The value for the java.security.auth.login.config property at the time that 208 * this class was loaded. If this is set, then it will be used in place of 209 * an automatically-generated config file. 210 */ 211 @Nullable private static final String DEFAULT_CONFIG_FILE = 212 StaticUtils.getSystemProperty(PROPERTY_CONFIG_FILE); 213 214 215 216 /** 217 * The default KDC address that will be used if none is explicitly configured. 218 */ 219 @Nullable private static final String DEFAULT_KDC_ADDRESS = 220 StaticUtils.getSystemProperty(PROPERTY_KDC_ADDRESS); 221 222 223 224 /** 225 * The default realm that will be used if none is explicitly configured. 226 */ 227 @Nullable private static final String DEFAULT_REALM = 228 StaticUtils.getSystemProperty(PROPERTY_REALM); 229 230 231 232 /** 233 * The serial version UID for this serializable class. 234 */ 235 private static final long serialVersionUID = 2511890818146955112L; 236 237 238 239 // The password for the GSSAPI bind request. 240 @Nullable private final ASN1OctetString password; 241 242 // A reference to the connection to use for bind processing. 243 @NotNull private final AtomicReference<LDAPConnection> conn; 244 245 // Indicates whether to enable JVM-level debugging for GSSAPI processing. 246 private final boolean enableGSSAPIDebugging; 247 248 // Indicates whether the client should act as the GSSAPI initiator or the 249 // acceptor. 250 @Nullable private final Boolean isInitiator; 251 252 // Indicates whether to attempt to refresh the configuration before the JAAS 253 // login method is called. 254 private final boolean refreshKrb5Config; 255 256 // Indicates whether to attempt to renew the client's existing ticket-granting 257 // ticket if authentication uses an existing Kerberos session. 258 private final boolean renewTGT; 259 260 // Indicates whether to require that the credentials be obtained from the 261 // ticket cache such that authentication will fail if the client does not have 262 // an existing Kerberos session. 263 private final boolean requireCachedCredentials; 264 265 // Indicates whether to allow the to obtain the credentials to be obtained 266 // from a keytab. 267 private final boolean useKeyTab; 268 269 // Indicates whether to allow the client to use credentials that are outside 270 // of the current subject. 271 private final boolean useSubjectCredentialsOnly; 272 273 // Indicates whether to enable the use pf a ticket cache. 274 private final boolean useTicketCache; 275 276 // The type of channel binding to use. 277 @NotNull private final GSSAPIChannelBindingType channelBindingType; 278 279 // The message ID from the last LDAP message sent from this request. 280 private int messageID; 281 282 // The SASL quality of protection value(s) allowed for the DIGEST-MD5 bind 283 // request. 284 @NotNull private final List<SASLQualityOfProtection> allowedQoP; 285 286 // A list that will be updated with messages about any unhandled callbacks 287 // encountered during processing. 288 @NotNull private final List<String> unhandledCallbackMessages; 289 290 // The names of any system properties that should not be altered by GSSAPI 291 // processing. 292 @NotNull private Set<String> suppressedSystemProperties; 293 294 // The authentication ID string for the GSSAPI bind request. 295 @Nullable private final String authenticationID; 296 297 // The authorization ID string for the GSSAPI bind request, if available. 298 @Nullable private final String authorizationID; 299 300 // The path to the JAAS configuration file to use for bind processing. 301 @Nullable private final String configFilePath; 302 303 // The name that will be used to identify this client in the JAAS framework. 304 @NotNull private final String jaasClientName; 305 306 // The KDC address for the GSSAPI bind request, if available. 307 @Nullable private final String kdcAddress; 308 309 // The path to the keytab file to use if useKeyTab is true. 310 @Nullable private final String keyTabPath; 311 312 // The realm for the GSSAPI bind request, if available. 313 @Nullable private final String realm; 314 315 // The server name that should be used when creating the Java SaslClient, if 316 // defined. 317 @Nullable private final String saslClientServerName; 318 319 // The protocol that should be used in the Kerberos service principal for 320 // the server system. 321 @NotNull private final String servicePrincipalProtocol; 322 323 // The path to the Kerberos ticket cache to use. 324 @Nullable private final String ticketCachePath; 325 326 327 328 /** 329 * Creates a new SASL GSSAPI bind request with the provided authentication ID 330 * and password. 331 * 332 * @param authenticationID The authentication ID for this bind request. It 333 * must not be {@code null}. 334 * @param password The password for this bind request. It must not 335 * be {@code null}. 336 * 337 * @throws LDAPException If a problem occurs while creating the JAAS 338 * configuration file to use during authentication 339 * processing. 340 */ 341 public GSSAPIBindRequest(@NotNull final String authenticationID, 342 @NotNull final String password) 343 throws LDAPException 344 { 345 this(new GSSAPIBindRequestProperties(authenticationID, password)); 346 } 347 348 349 350 /** 351 * Creates a new SASL GSSAPI bind request with the provided authentication ID 352 * and password. 353 * 354 * @param authenticationID The authentication ID for this bind request. It 355 * must not be {@code null}. 356 * @param password The password for this bind request. It must not 357 * be {@code null}. 358 * 359 * @throws LDAPException If a problem occurs while creating the JAAS 360 * configuration file to use during authentication 361 * processing. 362 */ 363 public GSSAPIBindRequest(@NotNull final String authenticationID, 364 @NotNull final byte[] password) 365 throws LDAPException 366 { 367 this(new GSSAPIBindRequestProperties(authenticationID, password)); 368 } 369 370 371 372 /** 373 * Creates a new SASL GSSAPI bind request with the provided authentication ID 374 * and password. 375 * 376 * @param authenticationID The authentication ID for this bind request. It 377 * must not be {@code null}. 378 * @param password The password for this bind request. It must not 379 * be {@code null}. 380 * @param controls The set of controls to include in the request. 381 * 382 * @throws LDAPException If a problem occurs while creating the JAAS 383 * configuration file to use during authentication 384 * processing. 385 */ 386 public GSSAPIBindRequest(@NotNull final String authenticationID, 387 @NotNull final String password, 388 @Nullable final Control[] controls) 389 throws LDAPException 390 { 391 this(new GSSAPIBindRequestProperties(authenticationID, password), controls); 392 } 393 394 395 396 /** 397 * Creates a new SASL GSSAPI bind request with the provided authentication ID 398 * and password. 399 * 400 * @param authenticationID The authentication ID for this bind request. It 401 * must not be {@code null}. 402 * @param password The password for this bind request. It must not 403 * be {@code null}. 404 * @param controls The set of controls to include in the request. 405 * 406 * @throws LDAPException If a problem occurs while creating the JAAS 407 * configuration file to use during authentication 408 * processing. 409 */ 410 public GSSAPIBindRequest(@NotNull final String authenticationID, 411 @NotNull final byte[] password, 412 @Nullable final Control[] controls) 413 throws LDAPException 414 { 415 this(new GSSAPIBindRequestProperties(authenticationID, password), controls); 416 } 417 418 419 420 /** 421 * Creates a new SASL GSSAPI bind request with the provided information. 422 * 423 * @param authenticationID The authentication ID for this bind request. It 424 * must not be {@code null}. 425 * @param authorizationID The authorization ID for this bind request. It 426 * may be {@code null} if no alternate authorization 427 * ID should be used. 428 * @param password The password for this bind request. It must not 429 * be {@code null}. 430 * @param realm The realm to use for the authentication. It may 431 * be {@code null} to attempt to use the default 432 * realm from the system configuration. 433 * @param kdcAddress The address of the Kerberos key distribution 434 * center. It may be {@code null} to attempt to use 435 * the default KDC from the system configuration. 436 * @param configFilePath The path to the JAAS configuration file to use 437 * for the authentication processing. It may be 438 * {@code null} to use the default JAAS 439 * configuration. 440 * 441 * @throws LDAPException If a problem occurs while creating the JAAS 442 * configuration file to use during authentication 443 * processing. 444 */ 445 public GSSAPIBindRequest(@NotNull final String authenticationID, 446 @Nullable final String authorizationID, 447 @NotNull final String password, 448 @Nullable final String realm, 449 @Nullable final String kdcAddress, 450 @Nullable final String configFilePath) 451 throws LDAPException 452 { 453 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 454 new ASN1OctetString(password), realm, kdcAddress, configFilePath)); 455 } 456 457 458 459 /** 460 * Creates a new SASL GSSAPI bind request with the provided information. 461 * 462 * @param authenticationID The authentication ID for this bind request. It 463 * must not be {@code null}. 464 * @param authorizationID The authorization ID for this bind request. It 465 * may be {@code null} if no alternate authorization 466 * ID should be used. 467 * @param password The password for this bind request. It must not 468 * be {@code null}. 469 * @param realm The realm to use for the authentication. It may 470 * be {@code null} to attempt to use the default 471 * realm from the system configuration. 472 * @param kdcAddress The address of the Kerberos key distribution 473 * center. It may be {@code null} to attempt to use 474 * the default KDC from the system configuration. 475 * @param configFilePath The path to the JAAS configuration file to use 476 * for the authentication processing. It may be 477 * {@code null} to use the default JAAS 478 * configuration. 479 * 480 * @throws LDAPException If a problem occurs while creating the JAAS 481 * configuration file to use during authentication 482 * processing. 483 */ 484 public GSSAPIBindRequest(@NotNull final String authenticationID, 485 @Nullable final String authorizationID, 486 @NotNull final byte[] password, 487 @Nullable final String realm, 488 @Nullable final String kdcAddress, 489 @Nullable final String configFilePath) 490 throws LDAPException 491 { 492 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 493 new ASN1OctetString(password), realm, kdcAddress, configFilePath)); 494 } 495 496 497 498 /** 499 * Creates a new SASL GSSAPI bind request with the provided information. 500 * 501 * @param authenticationID The authentication ID for this bind request. It 502 * must not be {@code null}. 503 * @param authorizationID The authorization ID for this bind request. It 504 * may be {@code null} if no alternate authorization 505 * ID should be used. 506 * @param password The password for this bind request. It must not 507 * be {@code null}. 508 * @param realm The realm to use for the authentication. It may 509 * be {@code null} to attempt to use the default 510 * realm from the system configuration. 511 * @param kdcAddress The address of the Kerberos key distribution 512 * center. It may be {@code null} to attempt to use 513 * the default KDC from the system configuration. 514 * @param configFilePath The path to the JAAS configuration file to use 515 * for the authentication processing. It may be 516 * {@code null} to use the default JAAS 517 * configuration. 518 * @param controls The set of controls to include in the request. 519 * 520 * @throws LDAPException If a problem occurs while creating the JAAS 521 * configuration file to use during authentication 522 * processing. 523 */ 524 public GSSAPIBindRequest(@NotNull final String authenticationID, 525 @Nullable final String authorizationID, 526 @NotNull final String password, 527 @Nullable final String realm, 528 @Nullable final String kdcAddress, 529 @Nullable final String configFilePath, 530 @Nullable final Control[] controls) 531 throws LDAPException 532 { 533 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 534 new ASN1OctetString(password), realm, kdcAddress, configFilePath), 535 controls); 536 } 537 538 539 540 /** 541 * Creates a new SASL GSSAPI bind request with the provided information. 542 * 543 * @param authenticationID The authentication ID for this bind request. It 544 * must not be {@code null}. 545 * @param authorizationID The authorization ID for this bind request. It 546 * may be {@code null} if no alternate authorization 547 * ID should be used. 548 * @param password The password for this bind request. It must not 549 * be {@code null}. 550 * @param realm The realm to use for the authentication. It may 551 * be {@code null} to attempt to use the default 552 * realm from the system configuration. 553 * @param kdcAddress The address of the Kerberos key distribution 554 * center. It may be {@code null} to attempt to use 555 * the default KDC from the system configuration. 556 * @param configFilePath The path to the JAAS configuration file to use 557 * for the authentication processing. It may be 558 * {@code null} to use the default JAAS 559 * configuration. 560 * @param controls The set of controls to include in the request. 561 * 562 * @throws LDAPException If a problem occurs while creating the JAAS 563 * configuration file to use during authentication 564 * processing. 565 */ 566 public GSSAPIBindRequest(@NotNull final String authenticationID, 567 @Nullable final String authorizationID, 568 @NotNull final byte[] password, 569 @Nullable final String realm, 570 @Nullable final String kdcAddress, 571 @Nullable final String configFilePath, 572 @Nullable final Control[] controls) 573 throws LDAPException 574 { 575 this(new GSSAPIBindRequestProperties(authenticationID, authorizationID, 576 new ASN1OctetString(password), realm, kdcAddress, configFilePath), 577 controls); 578 } 579 580 581 582 /** 583 * Creates a new SASL GSSAPI bind request with the provided set of properties. 584 * 585 * @param gssapiProperties The set of properties that should be used for 586 * the GSSAPI bind request. It must not be 587 * {@code null}. 588 * @param controls The set of controls to include in the request. 589 * 590 * @throws LDAPException If a problem occurs while creating the JAAS 591 * configuration file to use during authentication 592 * processing. 593 */ 594 public GSSAPIBindRequest( 595 @NotNull final GSSAPIBindRequestProperties gssapiProperties, 596 @Nullable final Control... controls) 597 throws LDAPException 598 { 599 super(controls); 600 601 Validator.ensureNotNull(gssapiProperties); 602 603 authenticationID = gssapiProperties.getAuthenticationID(); 604 password = gssapiProperties.getPassword(); 605 realm = gssapiProperties.getRealm(); 606 allowedQoP = gssapiProperties.getAllowedQoP(); 607 kdcAddress = gssapiProperties.getKDCAddress(); 608 jaasClientName = gssapiProperties.getJAASClientName(); 609 saslClientServerName = gssapiProperties.getSASLClientServerName(); 610 servicePrincipalProtocol = gssapiProperties.getServicePrincipalProtocol(); 611 enableGSSAPIDebugging = gssapiProperties.enableGSSAPIDebugging(); 612 useKeyTab = gssapiProperties.useKeyTab(); 613 useSubjectCredentialsOnly = gssapiProperties.useSubjectCredentialsOnly(); 614 useTicketCache = gssapiProperties.useTicketCache(); 615 requireCachedCredentials = gssapiProperties.requireCachedCredentials(); 616 refreshKrb5Config = gssapiProperties.refreshKrb5Config(); 617 renewTGT = gssapiProperties.renewTGT(); 618 keyTabPath = gssapiProperties.getKeyTabPath(); 619 ticketCachePath = gssapiProperties.getTicketCachePath(); 620 isInitiator = gssapiProperties.getIsInitiator(); 621 channelBindingType = gssapiProperties.getChannelBindingType(); 622 suppressedSystemProperties = 623 gssapiProperties.getSuppressedSystemProperties(); 624 625 unhandledCallbackMessages = new ArrayList<>(5); 626 627 conn = new AtomicReference<>(); 628 messageID = -1; 629 630 final String authzID = gssapiProperties.getAuthorizationID(); 631 if (authzID == null) 632 { 633 authorizationID = null; 634 } 635 else 636 { 637 authorizationID = authzID; 638 } 639 640 final String cfgPath = gssapiProperties.getConfigFilePath(); 641 if (cfgPath == null) 642 { 643 if (DEFAULT_CONFIG_FILE == null) 644 { 645 configFilePath = getConfigFilePath(gssapiProperties); 646 } 647 else 648 { 649 configFilePath = DEFAULT_CONFIG_FILE; 650 } 651 } 652 else 653 { 654 configFilePath = cfgPath; 655 } 656 } 657 658 659 660 /** 661 * {@inheritDoc} 662 */ 663 @Override() 664 @NotNull() 665 public String getSASLMechanismName() 666 { 667 return GSSAPI_MECHANISM_NAME; 668 } 669 670 671 672 /** 673 * Retrieves the authentication ID for the GSSAPI bind request, if defined. 674 * 675 * @return The authentication ID for the GSSAPI bind request, or {@code null} 676 * if an existing Kerberos session should be used. 677 */ 678 @Nullable() 679 public String getAuthenticationID() 680 { 681 return authenticationID; 682 } 683 684 685 686 /** 687 * Retrieves the authorization ID for this bind request, if any. 688 * 689 * @return The authorization ID for this bind request, or {@code null} if 690 * there should not be a separate authorization identity. 691 */ 692 @Nullable() 693 public String getAuthorizationID() 694 { 695 return authorizationID; 696 } 697 698 699 700 /** 701 * Retrieves the string representation of the password for this bind request, 702 * if defined. 703 * 704 * @return The string representation of the password for this bind request, 705 * or {@code null} if an existing Kerberos session should be used. 706 */ 707 @Nullable() 708 public String getPasswordString() 709 { 710 if (password == null) 711 { 712 return null; 713 } 714 else 715 { 716 return password.stringValue(); 717 } 718 } 719 720 721 722 /** 723 * Retrieves the bytes that comprise the the password for this bind request, 724 * if defined. 725 * 726 * @return The bytes that comprise the password for this bind request, or 727 * {@code null} if an existing Kerberos session should be used. 728 */ 729 @Nullable() 730 public byte[] getPasswordBytes() 731 { 732 if (password == null) 733 { 734 return null; 735 } 736 else 737 { 738 return password.getValue(); 739 } 740 } 741 742 743 744 /** 745 * Retrieves the realm for this bind request, if any. 746 * 747 * @return The realm for this bind request, or {@code null} if none was 748 * defined and the client should attempt to determine the realm from 749 * the system configuration. 750 */ 751 @Nullable() 752 public String getRealm() 753 { 754 return realm; 755 } 756 757 758 759 /** 760 * Retrieves the list of allowed qualities of protection that may be used for 761 * communication that occurs on the connection after the authentication has 762 * completed, in order from most preferred to least preferred. 763 * 764 * @return The list of allowed qualities of protection that may be used for 765 * communication that occurs on the connection after the 766 * authentication has completed, in order from most preferred to 767 * least preferred. 768 */ 769 @NotNull() 770 public List<SASLQualityOfProtection> getAllowedQoP() 771 { 772 return allowedQoP; 773 } 774 775 776 777 /** 778 * Retrieves the address of the Kerberos key distribution center. 779 * 780 * @return The address of the Kerberos key distribution center, or 781 * {@code null} if none was defined and the client should attempt to 782 * determine the KDC address from the system configuration. 783 */ 784 @Nullable() 785 public String getKDCAddress() 786 { 787 return kdcAddress; 788 } 789 790 791 792 /** 793 * Retrieves the path to the JAAS configuration file that will be used during 794 * authentication processing. 795 * 796 * @return The path to the JAAS configuration file that will be used during 797 * authentication processing. 798 */ 799 @Nullable() 800 public String getConfigFilePath() 801 { 802 return configFilePath; 803 } 804 805 806 807 /** 808 * Retrieves the protocol specified in the service principal that the 809 * directory server uses for its communication with the KDC. 810 * 811 * @return The protocol specified in the service principal that the directory 812 * server uses for its communication with the KDC. 813 */ 814 @NotNull() 815 public String getServicePrincipalProtocol() 816 { 817 return servicePrincipalProtocol; 818 } 819 820 821 822 /** 823 * Indicates whether to refresh the configuration before the JAAS 824 * {@code login} method is called. 825 * 826 * @return {@code true} if the GSSAPI implementation should refresh the 827 * configuration before the JAAS {@code login} method is called, or 828 * {@code false} if not. 829 */ 830 public boolean refreshKrb5Config() 831 { 832 return refreshKrb5Config; 833 } 834 835 836 837 /** 838 * Indicates whether to use a keytab to obtain the user credentials. 839 * 840 * @return {@code true} if the GSSAPI login attempt should use a keytab to 841 * obtain the user credentials, or {@code false} if not. 842 */ 843 public boolean useKeyTab() 844 { 845 return useKeyTab; 846 } 847 848 849 850 /** 851 * Retrieves the path to the keytab file from which to obtain the user 852 * credentials. This will only be used if {@link #useKeyTab} returns 853 * {@code true}. 854 * 855 * @return The path to the keytab file from which to obtain the user 856 * credentials, or {@code null} if the default keytab location should 857 * be used. 858 */ 859 @Nullable() 860 public String getKeyTabPath() 861 { 862 return keyTabPath; 863 } 864 865 866 867 /** 868 * Indicates whether to enable the use of a ticket cache to to avoid the need 869 * to supply credentials if the client already has an existing Kerberos 870 * session. 871 * 872 * @return {@code true} if a ticket cache may be used to take advantage of an 873 * existing Kerberos session, or {@code false} if Kerberos 874 * credentials should always be provided. 875 */ 876 public boolean useTicketCache() 877 { 878 return useTicketCache; 879 } 880 881 882 883 /** 884 * Indicates whether GSSAPI authentication should only occur using an existing 885 * Kerberos session. 886 * 887 * @return {@code true} if GSSAPI authentication should only use an existing 888 * Kerberos session and should fail if the client does not have an 889 * existing session, or {@code false} if the client will be allowed 890 * to create a new session if one does not already exist. 891 */ 892 public boolean requireCachedCredentials() 893 { 894 return requireCachedCredentials; 895 } 896 897 898 899 /** 900 * Retrieves the path to the Kerberos ticket cache file that should be used 901 * during authentication, if defined. 902 * 903 * @return The path to the Kerberos ticket cache file that should be used 904 * during authentication, or {@code null} if the default ticket cache 905 * file should be used. 906 */ 907 @Nullable() 908 public String getTicketCachePath() 909 { 910 return ticketCachePath; 911 } 912 913 914 915 /** 916 * Indicates whether to attempt to renew the client's ticket-granting ticket 917 * (TGT) if an existing Kerberos session is used to authenticate. 918 * 919 * @return {@code true} if the client should attempt to renew its 920 * ticket-granting ticket if the authentication is processed using an 921 * existing Kerberos session, or {@code false} if not. 922 */ 923 public boolean renewTGT() 924 { 925 return renewTGT; 926 } 927 928 929 930 /** 931 * Indicates whether to allow the client to use credentials that are outside 932 * of the current subject, obtained via some system-specific mechanism. 933 * 934 * @return {@code true} if the client will only be allowed to use credentials 935 * that are within the current subject, or {@code false} if the 936 * client will be allowed to use credentials outside the current 937 * subject. 938 */ 939 public boolean useSubjectCredentialsOnly() 940 { 941 return useSubjectCredentialsOnly; 942 } 943 944 945 946 /** 947 * Indicates whether the client should be configured so that it explicitly 948 * indicates whether it is the initiator or the acceptor. 949 * 950 * @return {@code Boolean.TRUE} if the client should explicitly indicate that 951 * it is the GSSAPI initiator, {@code Boolean.FALSE} if the client 952 * should explicitly indicate that it is the GSSAPI acceptor, or 953 * {@code null} if the client should not explicitly indicate either 954 * state (which is the default behavior unless the 955 * {@link GSSAPIBindRequestProperties#setIsInitiator} method has 956 * been used to explicitly specify a value). 957 */ 958 @Nullable() 959 public Boolean getIsInitiator() 960 { 961 return isInitiator; 962 } 963 964 965 966 /** 967 * Retrieves a set of system properties that will not be altered by GSSAPI 968 * processing. 969 * 970 * @return A set of system properties that will not be altered by GSSAPI 971 * processing. 972 */ 973 @NotNull() 974 public Set<String> getSuppressedSystemProperties() 975 { 976 return suppressedSystemProperties; 977 } 978 979 980 981 /** 982 * Retrieves the type of channel binding that should be used for this GSSAPI 983 * bind request. 984 * 985 * @return The type of channel binding that should be used for this GSSAPI 986 * bind request, or {@link GSSAPIChannelBindingType#NONE} if no 987 * channel binding should be used. 988 */ 989 @NotNull() 990 public GSSAPIChannelBindingType getChannelBindingType() 991 { 992 return channelBindingType; 993 } 994 995 996 997 /** 998 * Indicates whether JVM-level debugging should be enabled for GSSAPI bind 999 * processing. 1000 * 1001 * @return {@code true} if JVM-level debugging should be enabled for GSSAPI 1002 * bind processing, or {@code false} if not. 1003 */ 1004 public boolean enableGSSAPIDebugging() 1005 { 1006 return enableGSSAPIDebugging; 1007 } 1008 1009 1010 1011 /** 1012 * Retrieves the path to the default JAAS configuration file that will be used 1013 * if no file was explicitly provided. A new file may be created if 1014 * necessary. 1015 * 1016 * @param properties The GSSAPI properties that should be used for 1017 * authentication. 1018 * 1019 * @return The path to the default JAAS configuration file that will be used 1020 * if no file was explicitly provided. 1021 * 1022 * @throws LDAPException If an error occurs while attempting to create the 1023 * configuration file. 1024 */ 1025 @NotNull() 1026 private static String getConfigFilePath( 1027 @NotNull final GSSAPIBindRequestProperties properties) 1028 throws LDAPException 1029 { 1030 try 1031 { 1032 final File f = 1033 File.createTempFile("GSSAPIBindRequest-JAAS-Config-", ".conf"); 1034 f.deleteOnExit(); 1035 final PrintWriter w = new PrintWriter(new FileWriter(f)); 1036 1037 try 1038 { 1039 // The JAAS configuration file may vary based on the JVM that we're 1040 // using. For Sun-based JVMs, the module will be 1041 // "com.sun.security.auth.module.Krb5LoginModule". 1042 try 1043 { 1044 final Class<?> sunModuleClass = 1045 Class.forName("com.sun.security.auth.module.Krb5LoginModule"); 1046 if (sunModuleClass != null) 1047 { 1048 writeSunJAASConfig(w, properties); 1049 return f.getAbsolutePath(); 1050 } 1051 } 1052 catch (final ClassNotFoundException cnfe) 1053 { 1054 // This is fine. 1055 Debug.debugException(cnfe); 1056 } 1057 1058 1059 // For the IBM JVMs, the module will be 1060 // "com.ibm.security.auth.module.Krb5LoginModule". 1061 try 1062 { 1063 final Class<?> ibmModuleClass = 1064 Class.forName("com.ibm.security.auth.module.Krb5LoginModule"); 1065 if (ibmModuleClass != null) 1066 { 1067 writeIBMJAASConfig(w, properties); 1068 return f.getAbsolutePath(); 1069 } 1070 } 1071 catch (final ClassNotFoundException cnfe) 1072 { 1073 // This is fine. 1074 Debug.debugException(cnfe); 1075 } 1076 1077 1078 // If we've gotten here, then we can't generate an appropriate 1079 // configuration. 1080 throw new LDAPException(ResultCode.LOCAL_ERROR, 1081 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get( 1082 ERR_GSSAPI_NO_SUPPORTED_JAAS_MODULE.get())); 1083 } 1084 finally 1085 { 1086 w.close(); 1087 } 1088 } 1089 catch (final LDAPException le) 1090 { 1091 Debug.debugException(le); 1092 throw le; 1093 } 1094 catch (final Exception e) 1095 { 1096 Debug.debugException(e); 1097 1098 throw new LDAPException(ResultCode.LOCAL_ERROR, 1099 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get( 1100 StaticUtils.getExceptionMessage(e)), 1101 e); 1102 } 1103 } 1104 1105 1106 1107 /** 1108 * Writes a JAAS configuration file in a form appropriate for Sun VMs. 1109 * 1110 * @param w The writer to use to create the config file. 1111 * @param p The properties to use for GSSAPI authentication. 1112 */ 1113 private static void writeSunJAASConfig(@NotNull final PrintWriter w, 1114 @NotNull final GSSAPIBindRequestProperties p) 1115 { 1116 w.println(p.getJAASClientName() + " {"); 1117 w.println(" com.sun.security.auth.module.Krb5LoginModule required"); 1118 w.println(" client=true"); 1119 1120 if (p.getIsInitiator() != null) 1121 { 1122 w.println(" isInitiator=" + p.getIsInitiator()); 1123 } 1124 1125 if (p.refreshKrb5Config()) 1126 { 1127 w.println(" refreshKrb5Config=true"); 1128 } 1129 1130 if (p.useKeyTab()) 1131 { 1132 w.println(" useKeyTab=true"); 1133 if (p.getKeyTabPath() != null) 1134 { 1135 w.println(" keyTab=\"" + p.getKeyTabPath() + '"'); 1136 } 1137 } 1138 1139 if (p.useTicketCache()) 1140 { 1141 w.println(" useTicketCache=true"); 1142 w.println(" renewTGT=" + p.renewTGT()); 1143 w.println(" doNotPrompt=" + p.requireCachedCredentials()); 1144 1145 final String ticketCachePath = p.getTicketCachePath(); 1146 if (ticketCachePath != null) 1147 { 1148 w.println(" ticketCache=\"" + ticketCachePath + '"'); 1149 } 1150 } 1151 else 1152 { 1153 w.println(" useTicketCache=false"); 1154 } 1155 1156 if (p.enableGSSAPIDebugging()) 1157 { 1158 w.println(" debug=true"); 1159 } 1160 1161 w.println(" ;"); 1162 w.println("};"); 1163 } 1164 1165 1166 1167 /** 1168 * Writes a JAAS configuration file in a form appropriate for IBM VMs. 1169 * 1170 * @param w The writer to use to create the config file. 1171 * @param p The properties to use for GSSAPI authentication. 1172 */ 1173 private static void writeIBMJAASConfig(@NotNull final PrintWriter w, 1174 @NotNull final GSSAPIBindRequestProperties p) 1175 { 1176 // NOTE: It does not appear that the IBM GSSAPI implementation has any 1177 // analog for the renewTGT property, so it will be ignored. 1178 w.println(p.getJAASClientName() + " {"); 1179 w.println(" com.ibm.security.auth.module.Krb5LoginModule required"); 1180 if ((p.getIsInitiator() == null) || p.getIsInitiator().booleanValue()) 1181 { 1182 w.println(" credsType=initiator"); 1183 } 1184 else 1185 { 1186 w.println(" credsType=acceptor"); 1187 } 1188 1189 if (p.refreshKrb5Config()) 1190 { 1191 w.println(" refreshKrb5Config=true"); 1192 } 1193 1194 if (p.useKeyTab()) 1195 { 1196 w.println(" useKeyTab=true"); 1197 if (p.getKeyTabPath() != null) 1198 { 1199 w.println(" keyTab=\"" + p.getKeyTabPath() + '"'); 1200 } 1201 } 1202 1203 if (p.useTicketCache()) 1204 { 1205 final String ticketCachePath = p.getTicketCachePath(); 1206 if (ticketCachePath == null) 1207 { 1208 if (p.requireCachedCredentials()) 1209 { 1210 w.println(" useDefaultCcache=true"); 1211 } 1212 } 1213 else 1214 { 1215 final File f = new File(ticketCachePath); 1216 final String path = f.getAbsolutePath().replace('\\', '/'); 1217 w.println(" useCcache=\"file://" + path + '"'); 1218 } 1219 } 1220 else 1221 { 1222 w.println(" useDefaultCcache=false"); 1223 } 1224 1225 if (p.enableGSSAPIDebugging()) 1226 { 1227 w.println(" debug=true"); 1228 } 1229 1230 w.println(" ;"); 1231 w.println("};"); 1232 } 1233 1234 1235 1236 /** 1237 * Sends this bind request to the target server over the provided connection 1238 * and returns the corresponding response. 1239 * 1240 * @param connection The connection to use to send this bind request to the 1241 * server and read the associated response. 1242 * @param depth The current referral depth for this request. It should 1243 * always be one for the initial request, and should only 1244 * be incremented when following referrals. 1245 * 1246 * @return The bind response read from the server. 1247 * 1248 * @throws LDAPException If a problem occurs while sending the request or 1249 * reading the response. 1250 */ 1251 @Override() 1252 @NotNull() 1253 protected BindResult process(@NotNull final LDAPConnection connection, 1254 final int depth) 1255 throws LDAPException 1256 { 1257 setReferralDepth(depth); 1258 1259 if (! conn.compareAndSet(null, connection)) 1260 { 1261 throw new LDAPException(ResultCode.LOCAL_ERROR, 1262 ERR_GSSAPI_MULTIPLE_CONCURRENT_REQUESTS.get()); 1263 } 1264 1265 setProperty(PROPERTY_CONFIG_FILE, configFilePath); 1266 setProperty(PROPERTY_SUBJECT_CREDS_ONLY, 1267 String.valueOf(useSubjectCredentialsOnly)); 1268 if (Debug.debugEnabled(DebugType.LDAP)) 1269 { 1270 Debug.debug(Level.CONFIG, DebugType.LDAP, 1271 "Using config file property " + PROPERTY_CONFIG_FILE + " = '" + 1272 configFilePath + "'."); 1273 Debug.debug(Level.CONFIG, DebugType.LDAP, 1274 "Using subject creds only property " + PROPERTY_SUBJECT_CREDS_ONLY + 1275 " = '" + useSubjectCredentialsOnly + "'."); 1276 } 1277 1278 if (kdcAddress == null) 1279 { 1280 if (DEFAULT_KDC_ADDRESS == null) 1281 { 1282 clearProperty(PROPERTY_KDC_ADDRESS); 1283 if (Debug.debugEnabled(DebugType.LDAP)) 1284 { 1285 Debug.debug(Level.CONFIG, DebugType.LDAP, 1286 "Clearing kdcAddress property '" + PROPERTY_KDC_ADDRESS + "'."); 1287 } 1288 } 1289 else 1290 { 1291 setProperty(PROPERTY_KDC_ADDRESS, DEFAULT_KDC_ADDRESS); 1292 if (Debug.debugEnabled(DebugType.LDAP)) 1293 { 1294 Debug.debug(Level.CONFIG, DebugType.LDAP, 1295 "Using default kdcAddress property " + PROPERTY_KDC_ADDRESS + 1296 " = '" + DEFAULT_KDC_ADDRESS + "'."); 1297 } 1298 } 1299 } 1300 else 1301 { 1302 setProperty(PROPERTY_KDC_ADDRESS, kdcAddress); 1303 if (Debug.debugEnabled(DebugType.LDAP)) 1304 { 1305 Debug.debug(Level.CONFIG, DebugType.LDAP, 1306 "Using kdcAddress property " + PROPERTY_KDC_ADDRESS + " = '" + 1307 kdcAddress + "'."); 1308 } 1309 } 1310 1311 if (realm == null) 1312 { 1313 if (DEFAULT_REALM == null) 1314 { 1315 clearProperty(PROPERTY_REALM); 1316 if (Debug.debugEnabled(DebugType.LDAP)) 1317 { 1318 Debug.debug(Level.CONFIG, DebugType.LDAP, 1319 "Clearing realm property '" + PROPERTY_REALM + "'."); 1320 } 1321 } 1322 else 1323 { 1324 setProperty(PROPERTY_REALM, DEFAULT_REALM); 1325 if (Debug.debugEnabled(DebugType.LDAP)) 1326 { 1327 Debug.debug(Level.CONFIG, DebugType.LDAP, 1328 "Using default realm property " + PROPERTY_REALM + " = '" + 1329 DEFAULT_REALM + "'."); 1330 } 1331 } 1332 } 1333 else 1334 { 1335 setProperty(PROPERTY_REALM, realm); 1336 if (Debug.debugEnabled(DebugType.LDAP)) 1337 { 1338 Debug.debug(Level.CONFIG, DebugType.LDAP, 1339 "Using realm property " + PROPERTY_REALM + " = '" + realm + "'."); 1340 } 1341 } 1342 1343 try 1344 { 1345 // Reload the configuration before creating the login context, which may 1346 // work around problems that could arise if certain configuration is 1347 // loaded and cached before the above system properties were set. 1348 Configuration.getConfiguration().refresh(); 1349 } 1350 catch (final Exception e) 1351 { 1352 Debug.debugException(e); 1353 } 1354 1355 try 1356 { 1357 final LoginContext context; 1358 try 1359 { 1360 context = new LoginContext(jaasClientName, this); 1361 context.login(); 1362 } 1363 catch (final Exception e) 1364 { 1365 Debug.debugException(e); 1366 1367 throw new LDAPException(ResultCode.LOCAL_ERROR, 1368 ERR_GSSAPI_CANNOT_INITIALIZE_JAAS_CONTEXT.get( 1369 StaticUtils.getExceptionMessage(e)), 1370 e); 1371 } 1372 1373 try 1374 { 1375 return (BindResult) Subject.doAs(context.getSubject(), this); 1376 } 1377 catch (final Exception e) 1378 { 1379 Debug.debugException(e); 1380 if (e instanceof LDAPException) 1381 { 1382 throw (LDAPException) e; 1383 } 1384 else 1385 { 1386 throw new LDAPException(ResultCode.LOCAL_ERROR, 1387 ERR_GSSAPI_AUTHENTICATION_FAILED.get( 1388 StaticUtils.getExceptionMessage(e)), 1389 e); 1390 } 1391 } 1392 } 1393 finally 1394 { 1395 conn.set(null); 1396 } 1397 } 1398 1399 1400 1401 /** 1402 * Perform the privileged portion of the authentication processing. 1403 * 1404 * @return {@code null}, since no return value is actually needed. 1405 * 1406 * @throws LDAPException If a problem occurs during processing. 1407 */ 1408 @InternalUseOnly() 1409 @Override() 1410 @NotNull() 1411 public Object run() 1412 throws LDAPException 1413 { 1414 unhandledCallbackMessages.clear(); 1415 1416 final LDAPConnection connection = conn.get(); 1417 1418 1419 final HashMap<String,Object> saslProperties = 1420 new HashMap<>(StaticUtils.computeMapCapacity(2)); 1421 saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP)); 1422 saslProperties.put(Sasl.SERVER_AUTH, "true"); 1423 1424 switch (channelBindingType) 1425 { 1426 case NONE: 1427 // No action is required. 1428 break; 1429 1430 case TLS_SERVER_END_POINT: 1431 saslProperties.put(PROPERTY_CHANNEL_BINDING_DATA, 1432 generateTLSServerEndPointChannelBindingData(connection)); 1433 break; 1434 1435 default: 1436 // This should never happen. 1437 throw new LDAPException(ResultCode.PARAM_ERROR, 1438 ERR_GSSAPI_UNSUPPORTED_CHANNEL_BINDING_TYPE.get( 1439 channelBindingType.getName())); 1440 } 1441 1442 final SaslClient saslClient; 1443 try 1444 { 1445 String serverName = saslClientServerName; 1446 if (serverName == null) 1447 { 1448 serverName = connection.getConnectedAddress(); 1449 } 1450 1451 final String[] mechanisms = { GSSAPI_MECHANISM_NAME }; 1452 saslClient = Sasl.createSaslClient(mechanisms, authorizationID, 1453 servicePrincipalProtocol, serverName, saslProperties, this); 1454 } 1455 catch (final Exception e) 1456 { 1457 Debug.debugException(e); 1458 throw new LDAPException(ResultCode.LOCAL_ERROR, 1459 ERR_GSSAPI_CANNOT_CREATE_SASL_CLIENT.get( 1460 StaticUtils.getExceptionMessage(e)), 1461 e); 1462 } 1463 1464 final SASLClientBindHandler bindHandler = new SASLClientBindHandler(this, 1465 connection, GSSAPI_MECHANISM_NAME, saslClient, getControls(), 1466 getResponseTimeoutMillis(connection), unhandledCallbackMessages); 1467 1468 try 1469 { 1470 return bindHandler.processSASLBind(); 1471 } 1472 finally 1473 { 1474 messageID = bindHandler.getMessageID(); 1475 } 1476 } 1477 1478 1479 1480 /** 1481 * Generates an appropriate value to use for TLS server endpoint channel 1482 * binding. See RFC 5929 section 4 for a description of this channel binding 1483 * type. 1484 * 1485 * @param connection The connection used to communicate with the server. It 1486 * must not be {@code null}. 1487 * 1488 * @return The bytes that comprise the TLS server endpoint channel binding 1489 * data. 1490 * 1491 * @throws LDAPException If a problem occurs while attempting to generate 1492 * the channel binding data. 1493 */ 1494 @NotNull() 1495 static byte[] generateTLSServerEndPointChannelBindingData( 1496 @NotNull final LDAPConnection connection) 1497 throws LDAPException 1498 { 1499 // Make sure that the connection has an associated SSL session. 1500 final String channelBindingType = 1501 GSSAPIChannelBindingType.TLS_SERVER_END_POINT.getName(); 1502 1503 final SSLSession sslSession = connection.getSSLSession(); 1504 if (sslSession == null) 1505 { 1506 throw new LDAPException(ResultCode.PARAM_ERROR, 1507 ERR_GSSAPI_TLS_SERVER_CB_NO_SSL_SESSION.get(channelBindingType)); 1508 } 1509 1510 1511 // Get the certificate presented by the server during TLS negotiation. 1512 final Certificate[] serverCertificateChain; 1513 try 1514 { 1515 serverCertificateChain = sslSession.getPeerCertificates(); 1516 } 1517 catch (final Exception e) 1518 { 1519 Debug.debugException(e); 1520 throw new LDAPException(ResultCode.LOCAL_ERROR, 1521 ERR_GSSAPI_TLS_SERVER_CB_CANNOT_GET_PEER_CERTS.get( 1522 channelBindingType, StaticUtils.getExceptionMessage(e)), 1523 e); 1524 } 1525 1526 if ((serverCertificateChain == null) || 1527 (serverCertificateChain.length == 0)) 1528 { 1529 throw new LDAPException(ResultCode.PARAM_ERROR, 1530 ERR_GSSAPI_TLS_SERVER_CB_NO_CERTS.get(channelBindingType)); 1531 } 1532 1533 if (! (serverCertificateChain[0] instanceof X509Certificate)) 1534 { 1535 throw new LDAPException(ResultCode.PARAM_ERROR, 1536 ERR_GSSAPI_TLS_SERVER_CB_SERVER_CERT_NOT_X509.get(channelBindingType, 1537 serverCertificateChain[0].getType())); 1538 } 1539 1540 final X509Certificate serverCertificate = 1541 (X509Certificate) serverCertificateChain[0]; 1542 1543 1544 // Determine the appropriate digest algorithm to use for generating the 1545 // channel binding data. 1546 final String signatureAlgorithmName = 1547 StaticUtils.toUpperCase(serverCertificate.getSigAlgName()); 1548 if (signatureAlgorithmName == null) 1549 { 1550 throw new LDAPException(ResultCode.PARAM_ERROR, 1551 ERR_GSSAPI_TLS_SERVER_CB_NO_SIG_ALG.get(channelBindingType, 1552 serverCertificate.getSubjectX500Principal().getName( 1553 X500Principal.RFC2253))); 1554 } 1555 1556 final int withPos = signatureAlgorithmName.indexOf("WITH"); 1557 if (withPos <= 0) 1558 { 1559 throw new LDAPException(ResultCode.PARAM_ERROR, 1560 ERR_GSSAPI_TLS_SERVER_CB_CANNOT_DETERMINE_SIG_DIGEST_ALG.get( 1561 channelBindingType, serverCertificate.getSigAlgName(), 1562 serverCertificate.getSubjectX500Principal().getName( 1563 X500Principal.RFC2253))); 1564 } 1565 1566 String digestAlgorithm = signatureAlgorithmName.substring(0, withPos); 1567 switch (digestAlgorithm) 1568 { 1569 case "MD5": 1570 case "SHA": 1571 case "SHA1": 1572 case "SHA-1": 1573 // All of these will be overridden to use the SHA-256 algorithm. 1574 digestAlgorithm = "SHA-256"; 1575 break; 1576 1577 case "SHA256": 1578 case "SHA-256": 1579 digestAlgorithm = "SHA-256"; 1580 break; 1581 1582 case "SHA384": 1583 case "SHA-384": 1584 digestAlgorithm = "SHA-384"; 1585 break; 1586 1587 case "SHA512": 1588 case "SHA-512": 1589 digestAlgorithm = "SHA-512"; 1590 break; 1591 1592 default: 1593 // Just use the digest algorithm extracted from the certificate. 1594 break; 1595 } 1596 1597 1598 // Generate a digest of the X.509 certificate using the selected digest 1599 // algorithm. 1600 final byte[] digestBytes; 1601 try 1602 { 1603 final MessageDigest messageDigest = 1604 CryptoHelper.getMessageDigest(digestAlgorithm); 1605 digestBytes = messageDigest.digest(serverCertificate.getEncoded()); 1606 } 1607 catch (final Exception e) 1608 { 1609 Debug.debugException(e); 1610 throw new LDAPException(ResultCode.LOCAL_ERROR, 1611 ERR_GSSAPI_TLS_SERVER_CB_CANNOT_DIGEST_CERT.get(channelBindingType, 1612 digestAlgorithm, 1613 serverCertificate.getSubjectX500Principal().getName( 1614 X500Principal.RFC2253), 1615 StaticUtils.getExceptionMessage(e)), 1616 e); 1617 } 1618 1619 1620 // The channel binding data will be a concatenation of the channel binding 1621 // type, a colon, and the digest bytes. 1622 final ByteStringBuffer buffer = new ByteStringBuffer(); 1623 buffer.append(channelBindingType); 1624 buffer.append(':'); 1625 buffer.append(digestBytes); 1626 return buffer.toByteArray(); 1627 } 1628 1629 1630 1631 /** 1632 * {@inheritDoc} 1633 */ 1634 @Override() 1635 @NotNull() 1636 public GSSAPIBindRequest getRebindRequest(@NotNull final String host, 1637 final int port) 1638 { 1639 try 1640 { 1641 final GSSAPIBindRequestProperties gssapiProperties = 1642 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1643 password, realm, kdcAddress, configFilePath); 1644 gssapiProperties.setAllowedQoP(allowedQoP); 1645 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1646 gssapiProperties.setUseTicketCache(useTicketCache); 1647 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1648 gssapiProperties.setRenewTGT(renewTGT); 1649 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1650 gssapiProperties.setTicketCachePath(ticketCachePath); 1651 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1652 gssapiProperties.setJAASClientName(jaasClientName); 1653 gssapiProperties.setSASLClientServerName(saslClientServerName); 1654 gssapiProperties.setSuppressedSystemProperties( 1655 suppressedSystemProperties); 1656 1657 return new GSSAPIBindRequest(gssapiProperties, getControls()); 1658 } 1659 catch (final Exception e) 1660 { 1661 // This should never happen. 1662 Debug.debugException(e); 1663 return null; 1664 } 1665 } 1666 1667 1668 1669 /** 1670 * Handles any necessary callbacks required for SASL authentication. 1671 * 1672 * @param callbacks The set of callbacks to be handled. 1673 * 1674 * @throws UnsupportedCallbackException If an unsupported type of callback 1675 * was received. 1676 */ 1677 @InternalUseOnly() 1678 @Override() 1679 public void handle(@NotNull final Callback[] callbacks) 1680 throws UnsupportedCallbackException 1681 { 1682 for (final Callback callback : callbacks) 1683 { 1684 if (callback instanceof NameCallback) 1685 { 1686 ((NameCallback) callback).setName(authenticationID); 1687 } 1688 else if (callback instanceof PasswordCallback) 1689 { 1690 if (password == null) 1691 { 1692 throw new UnsupportedCallbackException(callback, 1693 ERR_GSSAPI_NO_PASSWORD_AVAILABLE.get()); 1694 } 1695 else 1696 { 1697 ((PasswordCallback) callback).setPassword( 1698 password.stringValue().toCharArray()); 1699 } 1700 } 1701 else if (callback instanceof RealmCallback) 1702 { 1703 final RealmCallback rc = (RealmCallback) callback; 1704 if (realm == null) 1705 { 1706 unhandledCallbackMessages.add( 1707 ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED.get(rc.getPrompt())); 1708 } 1709 else 1710 { 1711 rc.setText(realm); 1712 } 1713 } 1714 else 1715 { 1716 // This is an unexpected callback. 1717 if (Debug.debugEnabled(DebugType.LDAP)) 1718 { 1719 Debug.debug(Level.WARNING, DebugType.LDAP, 1720 "Unexpected GSSAPI SASL callback of type " + 1721 callback.getClass().getName()); 1722 } 1723 1724 unhandledCallbackMessages.add(ERR_GSSAPI_UNEXPECTED_CALLBACK.get( 1725 callback.getClass().getName())); 1726 } 1727 } 1728 } 1729 1730 1731 1732 /** 1733 * {@inheritDoc} 1734 */ 1735 @Override() 1736 public int getLastMessageID() 1737 { 1738 return messageID; 1739 } 1740 1741 1742 1743 /** 1744 * {@inheritDoc} 1745 */ 1746 @Override() 1747 @NotNull() 1748 public GSSAPIBindRequest duplicate() 1749 { 1750 return duplicate(getControls()); 1751 } 1752 1753 1754 1755 /** 1756 * {@inheritDoc} 1757 */ 1758 @Override() 1759 @NotNull() 1760 public GSSAPIBindRequest duplicate(@Nullable final Control[] controls) 1761 { 1762 try 1763 { 1764 final GSSAPIBindRequestProperties gssapiProperties = 1765 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1766 password, realm, kdcAddress, configFilePath); 1767 gssapiProperties.setAllowedQoP(allowedQoP); 1768 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1769 gssapiProperties.setUseTicketCache(useTicketCache); 1770 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1771 gssapiProperties.setRenewTGT(renewTGT); 1772 gssapiProperties.setRefreshKrb5Config(refreshKrb5Config); 1773 gssapiProperties.setUseKeyTab(useKeyTab); 1774 gssapiProperties.setKeyTabPath(keyTabPath); 1775 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1776 gssapiProperties.setTicketCachePath(ticketCachePath); 1777 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1778 gssapiProperties.setJAASClientName(jaasClientName); 1779 gssapiProperties.setSASLClientServerName(saslClientServerName); 1780 gssapiProperties.setIsInitiator(isInitiator); 1781 gssapiProperties.setSuppressedSystemProperties( 1782 suppressedSystemProperties); 1783 gssapiProperties.setChannelBindingType(channelBindingType); 1784 1785 final GSSAPIBindRequest bindRequest = 1786 new GSSAPIBindRequest(gssapiProperties, controls); 1787 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1788 bindRequest.setIntermediateResponseListener( 1789 getIntermediateResponseListener()); 1790 bindRequest.setReferralDepth(getReferralDepth()); 1791 bindRequest.setReferralConnector(getReferralConnectorInternal()); 1792 return bindRequest; 1793 } 1794 catch (final Exception e) 1795 { 1796 // This should never happen. 1797 Debug.debugException(e); 1798 return null; 1799 } 1800 } 1801 1802 1803 1804 /** 1805 * Clears the specified system property, unless it is one that is configured 1806 * to be suppressed. 1807 * 1808 * @param name The name of the property to be suppressed. 1809 */ 1810 private void clearProperty(@NotNull final String name) 1811 { 1812 if (! suppressedSystemProperties.contains(name)) 1813 { 1814 StaticUtils.clearSystemProperty(name); 1815 } 1816 } 1817 1818 1819 1820 /** 1821 * Sets the specified system property, unless it is one that is configured to 1822 * be suppressed. 1823 * 1824 * @param name The name of the property to be suppressed. 1825 * @param value The value of the property to be suppressed. 1826 */ 1827 private void setProperty(@NotNull final String name, 1828 @NotNull final String value) 1829 { 1830 if (! suppressedSystemProperties.contains(name)) 1831 { 1832 StaticUtils.setSystemProperty(name, value); 1833 } 1834 } 1835 1836 1837 1838 /** 1839 * {@inheritDoc} 1840 */ 1841 @Override() 1842 public void toString(@NotNull final StringBuilder buffer) 1843 { 1844 buffer.append("GSSAPIBindRequest(authenticationID='"); 1845 buffer.append(authenticationID); 1846 buffer.append('\''); 1847 1848 if (authorizationID != null) 1849 { 1850 buffer.append(", authorizationID='"); 1851 buffer.append(authorizationID); 1852 buffer.append('\''); 1853 } 1854 1855 if (realm != null) 1856 { 1857 buffer.append(", realm='"); 1858 buffer.append(realm); 1859 buffer.append('\''); 1860 } 1861 1862 buffer.append(", qop='"); 1863 buffer.append(SASLQualityOfProtection.toString(allowedQoP)); 1864 buffer.append('\''); 1865 1866 if (kdcAddress != null) 1867 { 1868 buffer.append(", kdcAddress='"); 1869 buffer.append(kdcAddress); 1870 buffer.append('\''); 1871 } 1872 1873 if (isInitiator != null) 1874 { 1875 buffer.append(", isInitiator="); 1876 buffer.append(isInitiator); 1877 } 1878 1879 buffer.append(", jaasClientName='"); 1880 buffer.append(jaasClientName); 1881 buffer.append("', configFilePath='"); 1882 buffer.append(configFilePath); 1883 buffer.append("', servicePrincipalProtocol='"); 1884 buffer.append(servicePrincipalProtocol); 1885 buffer.append("', enableGSSAPIDebugging="); 1886 buffer.append(enableGSSAPIDebugging); 1887 1888 final Control[] controls = getControls(); 1889 if (controls.length > 0) 1890 { 1891 buffer.append(", controls={"); 1892 for (int i=0; i < controls.length; i++) 1893 { 1894 if (i > 0) 1895 { 1896 buffer.append(", "); 1897 } 1898 1899 buffer.append(controls[i]); 1900 } 1901 buffer.append('}'); 1902 } 1903 1904 buffer.append(')'); 1905 } 1906 1907 1908 1909 /** 1910 * {@inheritDoc} 1911 */ 1912 @Override() 1913 public void toCode(@NotNull final List<String> lineList, 1914 @NotNull final String requestID, 1915 final int indentSpaces, final boolean includeProcessing) 1916 { 1917 // Create and update the bind request properties object. 1918 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 1919 "GSSAPIBindRequestProperties", requestID + "RequestProperties", 1920 "new GSSAPIBindRequestProperties", 1921 ToCodeArgHelper.createString(authenticationID, "Authentication ID"), 1922 ToCodeArgHelper.createString("---redacted-password---", "Password")); 1923 1924 if (authorizationID != null) 1925 { 1926 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1927 requestID + "RequestProperties.setAuthorizationID", 1928 ToCodeArgHelper.createString(authorizationID, null)); 1929 } 1930 1931 if (realm != null) 1932 { 1933 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1934 requestID + "RequestProperties.setRealm", 1935 ToCodeArgHelper.createString(realm, null)); 1936 } 1937 1938 final ArrayList<String> qopValues = new ArrayList<>(3); 1939 for (final SASLQualityOfProtection qop : allowedQoP) 1940 { 1941 qopValues.add("SASLQualityOfProtection." + qop.name()); 1942 } 1943 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1944 requestID + "RequestProperties.setAllowedQoP", 1945 ToCodeArgHelper.createRaw(qopValues, null)); 1946 1947 if (kdcAddress != null) 1948 { 1949 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1950 requestID + "RequestProperties.setKDCAddress", 1951 ToCodeArgHelper.createString(kdcAddress, null)); 1952 } 1953 1954 if (jaasClientName != null) 1955 { 1956 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1957 requestID + "RequestProperties.setJAASClientName", 1958 ToCodeArgHelper.createString(jaasClientName, null)); 1959 } 1960 1961 if (configFilePath != null) 1962 { 1963 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1964 requestID + "RequestProperties.setConfigFilePath", 1965 ToCodeArgHelper.createString(configFilePath, null)); 1966 } 1967 1968 if (saslClientServerName != null) 1969 { 1970 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1971 requestID + "RequestProperties.setSASLClientServerName", 1972 ToCodeArgHelper.createString(saslClientServerName, null)); 1973 } 1974 1975 if (servicePrincipalProtocol != null) 1976 { 1977 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1978 requestID + "RequestProperties.setServicePrincipalProtocol", 1979 ToCodeArgHelper.createString(servicePrincipalProtocol, null)); 1980 } 1981 1982 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1983 requestID + "RequestProperties.setRefreshKrb5Config", 1984 ToCodeArgHelper.createBoolean(refreshKrb5Config, null)); 1985 1986 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1987 requestID + "RequestProperties.setUseKeyTab", 1988 ToCodeArgHelper.createBoolean(useKeyTab, null)); 1989 1990 if (keyTabPath != null) 1991 { 1992 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1993 requestID + "RequestProperties.setKeyTabPath", 1994 ToCodeArgHelper.createString(keyTabPath, null)); 1995 } 1996 1997 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1998 requestID + "RequestProperties.setUseSubjectCredentialsOnly", 1999 ToCodeArgHelper.createBoolean(useSubjectCredentialsOnly, null)); 2000 2001 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2002 requestID + "RequestProperties.setUseTicketCache", 2003 ToCodeArgHelper.createBoolean(useTicketCache, null)); 2004 2005 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2006 requestID + "RequestProperties.setRequireCachedCredentials", 2007 ToCodeArgHelper.createBoolean(requireCachedCredentials, null)); 2008 2009 if (ticketCachePath != null) 2010 { 2011 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2012 requestID + "RequestProperties.setTicketCachePath", 2013 ToCodeArgHelper.createString(ticketCachePath, null)); 2014 } 2015 2016 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2017 requestID + "RequestProperties.setRenewTGT", 2018 ToCodeArgHelper.createBoolean(renewTGT, null)); 2019 2020 if (isInitiator != null) 2021 { 2022 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2023 requestID + "RequestProperties.setIsInitiator", 2024 ToCodeArgHelper.createBoolean(isInitiator, null)); 2025 } 2026 2027 if ((suppressedSystemProperties != null) && 2028 (! suppressedSystemProperties.isEmpty())) 2029 { 2030 final ArrayList<ToCodeArgHelper> suppressedArgs = 2031 new ArrayList<>(suppressedSystemProperties.size()); 2032 for (final String s : suppressedSystemProperties) 2033 { 2034 suppressedArgs.add(ToCodeArgHelper.createString(s, null)); 2035 } 2036 2037 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "List<String>", 2038 requestID + "SuppressedProperties", "Arrays.asList", suppressedArgs); 2039 2040 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2041 requestID + "RequestProperties.setSuppressedSystemProperties", 2042 ToCodeArgHelper.createRaw(requestID + "SuppressedProperties", null)); 2043 } 2044 2045 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2046 requestID + "RequestProperties.setEnableGSSAPIDebugging", 2047 ToCodeArgHelper.createBoolean(enableGSSAPIDebugging, null)); 2048 2049 2050 // Create the request variable. 2051 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2); 2052 constructorArgs.add( 2053 ToCodeArgHelper.createRaw(requestID + "RequestProperties", null)); 2054 2055 final Control[] controls = getControls(); 2056 if (controls.length > 0) 2057 { 2058 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 2059 "Bind Controls")); 2060 } 2061 2062 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "GSSAPIBindRequest", 2063 requestID + "Request", "new GSSAPIBindRequest", constructorArgs); 2064 2065 2066 // Add lines for processing the request and obtaining the result. 2067 if (includeProcessing) 2068 { 2069 // Generate a string with the appropriate indent. 2070 final StringBuilder buffer = new StringBuilder(); 2071 for (int i=0; i < indentSpaces; i++) 2072 { 2073 buffer.append(' '); 2074 } 2075 final String indent = buffer.toString(); 2076 2077 lineList.add(""); 2078 lineList.add(indent + "try"); 2079 lineList.add(indent + '{'); 2080 lineList.add(indent + " BindResult " + requestID + 2081 "Result = connection.bind(" + requestID + "Request);"); 2082 lineList.add(indent + " // The bind was processed successfully."); 2083 lineList.add(indent + '}'); 2084 lineList.add(indent + "catch (LDAPException e)"); 2085 lineList.add(indent + '{'); 2086 lineList.add(indent + " // The bind failed. Maybe the following will " + 2087 "help explain why."); 2088 lineList.add(indent + " // Note that the connection is now likely in " + 2089 "an unauthenticated state."); 2090 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 2091 lineList.add(indent + " String message = e.getMessage();"); 2092 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 2093 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 2094 lineList.add(indent + " Control[] responseControls = " + 2095 "e.getResponseControls();"); 2096 lineList.add(indent + '}'); 2097 } 2098 } 2099}