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