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