001/* 002 * Copyright 2009-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-2022 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-2022 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 * Indicates whether JVM-level debugging should be enabled for GSSAPI bind 983 * processing. 984 * 985 * @return {@code true} if JVM-level debugging should be enabled for GSSAPI 986 * bind processing, or {@code false} if not. 987 */ 988 public boolean enableGSSAPIDebugging() 989 { 990 return enableGSSAPIDebugging; 991 } 992 993 994 995 /** 996 * Retrieves the path to the default JAAS configuration file that will be used 997 * if no file was explicitly provided. A new file may be created if 998 * necessary. 999 * 1000 * @param properties The GSSAPI properties that should be used for 1001 * authentication. 1002 * 1003 * @return The path to the default JAAS configuration file that will be used 1004 * if no file was explicitly provided. 1005 * 1006 * @throws LDAPException If an error occurs while attempting to create the 1007 * configuration file. 1008 */ 1009 @NotNull() 1010 private static String getConfigFilePath( 1011 @NotNull final GSSAPIBindRequestProperties properties) 1012 throws LDAPException 1013 { 1014 try 1015 { 1016 final File f = 1017 File.createTempFile("GSSAPIBindRequest-JAAS-Config-", ".conf"); 1018 f.deleteOnExit(); 1019 final PrintWriter w = new PrintWriter(new FileWriter(f)); 1020 1021 try 1022 { 1023 // The JAAS configuration file may vary based on the JVM that we're 1024 // using. For Sun-based JVMs, the module will be 1025 // "com.sun.security.auth.module.Krb5LoginModule". 1026 try 1027 { 1028 final Class<?> sunModuleClass = 1029 Class.forName("com.sun.security.auth.module.Krb5LoginModule"); 1030 if (sunModuleClass != null) 1031 { 1032 writeSunJAASConfig(w, properties); 1033 return f.getAbsolutePath(); 1034 } 1035 } 1036 catch (final ClassNotFoundException cnfe) 1037 { 1038 // This is fine. 1039 Debug.debugException(cnfe); 1040 } 1041 1042 1043 // For the IBM JVMs, the module will be 1044 // "com.ibm.security.auth.module.Krb5LoginModule". 1045 try 1046 { 1047 final Class<?> ibmModuleClass = 1048 Class.forName("com.ibm.security.auth.module.Krb5LoginModule"); 1049 if (ibmModuleClass != null) 1050 { 1051 writeIBMJAASConfig(w, properties); 1052 return f.getAbsolutePath(); 1053 } 1054 } 1055 catch (final ClassNotFoundException cnfe) 1056 { 1057 // This is fine. 1058 Debug.debugException(cnfe); 1059 } 1060 1061 1062 // If we've gotten here, then we can't generate an appropriate 1063 // configuration. 1064 throw new LDAPException(ResultCode.LOCAL_ERROR, 1065 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get( 1066 ERR_GSSAPI_NO_SUPPORTED_JAAS_MODULE.get())); 1067 } 1068 finally 1069 { 1070 w.close(); 1071 } 1072 } 1073 catch (final LDAPException le) 1074 { 1075 Debug.debugException(le); 1076 throw le; 1077 } 1078 catch (final Exception e) 1079 { 1080 Debug.debugException(e); 1081 1082 throw new LDAPException(ResultCode.LOCAL_ERROR, 1083 ERR_GSSAPI_CANNOT_CREATE_JAAS_CONFIG.get( 1084 StaticUtils.getExceptionMessage(e)), 1085 e); 1086 } 1087 } 1088 1089 1090 1091 /** 1092 * Writes a JAAS configuration file in a form appropriate for Sun VMs. 1093 * 1094 * @param w The writer to use to create the config file. 1095 * @param p The properties to use for GSSAPI authentication. 1096 */ 1097 private static void writeSunJAASConfig(@NotNull final PrintWriter w, 1098 @NotNull final GSSAPIBindRequestProperties p) 1099 { 1100 w.println(p.getJAASClientName() + " {"); 1101 w.println(" com.sun.security.auth.module.Krb5LoginModule required"); 1102 w.println(" client=true"); 1103 1104 if (p.getIsInitiator() != null) 1105 { 1106 w.println(" isInitiator=" + p.getIsInitiator()); 1107 } 1108 1109 if (p.refreshKrb5Config()) 1110 { 1111 w.println(" refreshKrb5Config=true"); 1112 } 1113 1114 if (p.useKeyTab()) 1115 { 1116 w.println(" useKeyTab=true"); 1117 if (p.getKeyTabPath() != null) 1118 { 1119 w.println(" keyTab=\"" + p.getKeyTabPath() + '"'); 1120 } 1121 } 1122 1123 if (p.useTicketCache()) 1124 { 1125 w.println(" useTicketCache=true"); 1126 w.println(" renewTGT=" + p.renewTGT()); 1127 w.println(" doNotPrompt=" + p.requireCachedCredentials()); 1128 1129 final String ticketCachePath = p.getTicketCachePath(); 1130 if (ticketCachePath != null) 1131 { 1132 w.println(" ticketCache=\"" + ticketCachePath + '"'); 1133 } 1134 } 1135 else 1136 { 1137 w.println(" useTicketCache=false"); 1138 } 1139 1140 if (p.enableGSSAPIDebugging()) 1141 { 1142 w.println(" debug=true"); 1143 } 1144 1145 w.println(" ;"); 1146 w.println("};"); 1147 } 1148 1149 1150 1151 /** 1152 * Writes a JAAS configuration file in a form appropriate for IBM VMs. 1153 * 1154 * @param w The writer to use to create the config file. 1155 * @param p The properties to use for GSSAPI authentication. 1156 */ 1157 private static void writeIBMJAASConfig(@NotNull final PrintWriter w, 1158 @NotNull final GSSAPIBindRequestProperties p) 1159 { 1160 // NOTE: It does not appear that the IBM GSSAPI implementation has any 1161 // analog for the renewTGT property, so it will be ignored. 1162 w.println(p.getJAASClientName() + " {"); 1163 w.println(" com.ibm.security.auth.module.Krb5LoginModule required"); 1164 if ((p.getIsInitiator() == null) || p.getIsInitiator().booleanValue()) 1165 { 1166 w.println(" credsType=initiator"); 1167 } 1168 else 1169 { 1170 w.println(" credsType=acceptor"); 1171 } 1172 1173 if (p.refreshKrb5Config()) 1174 { 1175 w.println(" refreshKrb5Config=true"); 1176 } 1177 1178 if (p.useKeyTab()) 1179 { 1180 w.println(" useKeyTab=true"); 1181 if (p.getKeyTabPath() != null) 1182 { 1183 w.println(" keyTab=\"" + p.getKeyTabPath() + '"'); 1184 } 1185 } 1186 1187 if (p.useTicketCache()) 1188 { 1189 final String ticketCachePath = p.getTicketCachePath(); 1190 if (ticketCachePath == null) 1191 { 1192 if (p.requireCachedCredentials()) 1193 { 1194 w.println(" useDefaultCcache=true"); 1195 } 1196 } 1197 else 1198 { 1199 final File f = new File(ticketCachePath); 1200 final String path = f.getAbsolutePath().replace('\\', '/'); 1201 w.println(" useCcache=\"file://" + path + '"'); 1202 } 1203 } 1204 else 1205 { 1206 w.println(" useDefaultCcache=false"); 1207 } 1208 1209 if (p.enableGSSAPIDebugging()) 1210 { 1211 w.println(" debug=true"); 1212 } 1213 1214 w.println(" ;"); 1215 w.println("};"); 1216 } 1217 1218 1219 1220 /** 1221 * Sends this bind request to the target server over the provided connection 1222 * and returns the corresponding response. 1223 * 1224 * @param connection The connection to use to send this bind request to the 1225 * server and read the associated response. 1226 * @param depth The current referral depth for this request. It should 1227 * always be one for the initial request, and should only 1228 * be incremented when following referrals. 1229 * 1230 * @return The bind response read from the server. 1231 * 1232 * @throws LDAPException If a problem occurs while sending the request or 1233 * reading the response. 1234 */ 1235 @Override() 1236 @NotNull() 1237 protected BindResult process(@NotNull final LDAPConnection connection, 1238 final int depth) 1239 throws LDAPException 1240 { 1241 if (! conn.compareAndSet(null, connection)) 1242 { 1243 throw new LDAPException(ResultCode.LOCAL_ERROR, 1244 ERR_GSSAPI_MULTIPLE_CONCURRENT_REQUESTS.get()); 1245 } 1246 1247 setProperty(PROPERTY_CONFIG_FILE, configFilePath); 1248 setProperty(PROPERTY_SUBJECT_CREDS_ONLY, 1249 String.valueOf(useSubjectCredentialsOnly)); 1250 if (Debug.debugEnabled(DebugType.LDAP)) 1251 { 1252 Debug.debug(Level.CONFIG, DebugType.LDAP, 1253 "Using config file property " + PROPERTY_CONFIG_FILE + " = '" + 1254 configFilePath + "'."); 1255 Debug.debug(Level.CONFIG, DebugType.LDAP, 1256 "Using subject creds only property " + PROPERTY_SUBJECT_CREDS_ONLY + 1257 " = '" + useSubjectCredentialsOnly + "'."); 1258 } 1259 1260 if (kdcAddress == null) 1261 { 1262 if (DEFAULT_KDC_ADDRESS == null) 1263 { 1264 clearProperty(PROPERTY_KDC_ADDRESS); 1265 if (Debug.debugEnabled(DebugType.LDAP)) 1266 { 1267 Debug.debug(Level.CONFIG, DebugType.LDAP, 1268 "Clearing kdcAddress property '" + PROPERTY_KDC_ADDRESS + "'."); 1269 } 1270 } 1271 else 1272 { 1273 setProperty(PROPERTY_KDC_ADDRESS, DEFAULT_KDC_ADDRESS); 1274 if (Debug.debugEnabled(DebugType.LDAP)) 1275 { 1276 Debug.debug(Level.CONFIG, DebugType.LDAP, 1277 "Using default kdcAddress property " + PROPERTY_KDC_ADDRESS + 1278 " = '" + DEFAULT_KDC_ADDRESS + "'."); 1279 } 1280 } 1281 } 1282 else 1283 { 1284 setProperty(PROPERTY_KDC_ADDRESS, kdcAddress); 1285 if (Debug.debugEnabled(DebugType.LDAP)) 1286 { 1287 Debug.debug(Level.CONFIG, DebugType.LDAP, 1288 "Using kdcAddress property " + PROPERTY_KDC_ADDRESS + " = '" + 1289 kdcAddress + "'."); 1290 } 1291 } 1292 1293 if (realm == null) 1294 { 1295 if (DEFAULT_REALM == null) 1296 { 1297 clearProperty(PROPERTY_REALM); 1298 if (Debug.debugEnabled(DebugType.LDAP)) 1299 { 1300 Debug.debug(Level.CONFIG, DebugType.LDAP, 1301 "Clearing realm property '" + PROPERTY_REALM + "'."); 1302 } 1303 } 1304 else 1305 { 1306 setProperty(PROPERTY_REALM, DEFAULT_REALM); 1307 if (Debug.debugEnabled(DebugType.LDAP)) 1308 { 1309 Debug.debug(Level.CONFIG, DebugType.LDAP, 1310 "Using default realm property " + PROPERTY_REALM + " = '" + 1311 DEFAULT_REALM + "'."); 1312 } 1313 } 1314 } 1315 else 1316 { 1317 setProperty(PROPERTY_REALM, realm); 1318 if (Debug.debugEnabled(DebugType.LDAP)) 1319 { 1320 Debug.debug(Level.CONFIG, DebugType.LDAP, 1321 "Using realm property " + PROPERTY_REALM + " = '" + realm + "'."); 1322 } 1323 } 1324 1325 try 1326 { 1327 // Reload the configuration before creating the login context, which may 1328 // work around problems that could arise if certain configuration is 1329 // loaded and cached before the above system properties were set. 1330 Configuration.getConfiguration().refresh(); 1331 } 1332 catch (final Exception e) 1333 { 1334 Debug.debugException(e); 1335 } 1336 1337 try 1338 { 1339 final LoginContext context; 1340 try 1341 { 1342 context = new LoginContext(jaasClientName, this); 1343 context.login(); 1344 } 1345 catch (final Exception e) 1346 { 1347 Debug.debugException(e); 1348 1349 throw new LDAPException(ResultCode.LOCAL_ERROR, 1350 ERR_GSSAPI_CANNOT_INITIALIZE_JAAS_CONTEXT.get( 1351 StaticUtils.getExceptionMessage(e)), 1352 e); 1353 } 1354 1355 try 1356 { 1357 return (BindResult) Subject.doAs(context.getSubject(), this); 1358 } 1359 catch (final Exception e) 1360 { 1361 Debug.debugException(e); 1362 if (e instanceof LDAPException) 1363 { 1364 throw (LDAPException) e; 1365 } 1366 else 1367 { 1368 throw new LDAPException(ResultCode.LOCAL_ERROR, 1369 ERR_GSSAPI_AUTHENTICATION_FAILED.get( 1370 StaticUtils.getExceptionMessage(e)), 1371 e); 1372 } 1373 } 1374 } 1375 finally 1376 { 1377 conn.set(null); 1378 } 1379 } 1380 1381 1382 1383 /** 1384 * Perform the privileged portion of the authentication processing. 1385 * 1386 * @return {@code null}, since no return value is actually needed. 1387 * 1388 * @throws LDAPException If a problem occurs during processing. 1389 */ 1390 @InternalUseOnly() 1391 @Override() 1392 @NotNull() 1393 public Object run() 1394 throws LDAPException 1395 { 1396 unhandledCallbackMessages.clear(); 1397 1398 final LDAPConnection connection = conn.get(); 1399 1400 1401 final HashMap<String,Object> saslProperties = 1402 new HashMap<>(StaticUtils.computeMapCapacity(2)); 1403 saslProperties.put(Sasl.QOP, SASLQualityOfProtection.toString(allowedQoP)); 1404 saslProperties.put(Sasl.SERVER_AUTH, "true"); 1405 1406 switch (channelBindingType) 1407 { 1408 case NONE: 1409 // No action is required. 1410 break; 1411 1412 case TLS_SERVER_END_POINT: 1413 saslProperties.put(PROPERTY_CHANNEL_BINDING_DATA, 1414 generateTLSServerEndPointChannelBindingData(connection)); 1415 break; 1416 1417 default: 1418 // This should never happen. 1419 throw new LDAPException(ResultCode.PARAM_ERROR, 1420 ERR_GSSAPI_UNSUPPORTED_CHANNEL_BINDING_TYPE.get( 1421 channelBindingType.getName())); 1422 } 1423 1424 final SaslClient saslClient; 1425 try 1426 { 1427 String serverName = saslClientServerName; 1428 if (serverName == null) 1429 { 1430 serverName = connection.getConnectedAddress(); 1431 } 1432 1433 final String[] mechanisms = { GSSAPI_MECHANISM_NAME }; 1434 saslClient = Sasl.createSaslClient(mechanisms, authorizationID, 1435 servicePrincipalProtocol, serverName, saslProperties, this); 1436 } 1437 catch (final Exception e) 1438 { 1439 Debug.debugException(e); 1440 throw new LDAPException(ResultCode.LOCAL_ERROR, 1441 ERR_GSSAPI_CANNOT_CREATE_SASL_CLIENT.get( 1442 StaticUtils.getExceptionMessage(e)), 1443 e); 1444 } 1445 1446 final SASLClientBindHandler bindHandler = new SASLClientBindHandler(this, 1447 connection, GSSAPI_MECHANISM_NAME, saslClient, getControls(), 1448 getResponseTimeoutMillis(connection), unhandledCallbackMessages); 1449 1450 try 1451 { 1452 return bindHandler.processSASLBind(); 1453 } 1454 finally 1455 { 1456 messageID = bindHandler.getMessageID(); 1457 } 1458 } 1459 1460 1461 1462 /** 1463 * Generates an appropriate value to use for TLS server endpoint channel 1464 * binding. See RFC 5929 section 4 for a description of this channel binding 1465 * type. 1466 * 1467 * @param connection The connection used to communicate with the server. It 1468 * must not be {@code null}. 1469 * 1470 * @return The bytes that comprise the TLS server endpoint channel binding 1471 * data. 1472 * 1473 * @throws LDAPException If a problem occurs while attempting to generate 1474 * the channel binding data. 1475 */ 1476 @NotNull() 1477 static byte[] generateTLSServerEndPointChannelBindingData( 1478 @NotNull final LDAPConnection connection) 1479 throws LDAPException 1480 { 1481 // Make sure that the connection has an associated SSL session. 1482 final String channelBindingType = 1483 GSSAPIChannelBindingType.TLS_SERVER_END_POINT.getName(); 1484 1485 final SSLSession sslSession = connection.getSSLSession(); 1486 if (sslSession == null) 1487 { 1488 throw new LDAPException(ResultCode.PARAM_ERROR, 1489 ERR_GSSAPI_TLS_SERVER_CB_NO_SSL_SESSION.get(channelBindingType)); 1490 } 1491 1492 1493 // Get the certificate presented by the server during TLS negotiation. 1494 final Certificate[] serverCertificateChain; 1495 try 1496 { 1497 serverCertificateChain = sslSession.getPeerCertificates(); 1498 } 1499 catch (final Exception e) 1500 { 1501 Debug.debugException(e); 1502 throw new LDAPException(ResultCode.LOCAL_ERROR, 1503 ERR_GSSAPI_TLS_SERVER_CB_CANNOT_GET_PEER_CERTS.get( 1504 channelBindingType, StaticUtils.getExceptionMessage(e)), 1505 e); 1506 } 1507 1508 if ((serverCertificateChain == null) || 1509 (serverCertificateChain.length == 0)) 1510 { 1511 throw new LDAPException(ResultCode.PARAM_ERROR, 1512 ERR_GSSAPI_TLS_SERVER_CB_NO_CERTS.get(channelBindingType)); 1513 } 1514 1515 if (! (serverCertificateChain[0] instanceof X509Certificate)) 1516 { 1517 throw new LDAPException(ResultCode.PARAM_ERROR, 1518 ERR_GSSAPI_TLS_SERVER_CB_SERVER_CERT_NOT_X509.get(channelBindingType, 1519 serverCertificateChain[0].getType())); 1520 } 1521 1522 final X509Certificate serverCertificate = 1523 (X509Certificate) serverCertificateChain[0]; 1524 1525 1526 // Determine the appropriate digest algorithm to use for generating the 1527 // channel binding data. 1528 final String signatureAlgorithmName = 1529 StaticUtils.toUpperCase(serverCertificate.getSigAlgName()); 1530 if (signatureAlgorithmName == null) 1531 { 1532 throw new LDAPException(ResultCode.PARAM_ERROR, 1533 ERR_GSSAPI_TLS_SERVER_CB_NO_SIG_ALG.get(channelBindingType, 1534 serverCertificate.getSubjectX500Principal().getName( 1535 X500Principal.RFC2253))); 1536 } 1537 1538 final int withPos = signatureAlgorithmName.indexOf("WITH"); 1539 if (withPos <= 0) 1540 { 1541 throw new LDAPException(ResultCode.PARAM_ERROR, 1542 ERR_GSSAPI_TLS_SERVER_CB_CANNOT_DETERMINE_SIG_DIGEST_ALG.get( 1543 channelBindingType, serverCertificate.getSigAlgName(), 1544 serverCertificate.getSubjectX500Principal().getName( 1545 X500Principal.RFC2253))); 1546 } 1547 1548 String digestAlgorithm = signatureAlgorithmName.substring(0, withPos); 1549 switch (digestAlgorithm) 1550 { 1551 case "MD5": 1552 case "SHA": 1553 case "SHA1": 1554 case "SHA-1": 1555 // All of these will be overridden to use the SHA-256 algorithm. 1556 digestAlgorithm = "SHA-256"; 1557 break; 1558 1559 case "SHA256": 1560 case "SHA-256": 1561 digestAlgorithm = "SHA-256"; 1562 break; 1563 1564 case "SHA384": 1565 case "SHA-384": 1566 digestAlgorithm = "SHA-384"; 1567 break; 1568 1569 case "SHA512": 1570 case "SHA-512": 1571 digestAlgorithm = "SHA-512"; 1572 break; 1573 1574 default: 1575 // Just use the digest algorithm extracted from the certificate. 1576 break; 1577 } 1578 1579 1580 // Generate a digest of the X.509 certificate using the selected digest 1581 // algorithm. 1582 final byte[] digestBytes; 1583 try 1584 { 1585 final MessageDigest messageDigest = 1586 CryptoHelper.getMessageDigest(digestAlgorithm); 1587 digestBytes = messageDigest.digest(serverCertificate.getEncoded()); 1588 } 1589 catch (final Exception e) 1590 { 1591 Debug.debugException(e); 1592 throw new LDAPException(ResultCode.LOCAL_ERROR, 1593 ERR_GSSAPI_TLS_SERVER_CB_CANNOT_DIGEST_CERT.get(channelBindingType, 1594 digestAlgorithm, 1595 serverCertificate.getSubjectX500Principal().getName( 1596 X500Principal.RFC2253), 1597 StaticUtils.getExceptionMessage(e)), 1598 e); 1599 } 1600 1601 1602 // The channel binding data will be a concatenation of the channel binding 1603 // type, a colon, and the digest bytes. 1604 final ByteStringBuffer buffer = new ByteStringBuffer(); 1605 buffer.append(channelBindingType); 1606 buffer.append(':'); 1607 buffer.append(digestBytes); 1608 return buffer.toByteArray(); 1609 } 1610 1611 1612 1613 /** 1614 * {@inheritDoc} 1615 */ 1616 @Override() 1617 @NotNull() 1618 public GSSAPIBindRequest getRebindRequest(@NotNull final String host, 1619 final int port) 1620 { 1621 try 1622 { 1623 final GSSAPIBindRequestProperties gssapiProperties = 1624 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1625 password, realm, kdcAddress, configFilePath); 1626 gssapiProperties.setAllowedQoP(allowedQoP); 1627 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1628 gssapiProperties.setUseTicketCache(useTicketCache); 1629 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1630 gssapiProperties.setRenewTGT(renewTGT); 1631 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1632 gssapiProperties.setTicketCachePath(ticketCachePath); 1633 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1634 gssapiProperties.setJAASClientName(jaasClientName); 1635 gssapiProperties.setSASLClientServerName(saslClientServerName); 1636 gssapiProperties.setSuppressedSystemProperties( 1637 suppressedSystemProperties); 1638 1639 return new GSSAPIBindRequest(gssapiProperties, getControls()); 1640 } 1641 catch (final Exception e) 1642 { 1643 // This should never happen. 1644 Debug.debugException(e); 1645 return null; 1646 } 1647 } 1648 1649 1650 1651 /** 1652 * Handles any necessary callbacks required for SASL authentication. 1653 * 1654 * @param callbacks The set of callbacks to be handled. 1655 * 1656 * @throws UnsupportedCallbackException If an unsupported type of callback 1657 * was received. 1658 */ 1659 @InternalUseOnly() 1660 @Override() 1661 public void handle(@NotNull final Callback[] callbacks) 1662 throws UnsupportedCallbackException 1663 { 1664 for (final Callback callback : callbacks) 1665 { 1666 if (callback instanceof NameCallback) 1667 { 1668 ((NameCallback) callback).setName(authenticationID); 1669 } 1670 else if (callback instanceof PasswordCallback) 1671 { 1672 if (password == null) 1673 { 1674 throw new UnsupportedCallbackException(callback, 1675 ERR_GSSAPI_NO_PASSWORD_AVAILABLE.get()); 1676 } 1677 else 1678 { 1679 ((PasswordCallback) callback).setPassword( 1680 password.stringValue().toCharArray()); 1681 } 1682 } 1683 else if (callback instanceof RealmCallback) 1684 { 1685 final RealmCallback rc = (RealmCallback) callback; 1686 if (realm == null) 1687 { 1688 unhandledCallbackMessages.add( 1689 ERR_GSSAPI_REALM_REQUIRED_BUT_NONE_PROVIDED.get(rc.getPrompt())); 1690 } 1691 else 1692 { 1693 rc.setText(realm); 1694 } 1695 } 1696 else 1697 { 1698 // This is an unexpected callback. 1699 if (Debug.debugEnabled(DebugType.LDAP)) 1700 { 1701 Debug.debug(Level.WARNING, DebugType.LDAP, 1702 "Unexpected GSSAPI SASL callback of type " + 1703 callback.getClass().getName()); 1704 } 1705 1706 unhandledCallbackMessages.add(ERR_GSSAPI_UNEXPECTED_CALLBACK.get( 1707 callback.getClass().getName())); 1708 } 1709 } 1710 } 1711 1712 1713 1714 /** 1715 * {@inheritDoc} 1716 */ 1717 @Override() 1718 public int getLastMessageID() 1719 { 1720 return messageID; 1721 } 1722 1723 1724 1725 /** 1726 * {@inheritDoc} 1727 */ 1728 @Override() 1729 @NotNull() 1730 public GSSAPIBindRequest duplicate() 1731 { 1732 return duplicate(getControls()); 1733 } 1734 1735 1736 1737 /** 1738 * {@inheritDoc} 1739 */ 1740 @Override() 1741 @NotNull() 1742 public GSSAPIBindRequest duplicate(@Nullable final Control[] controls) 1743 { 1744 try 1745 { 1746 final GSSAPIBindRequestProperties gssapiProperties = 1747 new GSSAPIBindRequestProperties(authenticationID, authorizationID, 1748 password, realm, kdcAddress, configFilePath); 1749 gssapiProperties.setAllowedQoP(allowedQoP); 1750 gssapiProperties.setServicePrincipalProtocol(servicePrincipalProtocol); 1751 gssapiProperties.setUseTicketCache(useTicketCache); 1752 gssapiProperties.setRequireCachedCredentials(requireCachedCredentials); 1753 gssapiProperties.setRenewTGT(renewTGT); 1754 gssapiProperties.setRefreshKrb5Config(refreshKrb5Config); 1755 gssapiProperties.setUseKeyTab(useKeyTab); 1756 gssapiProperties.setKeyTabPath(keyTabPath); 1757 gssapiProperties.setUseSubjectCredentialsOnly(useSubjectCredentialsOnly); 1758 gssapiProperties.setTicketCachePath(ticketCachePath); 1759 gssapiProperties.setEnableGSSAPIDebugging(enableGSSAPIDebugging); 1760 gssapiProperties.setJAASClientName(jaasClientName); 1761 gssapiProperties.setSASLClientServerName(saslClientServerName); 1762 gssapiProperties.setIsInitiator(isInitiator); 1763 gssapiProperties.setSuppressedSystemProperties( 1764 suppressedSystemProperties); 1765 1766 final GSSAPIBindRequest bindRequest = 1767 new GSSAPIBindRequest(gssapiProperties, controls); 1768 bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null)); 1769 return bindRequest; 1770 } 1771 catch (final Exception e) 1772 { 1773 // This should never happen. 1774 Debug.debugException(e); 1775 return null; 1776 } 1777 } 1778 1779 1780 1781 /** 1782 * Clears the specified system property, unless it is one that is configured 1783 * to be suppressed. 1784 * 1785 * @param name The name of the property to be suppressed. 1786 */ 1787 private void clearProperty(@NotNull final String name) 1788 { 1789 if (! suppressedSystemProperties.contains(name)) 1790 { 1791 StaticUtils.clearSystemProperty(name); 1792 } 1793 } 1794 1795 1796 1797 /** 1798 * Sets the specified system property, unless it is one that is configured to 1799 * be suppressed. 1800 * 1801 * @param name The name of the property to be suppressed. 1802 * @param value The value of the property to be suppressed. 1803 */ 1804 private void setProperty(@NotNull final String name, 1805 @NotNull final String value) 1806 { 1807 if (! suppressedSystemProperties.contains(name)) 1808 { 1809 StaticUtils.setSystemProperty(name, value); 1810 } 1811 } 1812 1813 1814 1815 /** 1816 * {@inheritDoc} 1817 */ 1818 @Override() 1819 public void toString(@NotNull final StringBuilder buffer) 1820 { 1821 buffer.append("GSSAPIBindRequest(authenticationID='"); 1822 buffer.append(authenticationID); 1823 buffer.append('\''); 1824 1825 if (authorizationID != null) 1826 { 1827 buffer.append(", authorizationID='"); 1828 buffer.append(authorizationID); 1829 buffer.append('\''); 1830 } 1831 1832 if (realm != null) 1833 { 1834 buffer.append(", realm='"); 1835 buffer.append(realm); 1836 buffer.append('\''); 1837 } 1838 1839 buffer.append(", qop='"); 1840 buffer.append(SASLQualityOfProtection.toString(allowedQoP)); 1841 buffer.append('\''); 1842 1843 if (kdcAddress != null) 1844 { 1845 buffer.append(", kdcAddress='"); 1846 buffer.append(kdcAddress); 1847 buffer.append('\''); 1848 } 1849 1850 if (isInitiator != null) 1851 { 1852 buffer.append(", isInitiator="); 1853 buffer.append(isInitiator); 1854 } 1855 1856 buffer.append(", jaasClientName='"); 1857 buffer.append(jaasClientName); 1858 buffer.append("', configFilePath='"); 1859 buffer.append(configFilePath); 1860 buffer.append("', servicePrincipalProtocol='"); 1861 buffer.append(servicePrincipalProtocol); 1862 buffer.append("', enableGSSAPIDebugging="); 1863 buffer.append(enableGSSAPIDebugging); 1864 1865 final Control[] controls = getControls(); 1866 if (controls.length > 0) 1867 { 1868 buffer.append(", controls={"); 1869 for (int i=0; i < controls.length; i++) 1870 { 1871 if (i > 0) 1872 { 1873 buffer.append(", "); 1874 } 1875 1876 buffer.append(controls[i]); 1877 } 1878 buffer.append('}'); 1879 } 1880 1881 buffer.append(')'); 1882 } 1883 1884 1885 1886 /** 1887 * {@inheritDoc} 1888 */ 1889 @Override() 1890 public void toCode(@NotNull final List<String> lineList, 1891 @NotNull final String requestID, 1892 final int indentSpaces, final boolean includeProcessing) 1893 { 1894 // Create and update the bind request properties object. 1895 ToCodeHelper.generateMethodCall(lineList, indentSpaces, 1896 "GSSAPIBindRequestProperties", requestID + "RequestProperties", 1897 "new GSSAPIBindRequestProperties", 1898 ToCodeArgHelper.createString(authenticationID, "Authentication ID"), 1899 ToCodeArgHelper.createString("---redacted-password---", "Password")); 1900 1901 if (authorizationID != null) 1902 { 1903 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1904 requestID + "RequestProperties.setAuthorizationID", 1905 ToCodeArgHelper.createString(authorizationID, null)); 1906 } 1907 1908 if (realm != null) 1909 { 1910 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1911 requestID + "RequestProperties.setRealm", 1912 ToCodeArgHelper.createString(realm, null)); 1913 } 1914 1915 final ArrayList<String> qopValues = new ArrayList<>(3); 1916 for (final SASLQualityOfProtection qop : allowedQoP) 1917 { 1918 qopValues.add("SASLQualityOfProtection." + qop.name()); 1919 } 1920 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1921 requestID + "RequestProperties.setAllowedQoP", 1922 ToCodeArgHelper.createRaw(qopValues, null)); 1923 1924 if (kdcAddress != null) 1925 { 1926 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1927 requestID + "RequestProperties.setKDCAddress", 1928 ToCodeArgHelper.createString(kdcAddress, null)); 1929 } 1930 1931 if (jaasClientName != null) 1932 { 1933 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1934 requestID + "RequestProperties.setJAASClientName", 1935 ToCodeArgHelper.createString(jaasClientName, null)); 1936 } 1937 1938 if (configFilePath != null) 1939 { 1940 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1941 requestID + "RequestProperties.setConfigFilePath", 1942 ToCodeArgHelper.createString(configFilePath, null)); 1943 } 1944 1945 if (saslClientServerName != null) 1946 { 1947 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1948 requestID + "RequestProperties.setSASLClientServerName", 1949 ToCodeArgHelper.createString(saslClientServerName, null)); 1950 } 1951 1952 if (servicePrincipalProtocol != null) 1953 { 1954 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1955 requestID + "RequestProperties.setServicePrincipalProtocol", 1956 ToCodeArgHelper.createString(servicePrincipalProtocol, null)); 1957 } 1958 1959 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1960 requestID + "RequestProperties.setRefreshKrb5Config", 1961 ToCodeArgHelper.createBoolean(refreshKrb5Config, null)); 1962 1963 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1964 requestID + "RequestProperties.setUseKeyTab", 1965 ToCodeArgHelper.createBoolean(useKeyTab, null)); 1966 1967 if (keyTabPath != null) 1968 { 1969 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1970 requestID + "RequestProperties.setKeyTabPath", 1971 ToCodeArgHelper.createString(keyTabPath, null)); 1972 } 1973 1974 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1975 requestID + "RequestProperties.setUseSubjectCredentialsOnly", 1976 ToCodeArgHelper.createBoolean(useSubjectCredentialsOnly, null)); 1977 1978 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1979 requestID + "RequestProperties.setUseTicketCache", 1980 ToCodeArgHelper.createBoolean(useTicketCache, null)); 1981 1982 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1983 requestID + "RequestProperties.setRequireCachedCredentials", 1984 ToCodeArgHelper.createBoolean(requireCachedCredentials, null)); 1985 1986 if (ticketCachePath != null) 1987 { 1988 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1989 requestID + "RequestProperties.setTicketCachePath", 1990 ToCodeArgHelper.createString(ticketCachePath, null)); 1991 } 1992 1993 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 1994 requestID + "RequestProperties.setRenewTGT", 1995 ToCodeArgHelper.createBoolean(renewTGT, null)); 1996 1997 if (isInitiator != null) 1998 { 1999 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2000 requestID + "RequestProperties.setIsInitiator", 2001 ToCodeArgHelper.createBoolean(isInitiator, null)); 2002 } 2003 2004 if ((suppressedSystemProperties != null) && 2005 (! suppressedSystemProperties.isEmpty())) 2006 { 2007 final ArrayList<ToCodeArgHelper> suppressedArgs = 2008 new ArrayList<>(suppressedSystemProperties.size()); 2009 for (final String s : suppressedSystemProperties) 2010 { 2011 suppressedArgs.add(ToCodeArgHelper.createString(s, null)); 2012 } 2013 2014 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "List<String>", 2015 requestID + "SuppressedProperties", "Arrays.asList", suppressedArgs); 2016 2017 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2018 requestID + "RequestProperties.setSuppressedSystemProperties", 2019 ToCodeArgHelper.createRaw(requestID + "SuppressedProperties", null)); 2020 } 2021 2022 ToCodeHelper.generateMethodCall(lineList, indentSpaces, null, null, 2023 requestID + "RequestProperties.setEnableGSSAPIDebugging", 2024 ToCodeArgHelper.createBoolean(enableGSSAPIDebugging, null)); 2025 2026 2027 // Create the request variable. 2028 final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(2); 2029 constructorArgs.add( 2030 ToCodeArgHelper.createRaw(requestID + "RequestProperties", null)); 2031 2032 final Control[] controls = getControls(); 2033 if (controls.length > 0) 2034 { 2035 constructorArgs.add(ToCodeArgHelper.createControlArray(controls, 2036 "Bind Controls")); 2037 } 2038 2039 ToCodeHelper.generateMethodCall(lineList, indentSpaces, "GSSAPIBindRequest", 2040 requestID + "Request", "new GSSAPIBindRequest", constructorArgs); 2041 2042 2043 // Add lines for processing the request and obtaining the result. 2044 if (includeProcessing) 2045 { 2046 // Generate a string with the appropriate indent. 2047 final StringBuilder buffer = new StringBuilder(); 2048 for (int i=0; i < indentSpaces; i++) 2049 { 2050 buffer.append(' '); 2051 } 2052 final String indent = buffer.toString(); 2053 2054 lineList.add(""); 2055 lineList.add(indent + "try"); 2056 lineList.add(indent + '{'); 2057 lineList.add(indent + " BindResult " + requestID + 2058 "Result = connection.bind(" + requestID + "Request);"); 2059 lineList.add(indent + " // The bind was processed successfully."); 2060 lineList.add(indent + '}'); 2061 lineList.add(indent + "catch (LDAPException e)"); 2062 lineList.add(indent + '{'); 2063 lineList.add(indent + " // The bind failed. Maybe the following will " + 2064 "help explain why."); 2065 lineList.add(indent + " // Note that the connection is now likely in " + 2066 "an unauthenticated state."); 2067 lineList.add(indent + " ResultCode resultCode = e.getResultCode();"); 2068 lineList.add(indent + " String message = e.getMessage();"); 2069 lineList.add(indent + " String matchedDN = e.getMatchedDN();"); 2070 lineList.add(indent + " String[] referralURLs = e.getReferralURLs();"); 2071 lineList.add(indent + " Control[] responseControls = " + 2072 "e.getResponseControls();"); 2073 lineList.add(indent + '}'); 2074 } 2075 } 2076}