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