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