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.util.ArrayList;
041
042import com.unboundid.asn1.ASN1Element;
043import com.unboundid.asn1.ASN1OctetString;
044import com.unboundid.asn1.ASN1Sequence;
045import com.unboundid.ldap.sdk.BindResult;
046import com.unboundid.ldap.sdk.Control;
047import com.unboundid.ldap.sdk.InternalSDKHelper;
048import com.unboundid.ldap.sdk.LDAPConnection;
049import com.unboundid.ldap.sdk.LDAPException;
050import com.unboundid.ldap.sdk.SASLBindRequest;
051import com.unboundid.util.NotExtensible;
052import com.unboundid.util.NotNull;
053import com.unboundid.util.Nullable;
054import com.unboundid.util.ThreadSafety;
055import com.unboundid.util.ThreadSafetyLevel;
056import com.unboundid.util.Validator;
057
058
059
060/**
061 * This class provides support for an UnboundID-proprietary SASL mechanism that
062 * uses the time-based one-time password mechanism (TOTP) as described in
063 * <A HREF="http://www.ietf.org/rfc/rfc6238.txt">RFC 6238</A>, optionally (based
064 * on the server configuration) in conjunction with a static password for a form
065 * of multifactor authentication.
066 * <BR>
067 * <BLOCKQUOTE>
068 *   <B>NOTE:</B>  This class, and other classes within the
069 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
070 *   supported for use against Ping Identity, UnboundID, and
071 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
072 *   for proprietary functionality or for external specifications that are not
073 *   considered stable or mature enough to be guaranteed to work in an
074 *   interoperable way with other types of LDAP servers.
075 * </BLOCKQUOTE>
076 * <BR>
077 * The name for this SASL mechanism is "UNBOUNDID-TOTP".  An UNBOUNDID-TOTP SASL
078 * bind request MUST include SASL credentials with the following ASN.1 encoding:
079 * <BR><BR>
080 * <PRE>
081 * UnboundIDTOTPCredentials ::= SEQUENCE {
082 *   authenticationID  [0] OCTET STRING,
083 *   authorizationID   [1] OCTET STRING OPTIONAL,
084 *   totpPassword      [2] OCTET STRING,
085 *   staticPassword    [3] OCTET STRING OPTIONAL }
086 * </PRE>
087 * <BR><BR>
088 * Note that this class is abstract, with two different concrete
089 * implementations:  the {@link SingleUseTOTPBindRequest} class may be used for
090 * cases in which the one-time password will be obtained from an external source
091 * (e.g., provided by the user, perhaps using the Google Authenticator
092 * application), and the {@link ReusableTOTPBindRequest} class may be used for
093 * cases in which the one-time password should be generated by the LDAP SDK
094 * itself.  Because the {@code SingleUseTOTPBindRequest} class contains a
095 * point-in-time password, it cannot be used for re-authentication (e.g., for
096 * use with a connection pool, following referrals, or with the auto-reconnect
097 * feature).  If TOTP authentication should be used in contexts where one or
098 * more of these may be needed, then the dynamic variant should be used.
099 */
100@NotExtensible()
101@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
102public abstract class UnboundIDTOTPBindRequest
103       extends SASLBindRequest
104{
105  /**
106   * The name for the UnboundID TOTP SASL mechanism.
107   */
108  @NotNull public static final String UNBOUNDID_TOTP_MECHANISM_NAME =
109       "UNBOUNDID-TOTP";
110
111
112
113  /**
114   * The BER type for the authentication ID included in the request.
115   */
116  static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
117
118
119
120  /**
121   * The BER type for the authorization ID included in the request.
122   */
123  static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81;
124
125
126
127  /**
128   * The BER type for the TOTP password included in the request.
129   */
130  static final byte TYPE_TOTP_PASSWORD = (byte) 0x82;
131
132
133
134  /**
135   * The BER type for the static password included in the request.
136   */
137  static final byte TYPE_STATIC_PASSWORD = (byte) 0x83;
138
139
140
141  /**
142   * The serial version UID for this serializable class.
143   */
144  private static final long serialVersionUID = -8751931123826994145L;
145
146
147
148  // The static password for the target user, if provided.
149  @Nullable private final ASN1OctetString staticPassword;
150
151  // The message ID from the last LDAP message sent from this request.
152  private volatile int messageID = -1;
153
154  // The authentication identity for the bind.
155  @NotNull private final String authenticationID;
156
157  // The authorization identity for the bind, if provided.
158  @Nullable private final String authorizationID;
159
160
161
162  /**
163   * Creates a new TOTP bind request with the provided information.
164   *
165   * @param  authenticationID  The authentication identity for the bind request.
166   *                           It must not be {@code null}, and must be in the
167   *                           form "u:" followed by a username, or "dn:"
168   *                           followed by a DN.
169   * @param  authorizationID   The authorization identity for the bind request.
170   *                           It may be {@code null} if the authorization
171   *                           identity should be the same as the authentication
172   *                           identity.  If an authorization identity is
173   *                           specified, it must be in the form "u:" followed
174   *                           by a username, or "dn:" followed by a DN.  The
175   *                           value "dn:" may indicate an authorization
176   *                           identity of the anonymous user.
177   * @param  staticPassword    The static password for the target user.  It may
178   *                           be {@code null} if only the one-time password is
179   *                           to be used for authentication (which may or may
180   *                           not be allowed by the server).
181   * @param  controls          The set of controls to include in the bind
182   *                           request.
183   */
184  protected UnboundIDTOTPBindRequest(@NotNull final String authenticationID,
185                                     @Nullable final String authorizationID,
186                                     @Nullable final String staticPassword,
187                                     @Nullable final Control... controls)
188  {
189    super(controls);
190
191    Validator.ensureNotNull(authenticationID);
192
193    this.authenticationID = authenticationID;
194    this.authorizationID  = authorizationID;
195
196    if (staticPassword == null)
197    {
198      this.staticPassword = null;
199    }
200    else
201    {
202      this.staticPassword =
203           new ASN1OctetString(TYPE_STATIC_PASSWORD, staticPassword);
204    }
205  }
206
207
208
209  /**
210   * Creates a new TOTP bind request with the provided information.
211   *
212   * @param  authenticationID  The authentication identity for the bind request.
213   *                           It must not be {@code null}, and must be in the
214   *                           form "u:" followed by a username, or "dn:"
215   *                           followed by a DN.
216   * @param  authorizationID   The authorization identity for the bind request.
217   *                           It may be {@code null} if the authorization
218   *                           identity should be the same as the authentication
219   *                           identity.  If an authorization identity is
220   *                           specified, it must be in the form "u:" followed
221   *                           by a username, or "dn:" followed by a DN.  The
222   *                           value "dn:" may indicate an authorization
223   *                           identity of the anonymous user.
224   * @param  staticPassword    The static password for the target user.  It may
225   *                           be {@code null} if only the one-time password is
226   *                           to be used for authentication (which may or may
227   *                           not be allowed by the server).
228   * @param  controls          The set of controls to include in the bind
229   *                           request.
230   */
231  protected UnboundIDTOTPBindRequest(@NotNull final String authenticationID,
232                                     @Nullable final String authorizationID,
233                                     @Nullable final byte[] staticPassword,
234                                     @Nullable final Control... controls)
235  {
236    super(controls);
237
238    Validator.ensureNotNull(authenticationID);
239
240    this.authenticationID = authenticationID;
241    this.authorizationID  = authorizationID;
242
243    if (staticPassword == null)
244    {
245      this.staticPassword = null;
246    }
247    else
248    {
249      this.staticPassword =
250           new ASN1OctetString(TYPE_STATIC_PASSWORD, staticPassword);
251    }
252  }
253
254
255
256  /**
257   * Creates a new TOTP bind request with the provided information.
258   *
259   * @param  authenticationID  The authentication identity for the bind request.
260   *                           It must not be {@code null}, and must be in the
261   *                           form "u:" followed by a username, or "dn:"
262   *                           followed by a DN.
263   * @param  authorizationID   The authorization identity for the bind request.
264   *                           It may be {@code null} if the authorization
265   *                           identity should be the same as the authentication
266   *                           identity.  If an authorization identity is
267   *                           specified, it must be in the form "u:" followed
268   *                           by a username, or "dn:" followed by a DN.  The
269   *                           value "dn:" may indicate an authorization
270   *                           identity of the anonymous user.
271   * @param  staticPassword    The static password for the target user.  It may
272   *                           be {@code null} if only the one-time password is
273   *                           to be used for authentication (which may or may
274   *                           not be allowed by the server).  If it is
275   *                           non-{@code null}, then it must have the
276   *                           appropriate BER type.
277   * @param  controls          The set of controls to include in the bind
278   *                           request.
279   */
280  protected UnboundIDTOTPBindRequest(@NotNull final String authenticationID,
281                 @Nullable final String authorizationID,
282                 @Nullable final ASN1OctetString staticPassword,
283                 @Nullable final Control... controls)
284  {
285    super(controls);
286
287    Validator.ensureNotNull(authenticationID);
288
289    if (staticPassword != null)
290    {
291      Validator.ensureTrue(staticPassword.getType() == TYPE_STATIC_PASSWORD);
292    }
293
294    this.authenticationID = authenticationID;
295    this.authorizationID  = authorizationID;
296    this.staticPassword   = staticPassword;
297  }
298
299
300
301  /**
302   * Retrieves the authentication ID for the bind request.
303   *
304   * @return  The authentication ID for the bind request.
305   */
306  @NotNull()
307  public final String getAuthenticationID()
308  {
309    return authenticationID;
310  }
311
312
313
314  /**
315   * Retrieves the authorization ID for the bind request, if one was provided.
316   *
317   * @return  The authorization ID for the bind request, or {@code null} if the
318   *          authorization ID should be the same as the authentication ID.
319   */
320  @Nullable()
321  public final String getAuthorizationID()
322  {
323    return authorizationID;
324  }
325
326
327
328  /**
329   * Retrieves the static password for the bind request, if one was provided.
330   *
331   * @return  The static password for the bind request, or {@code null} if no
332   *          static password was provided and only the one-time password should
333   *          be used for authentication.
334   */
335  @Nullable()
336  public final ASN1OctetString getStaticPassword()
337  {
338    return staticPassword;
339  }
340
341
342
343  /**
344   * {@inheritDoc}
345   */
346  @Override()
347  @NotNull()
348  public final String getSASLMechanismName()
349  {
350    return UNBOUNDID_TOTP_MECHANISM_NAME;
351  }
352
353
354
355  /**
356   * {@inheritDoc}
357   */
358  @Override()
359  @NotNull()
360  protected final BindResult process(@NotNull final LDAPConnection connection,
361                                     final int depth)
362            throws LDAPException
363  {
364    setReferralDepth(depth);
365
366    messageID = InternalSDKHelper.nextMessageID(connection);
367    return sendBindRequest(connection, "", getSASLCredentials(), getControls(),
368         getResponseTimeoutMillis(connection));
369  }
370
371
372
373  /**
374   * Retrieves the encoded SASL credentials that may be included in an
375   * UNBOUNDID-TOTP SASL bind request.
376   *
377   * @return  The encoded SASL credentials that may be included in an
378   *          UNBOUNDID-TOTP SASL bind request.
379   *
380   * @throws  LDAPException  If a problem is encountered while attempting to
381   *                         obtain the encoded credentials.
382   */
383  @NotNull()
384  protected abstract ASN1OctetString getSASLCredentials()
385            throws LDAPException;
386
387
388
389  /**
390   * Encodes the provided information in a form suitable for inclusion in an
391   * UNBOUNDID-TOTP SASL bind request.
392   *
393   * @param  authenticationID  The authentication identity for the bind request.
394   *                           It must not be {@code null}, and must be in the
395   *                           form "u:" followed by a username, or "dn:"
396   *                           followed by a DN.
397   * @param  authorizationID   The authorization identity for the bind request.
398   *                           It may be {@code null} if the authorization
399   *                           identity should be the same as the authentication
400   *                           identity.  If an authorization identity is
401   *                           specified, it must be in the form "u:" followed
402   *                           by a username, or "dn:" followed by a DN.  The
403   *                           value "dn:" may indicate an authorization
404   *                           identity of the anonymous user.
405   * @param  totpPassword      The TOTP password to include in the bind request.
406   *                           It must not be {@code null}.
407   * @param  staticPassword    The static password for the target user.  It may
408   *                           be {@code null} if only the one-time password is
409   *                           to be used for authentication (which may or may
410   *                           not be allowed by the server).
411   *
412   * @return  The encoded SASL credentials.
413   */
414  @NotNull()
415  public static ASN1OctetString encodeCredentials(
416                     @NotNull final String authenticationID,
417                     @Nullable final String authorizationID,
418                     @NotNull final String totpPassword,
419                     @Nullable final ASN1OctetString staticPassword)
420  {
421    Validator.ensureNotNull(authenticationID);
422    Validator.ensureNotNull(totpPassword);
423
424    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
425    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
426
427    if (authorizationID != null)
428    {
429      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
430    }
431
432    elements.add(new ASN1OctetString(TYPE_TOTP_PASSWORD, totpPassword));
433
434    if (staticPassword != null)
435    {
436      if (staticPassword.getType() == TYPE_STATIC_PASSWORD)
437      {
438        elements.add(staticPassword);
439      }
440      else
441      {
442        elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD,
443             staticPassword.getValue()));
444      }
445    }
446
447    return new ASN1OctetString(new ASN1Sequence(elements).encode());
448  }
449
450
451
452  /**
453   * {@inheritDoc}
454   */
455  @Override()
456  public final int getLastMessageID()
457  {
458    return messageID;
459  }
460
461
462
463  /**
464   * {@inheritDoc}
465   */
466  @Override()
467  public final void toString(@NotNull final StringBuilder buffer)
468  {
469    buffer.append("UnboundIDTOTPBindRequest(authID='");
470    buffer.append(authenticationID);
471    buffer.append("', ");
472
473    if (authorizationID != null)
474    {
475      buffer.append("authzID='");
476      buffer.append(authorizationID);
477      buffer.append("', ");
478    }
479
480    buffer.append("includesStaticPassword=");
481    buffer.append(staticPassword != null);
482
483
484    final Control[] controls = getControls();
485    if (controls.length > 0)
486    {
487      buffer.append(", controls={");
488      for (int i=0; i < controls.length; i++)
489      {
490        if (i > 0)
491        {
492          buffer.append(", ");
493        }
494
495        buffer.append(controls[i]);
496      }
497      buffer.append('}');
498    }
499
500    buffer.append(')');
501  }
502}