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