001/*
002 * Copyright 2012-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-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) 2012-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.unboundidds;
037
038
039
040import java.nio.charset.StandardCharsets;
041import java.util.ArrayList;
042import java.util.List;
043
044import com.unboundid.asn1.ASN1OctetString;
045import com.unboundid.ldap.sdk.Control;
046import com.unboundid.ldap.sdk.LDAPException;
047import com.unboundid.ldap.sdk.ToCodeArgHelper;
048import com.unboundid.ldap.sdk.ToCodeHelper;
049import com.unboundid.util.NotMutable;
050import com.unboundid.util.NotNull;
051import com.unboundid.util.Nullable;
052import com.unboundid.util.ThreadSafety;
053import com.unboundid.util.ThreadSafetyLevel;
054import com.unboundid.util.Validator;
055
056
057
058/**
059 * This class provides an implementation of the UNBOUNDID-TOTP SASL bind request
060 * that may be used to repeatedly generate one-time password values.  Because it
061 * is configured with the shared secret rather than a point-in-time version of
062 * the password, it can be used for cases in which the authentication process
063 * may need to be repeated (e.g., for use in a connection pool, following
064 * referrals, or if the auto-reconnect feature is enabled).  If the shared
065 * secret is not known and the one-time password will be provided from an
066 * external source (e.g., entered by a user), then the
067 * {@link SingleUseTOTPBindRequest} variant should be used instead.
068 * <BR>
069 * <BLOCKQUOTE>
070 *   <B>NOTE:</B>  This class, and other classes within the
071 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
072 *   supported for use against Ping Identity, UnboundID, and
073 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
074 *   for proprietary functionality or for external specifications that are not
075 *   considered stable or mature enough to be guaranteed to work in an
076 *   interoperable way with other types of LDAP servers.
077 * </BLOCKQUOTE>
078 */
079@NotMutable()
080@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
081public final class ReusableTOTPBindRequest
082       extends UnboundIDTOTPBindRequest
083{
084  /**
085   * The serial version UID for this serializable class.
086   */
087  private static final long serialVersionUID = -8283436883838802510L;
088
089
090
091  // The shared secret key to use when generating the TOTP password.
092  @NotNull private final byte[] sharedSecret;
093
094  // The duration (in seconds) of the time interval to use when generating the
095  // TOTP password.
096  private final int totpIntervalDurationSeconds;
097
098  // The number of digits to include in the generated TOTP password.
099  private final int totpNumDigits;
100
101
102
103  /**
104   * Creates a new SASL TOTP bind request with the provided information.
105   *
106   * @param  authenticationID  The authentication identity for the bind request.
107   *                           It must not be {@code null}, and must be in the
108   *                           form "u:" followed by a username, or "dn:"
109   *                           followed by a DN.
110   * @param  authorizationID   The authorization identity for the bind request.
111   *                           It may be {@code null} if the authorization
112   *                           identity should be the same as the authentication
113   *                           identity.  If an authorization identity is
114   *                           specified, it must be in the form "u:" followed
115   *                           by a username, or "dn:" followed by a DN.  The
116   *                           value "dn:" may indicate an authorization
117   *                           identity of the anonymous user.
118   * @param  sharedSecret      The shared secret key to use when generating the
119   *                           TOTP password.
120   * @param  staticPassword    The static password for the target user.  It may
121   *                           be {@code null} if only the one-time password is
122   *                           to be used for authentication (which may or may
123   *                           not be allowed by the server).
124   * @param  controls          The set of controls to include in the bind
125   *                           request.
126   */
127  public ReusableTOTPBindRequest(@NotNull final String authenticationID,
128                                 @Nullable final String authorizationID,
129                                 @NotNull final byte[] sharedSecret,
130                                 @Nullable final String staticPassword,
131                                 @Nullable final Control... controls)
132  {
133    this(authenticationID, authorizationID, sharedSecret, staticPassword,
134         OneTimePassword.DEFAULT_TOTP_INTERVAL_DURATION_SECONDS,
135         OneTimePassword.DEFAULT_TOTP_NUM_DIGITS, controls);
136  }
137
138
139
140  /**
141   * Creates a new SASL TOTP bind request with the provided information.
142   *
143   * @param  authenticationID  The authentication identity for the bind request.
144   *                           It must not be {@code null}, and must be in the
145   *                           form "u:" followed by a username, or "dn:"
146   *                           followed by a DN.
147   * @param  authorizationID   The authorization identity for the bind request.
148   *                           It may be {@code null} if the authorization
149   *                           identity should be the same as the authentication
150   *                           identity.  If an authorization identity is
151   *                           specified, it must be in the form "u:" followed
152   *                           by a username, or "dn:" followed by a DN.  The
153   *                           value "dn:" may indicate an authorization
154   *                           identity of the anonymous user.
155   * @param  sharedSecret      The shared secret key to use when generating the
156   *                           TOTP password.
157   * @param  staticPassword    The static password for the target user.  It may
158   *                           be {@code null} if only the one-time password is
159   *                           to be used for authentication (which may or may
160   *                           not be allowed by the server).
161   * @param  controls          The set of controls to include in the bind
162   *                           request.
163   */
164  public ReusableTOTPBindRequest(@NotNull final String authenticationID,
165                                 @Nullable final String authorizationID,
166                                 @NotNull final byte[] sharedSecret,
167                                 @Nullable final byte[] staticPassword,
168                                 @Nullable final Control... controls)
169  {
170    this(authenticationID, authorizationID, sharedSecret, staticPassword,
171         OneTimePassword.DEFAULT_TOTP_INTERVAL_DURATION_SECONDS,
172         OneTimePassword.DEFAULT_TOTP_NUM_DIGITS, controls);
173  }
174
175
176
177  /**
178   * Creates a new SASL TOTP bind request with the provided information.
179   *
180   * @param  authenticationID             The authentication identity for the
181   *                                      bind request.  It must not be
182   *                                      {@code null}, and must be in the form
183   *                                      "u:" followed by a username, or "dn:"
184   *                                      followed by a DN.
185   * @param  authorizationID              The authorization identity for the
186   *                                      bind request.  It may be {@code null}
187   *                                      if the authorization identity should
188   *                                      be the same as the authentication
189   *                                      identity.  If an authorization
190   *                                      identity is specified, it must be in
191   *                                      the form "u:" followed by a username,
192   *                                      or "dn:" followed by a DN.  The value
193   *                                      "dn:" may indicate an authorization
194   *                                      identity of the anonymous user.
195   * @param  sharedSecret                 The shared secret key to use when
196   *                                      generating the TOTP password.
197   * @param  staticPassword               The static password for the target
198   *                                      user.  It may be {@code null} if only
199   *                                      the one-time password is to be used
200   *                                      for authentication (which may or may
201   *                                      not be allowed by the server).
202   * @param  totpIntervalDurationSeconds  The duration (in seconds) of the time
203   *                                      interval to use for TOTP processing.
204   *                                      It must be greater than zero.
205   * @param  totpNumDigits                The number of digits to include in the
206   *                                      generated TOTP password.  It must be
207   *                                      greater than or equal to six and less
208   *                                      than or equal to eight.
209   * @param  controls                     The set of controls to include in the
210   *                                      bind request.
211   */
212  public ReusableTOTPBindRequest(@NotNull final String authenticationID,
213                                 @Nullable final String authorizationID,
214                                 @NotNull final byte[] sharedSecret,
215                                 @Nullable final String staticPassword,
216                                 final int totpIntervalDurationSeconds,
217                                 final int totpNumDigits,
218                                 @Nullable final Control... controls)
219  {
220    super(authenticationID, authorizationID, staticPassword, controls);
221
222    Validator.ensureTrue(totpIntervalDurationSeconds > 0);
223    Validator.ensureTrue((totpNumDigits >= 6) && (totpNumDigits <= 8));
224
225    this.sharedSecret                = sharedSecret;
226    this.totpIntervalDurationSeconds = totpIntervalDurationSeconds;
227    this.totpNumDigits               = totpNumDigits;
228  }
229
230
231
232  /**
233   * Creates a new SASL TOTP bind request with the provided information.
234   *
235   * @param  authenticationID             The authentication identity for the
236   *                                      bind request.  It must not be
237   *                                      {@code null}, and must be in the form
238   *                                      "u:" followed by a username, or "dn:"
239   *                                      followed by a DN.
240   * @param  authorizationID              The authorization identity for the
241   *                                      bind request.  It may be {@code null}
242   *                                      if the authorization identity should
243   *                                      be the same as the authentication
244   *                                      identity.  If an authorization
245   *                                      identity is specified, it must be in
246   *                                      the form "u:" followed by a username,
247   *                                      or "dn:" followed by a DN.  The value
248   *                                      "dn:" may indicate an authorization
249   *                                      identity of the anonymous user.
250   * @param  sharedSecret                 The shared secret key to use when
251   *                                      generating the TOTP password.
252   * @param  staticPassword               The static password for the target
253   *                                      user.  It may be {@code null} if only
254   *                                      the one-time password is to be used
255   *                                      for authentication (which may or may
256   *                                      not be allowed by the server).
257   * @param  totpIntervalDurationSeconds  The duration (in seconds) of the time
258   *                                      interval to use for TOTP processing.
259   *                                      It must be greater than zero.
260   * @param  totpNumDigits                The number of digits to include in the
261   *                                      generated TOTP password.  It must be
262   *                                      greater than or equal to six and less
263   *                                      than or equal to eight.
264   * @param  controls                     The set of controls to include in the
265   *                                      bind request.
266   */
267  public ReusableTOTPBindRequest(@NotNull final String authenticationID,
268                                 @Nullable final String authorizationID,
269                                 @NotNull final byte[] sharedSecret,
270                                 @Nullable final byte[] staticPassword,
271                                 final int totpIntervalDurationSeconds,
272                                 final int totpNumDigits,
273                                 @Nullable final Control... controls)
274  {
275    super(authenticationID, authorizationID, staticPassword, controls);
276
277    Validator.ensureTrue(totpIntervalDurationSeconds > 0);
278    Validator.ensureTrue((totpNumDigits >= 6) && (totpNumDigits <= 8));
279
280    this.sharedSecret                = sharedSecret;
281    this.totpIntervalDurationSeconds = totpIntervalDurationSeconds;
282    this.totpNumDigits               = totpNumDigits;
283  }
284
285
286
287  /**
288   * Creates a new SASL TOTP bind request with the provided information.
289   *
290   * @param  authenticationID             The authentication identity for the
291   *                                      bind request.  It must not be
292   *                                      {@code null}, and must be in the form
293   *                                      "u:" followed by a username, or "dn:"
294   *                                      followed by a DN.
295   * @param  authorizationID              The authorization identity for the
296   *                                      bind request.  It may be {@code null}
297   *                                      if the authorization identity should
298   *                                      be the same as the authentication
299   *                                      identity.  If an authorization
300   *                                      identity is specified, it must be in
301   *                                      the form "u:" followed by a username,
302   *                                      or "dn:" followed by a DN.  The value
303   *                                      "dn:" may indicate an authorization
304   *                                      identity of the anonymous user.
305   * @param  sharedSecret                 The shared secret key to use when
306   *                                      generating the TOTP password.
307   * @param  staticPassword               The static password for the target
308   *                                      user.  It may be {@code null} if only
309   *                                      the one-time password is to be used
310   *                                      for authentication (which may or may
311   *                                      not be allowed by the server).
312   * @param  totpIntervalDurationSeconds  The duration (in seconds) of the time
313   *                                      interval to use when generating the
314   *                                      TOTP password.  It must be greater
315   *                                      than zero.
316   * @param  totpNumDigits                The number of digits to include in the
317   *                                      generated TOTP password.  It must be
318   *                                      greater than or equal to six and less
319   *                                      than or equal to eight.
320   * @param  controls                     The set of controls to include in the
321   *                                      bind request.
322   */
323  private ReusableTOTPBindRequest(@NotNull final String authenticationID,
324               @Nullable final String authorizationID,
325               @NotNull final byte[] sharedSecret,
326               @Nullable final ASN1OctetString staticPassword,
327               final int totpIntervalDurationSeconds,
328               final int totpNumDigits,
329               @Nullable final Control... controls)
330  {
331    super(authenticationID, authorizationID, staticPassword, controls);
332
333    this.sharedSecret                = sharedSecret;
334    this.totpIntervalDurationSeconds = totpIntervalDurationSeconds;
335    this.totpNumDigits               = totpNumDigits;
336  }
337
338
339
340  /**
341   * Retrieves the shared secret key to use when generating the TOTP password.
342   *
343   * @return  The shared secret key to use when generating the TOTP password.
344   */
345  @NotNull()
346  public byte[] getSharedSecret()
347  {
348    return sharedSecret;
349  }
350
351
352
353  /**
354   * Retrieves the duration (in seconds) of the time interval to use when
355   * generating the TOTP password.
356   *
357   * @return  The duration (in seconds) of the time interval to use when
358   *          generating the TOTP password.
359   */
360  public int getTOTPIntervalDurationSeconds()
361  {
362    return totpIntervalDurationSeconds;
363  }
364
365
366
367  /**
368   * Retrieves the number of digits to include in the generated TOTP password.
369   *
370   * @return  The number of digits to include in the generated TOTP password.
371   */
372  public int getTOTPNumDigits()
373  {
374    return totpNumDigits;
375  }
376
377
378
379  /**
380   * {@inheritDoc}
381   */
382  @Override()
383  @NotNull()
384  protected ASN1OctetString getSASLCredentials()
385            throws LDAPException
386  {
387    // Generate the TOTP password.
388    final String totpPassword = OneTimePassword.totp(sharedSecret,
389         System.currentTimeMillis(), totpIntervalDurationSeconds,
390         totpNumDigits);
391
392    return encodeCredentials(getAuthenticationID(), getAuthorizationID(),
393         totpPassword, getStaticPassword());
394  }
395
396
397
398  /**
399   * {@inheritDoc}
400   */
401  @Override()
402  @NotNull()
403  public ReusableTOTPBindRequest getRebindRequest(@NotNull final String host,
404                                                  final int port)
405  {
406    return duplicate();
407  }
408
409
410
411  /**
412   * {@inheritDoc}
413   */
414  @Override()
415  @NotNull()
416  public ReusableTOTPBindRequest duplicate()
417  {
418    return duplicate(getControls());
419  }
420
421
422
423  /**
424   * {@inheritDoc}
425   */
426  @Override()
427  @NotNull()
428  public ReusableTOTPBindRequest duplicate(@Nullable final Control[] controls)
429  {
430    final ReusableTOTPBindRequest bindRequest =
431         new ReusableTOTPBindRequest(getAuthenticationID(),
432              getAuthorizationID(), sharedSecret, getStaticPassword(),
433              totpIntervalDurationSeconds, totpNumDigits, controls);
434    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
435    bindRequest.setIntermediateResponseListener(
436         getIntermediateResponseListener());
437    bindRequest.setReferralDepth(getReferralDepth());
438    bindRequest.setReferralConnector(getReferralConnectorInternal());
439    return bindRequest;
440  }
441
442
443
444  /**
445   * {@inheritDoc}
446   */
447  @Override()
448  public void toCode(@NotNull final List<String> lineList,
449                     @NotNull final String requestID,
450                     final int indentSpaces, final boolean includeProcessing)
451  {
452    // Create the request variable.
453    final ArrayList<ToCodeArgHelper> constructorArgs = new ArrayList<>(7);
454    constructorArgs.add(ToCodeArgHelper.createString(getAuthenticationID(),
455         "Authentication ID"));
456    constructorArgs.add(ToCodeArgHelper.createString(getAuthorizationID(),
457         "Authorization ID"));
458    constructorArgs.add(ToCodeArgHelper.createByteArray(
459         "---redacted-secret---".getBytes(StandardCharsets.UTF_8), true,
460         "Shared Secret"));
461    constructorArgs.add(ToCodeArgHelper.createString(
462         ((getStaticPassword() == null) ? "null" : "---redacted-password---"),
463         "Static Password"));
464    constructorArgs.add(ToCodeArgHelper.createInteger(
465         totpIntervalDurationSeconds, "Interval Duration (seconds)"));
466    constructorArgs.add(ToCodeArgHelper.createInteger(totpNumDigits,
467         "Number of TOTP Digits"));
468
469    final Control[] controls = getControls();
470    if (controls.length > 0)
471    {
472      constructorArgs.add(ToCodeArgHelper.createControlArray(controls,
473           "Bind Controls"));
474    }
475
476    ToCodeHelper.generateMethodCall(lineList, indentSpaces,
477         "ReusableTOTPBindRequest", requestID + "Request",
478         "new ReusableTOTPBindRequest", constructorArgs);
479
480
481    // Add lines for processing the request and obtaining the result.
482    if (includeProcessing)
483    {
484      // Generate a string with the appropriate indent.
485      final StringBuilder buffer = new StringBuilder();
486      for (int i=0; i < indentSpaces; i++)
487      {
488        buffer.append(' ');
489      }
490      final String indent = buffer.toString();
491
492      lineList.add("");
493      lineList.add(indent + "try");
494      lineList.add(indent + '{');
495      lineList.add(indent + "  BindResult " + requestID +
496           "Result = connection.bind(" + requestID + "Request);");
497      lineList.add(indent + "  // The bind was processed successfully.");
498      lineList.add(indent + '}');
499      lineList.add(indent + "catch (LDAPException e)");
500      lineList.add(indent + '{');
501      lineList.add(indent + "  // The bind failed.  Maybe the following will " +
502           "help explain why.");
503      lineList.add(indent + "  // Note that the connection is now likely in " +
504           "an unauthenticated state.");
505      lineList.add(indent + "  ResultCode resultCode = e.getResultCode();");
506      lineList.add(indent + "  String message = e.getMessage();");
507      lineList.add(indent + "  String matchedDN = e.getMatchedDN();");
508      lineList.add(indent + "  String[] referralURLs = e.getReferralURLs();");
509      lineList.add(indent + "  Control[] responseControls = " +
510           "e.getResponseControls();");
511      lineList.add(indent + '}');
512    }
513  }
514}