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