001/*
002 * Copyright 2016-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-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) 2016-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.extensions;
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.Control;
046import com.unboundid.ldap.sdk.ExtendedRequest;
047import com.unboundid.ldap.sdk.LDAPConnection;
048import com.unboundid.ldap.sdk.LDAPException;
049import com.unboundid.ldap.sdk.ResultCode;
050import com.unboundid.util.Debug;
051import com.unboundid.util.NotMutable;
052import com.unboundid.util.StaticUtils;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.Validator;
056
057import static com.unboundid.ldap.sdk.unboundidds.extensions.ExtOpMessages.*;
058
059
060
061/**
062 * This class provides an implementation of an extended request that may be used
063 * to generate a shared secret for use in generating TOTP authentication codes
064 * (as per <A HREF="http://www.ietf.org/rfc/rfc6238.txt">RFC 6238</A>, for
065 * example, using the mechanism provided in the
066 * {@link com.unboundid.ldap.sdk.unboundidds.OneTimePassword} class), which can
067 * be used to authenticate to the server via the
068 * {@link com.unboundid.ldap.sdk.unboundidds.UnboundIDTOTPBindRequest}.
069 * <BR>
070 * <BLOCKQUOTE>
071 *   <B>NOTE:</B>  This class, and other classes within the
072 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
073 *   supported for use against Ping Identity, UnboundID, and
074 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
075 *   for proprietary functionality or for external specifications that are not
076 *   considered stable or mature enough to be guaranteed to work in an
077 *   interoperable way with other types of LDAP servers.
078 * </BLOCKQUOTE>
079 * <BR>
080 * This request may be invoked in one of following ways:
081 * <BR><BR>
082 * <UL>
083 *   <LI>
084 *     With a {@code null} authentication identity and a non-{@code null} static
085 *     password.  In this case, the authorization identity for the operation
086 *     (typically the user as whom the underlying connection is authenticated,
087 *     but possibly a different user if the request also includes a control like
088 *     the proxied authorization or intermediate client request control that
089 *     specifies and alternate authorization identity, or if the client
090 *     authenticated with a SASL mechanism that included an alternate
091 *     authorization identity) will be used as the authentication identity for
092 *     this request, and the static password must be valid for that user.  This
093 *     will be treated as a user requesting a TOTP shared secret for their own
094 *     account.
095 *   </LI>
096 *   <LI>
097 *     With a non-{@code null} authentication identity (which may or may not
098 *     match the authorization identity for the operation) and a
099 *     non-{@code null} static password that is valid for the provided
100 *     authentication identity.  This will also be treated as a user requesting
101 *     a TOTP shared secret for their own account.
102 *   </LI>
103 *   <LI>
104 *     With a non-{@code null} authentication identity and a {@code null} static
105 *     password.  In this case, the authentication identity must not match the
106 *     authorization identity for the operation, and the authorization identity
107 *     must have the password-reset privilege.  This will be treated as an
108 *     administrator requesting a TOTP shared secret on behalf of a user and is
109 *     recommended only for the case in which the identity of the user has been
110 *     verified through some means other than a static password.
111 *   </LI>
112 * </UL>
113 * <BR><BR>
114 * If the request is processed successfully, the server will generate a TOTP
115 * shared secret for the user, will store it in the user's entry, and will
116 * return that secret back to the client via the
117 * {@link GenerateTOTPSharedSecretExtendedResult}.
118 * <BR><BR>
119 * Note that this operation will not interfere with any other TOTP shared
120 * secrets that may already exist in the user's entry; the new shared secret
121 * will be merged with any existing shared secret values for the user.  If a
122 * TOTP shared secret is no longer needed, the
123 * {@link RevokeTOTPSharedSecretExtendedRequest} may be used to remove it from
124 * the user's account.
125 * <BR><BR>
126 * This extended request has an OID of 1.3.6.1.4.1.30221.2.6.56, and it must
127 * include a request value with the following encoding:
128 * <BR><BR>
129 * <PRE>
130 *   GenerateTOTPSharedSecretRequest ::= SEQUENCE {
131 *        authenticationID     [0] OCTET STRING OPTIONAL,
132 *        staticPassword       [1] OCTET STRING OPTIONAL,
133 *        ... }
134 * </PRE>
135 *
136 */
137@NotMutable()
138@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
139public final class GenerateTOTPSharedSecretExtendedRequest
140       extends ExtendedRequest
141{
142  /**
143   * The OID (1.3.6.1.4.1.30221.2.6.56) for the generate TOTP shared secret
144   * extended request.
145   */
146  public static final String GENERATE_TOTP_SHARED_SECRET_REQUEST_OID =
147       "1.3.6.1.4.1.30221.2.6.56";
148
149
150
151  /**
152   * The BER type for the authentication ID element of the request value
153   * sequence.
154   */
155  private static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
156
157
158
159  /**
160   * The BER type for the static password element of the request value sequence.
161   */
162  private static final byte TYPE_STATIC_PASSWORD = (byte) 0x81;
163
164
165
166  /**
167   * The serial version UID for this serializable class.
168   */
169  private static final long serialVersionUID = -1617090986047944957L;
170
171
172
173  // The static password for the request.
174  private final ASN1OctetString staticPassword;
175
176  // The authentication ID for the request.
177  private final String authenticationID;
178
179
180
181  /**
182   * Creates a new generate TOTP shared secret extended request with the
183   * provided information.
184   *
185   * @param  authenticationID  The authentication ID to use to identify the user
186   *                           for whom to generate the TOTP shared secret.  It
187   *                           should be a string in the form "dn:" followed by
188   *                           the DN of the target user, or "u:" followed by
189   *                           the username.  It may be {@code null} if the TOTP
190   *                           shared secret is to be generated for the
191   *                           authorization identity for the operation, and
192   *                           only if the {@code staticPassword} is
193   *                           non-{@code null}).
194   * @param  staticPassword    The static password of the user for whom to
195   *                           generate the TOTP shared secret.  It may be
196   *                           {@code null} only if the {@code authenticationID}
197   *                           is non-{@code null}, is different from the
198   *                           operation's authorization identity, and the
199   *                           operation's authorization identity has the
200   *                           password-reset privilege.
201   * @param  controls          The set of controls to include in the request.
202   *                           It may be {@code null} or empty if there should
203   *                           not be any request controls.
204   */
205  public GenerateTOTPSharedSecretExtendedRequest(final String authenticationID,
206                                                 final String staticPassword,
207                                                 final Control... controls)
208  {
209    this(authenticationID, encodePassword(staticPassword), controls);
210  }
211
212
213
214  /**
215   * Creates a new generate TOTP shared secret extended request with the
216   * provided information.
217   *
218   * @param  authenticationID  The authentication ID to use to identify the user
219   *                           for whom to generate the TOTP shared secret.  It
220   *                           should be a string in the form "dn:" followed by
221   *                           the DN of the target user, or "u:" followed by
222   *                           the username.  It may be {@code null} if the TOTP
223   *                           shared secret is to be generated for the
224   *                           authorization identity for the operation, and
225   *                           only if the {@code staticPassword} is
226   *                           non-{@code null}).
227   * @param  staticPassword    The static password of the user for whom to
228   *                           generate the TOTP shared secret.  It may be
229   *                           {@code null} only if the {@code authenticationID}
230   *                           is non-{@code null}, is different from the
231   *                           operation's authorization identity, and the
232   *                           operation's authorization identity has the
233   *                           password-reset privilege.
234   * @param  controls          The set of controls to include in the request.
235   *                           It may be {@code null} or empty if there should
236   *                           not be any request controls.
237   */
238  public GenerateTOTPSharedSecretExtendedRequest(final String authenticationID,
239                                                 final byte[] staticPassword,
240                                                 final Control... controls)
241  {
242    this(authenticationID, encodePassword(staticPassword), controls);
243  }
244
245
246
247  /**
248   * Creates a new generate TOTP shared secret extended request with the
249   * provided information.
250   *
251   * @param  authenticationID  The authentication ID to use to identify the user
252   *                           for whom to generate the TOTP shared secret.  It
253   *                           should be a string in the form "dn:" followed by
254   *                           the DN of the target user, or "u:" followed by
255   *                           the username.  It may be {@code null} if the TOTP
256   *                           shared secret is to be generated for the
257   *                           authorization identity for the operation, and
258   *                           only if the {@code staticPassword} is
259   *                           non-{@code null}).
260   * @param  staticPassword    The static password of the user for whom to
261   *                           generate the TOTP shared secret.  It may be
262   *                           {@code null} only if the {@code authenticationID}
263   *                           is non-{@code null}, is different from the
264   *                           operation's authorization identity, and the
265   *                           operation's authorization identity has the
266   *                           password-reset privilege.
267   * @param  controls          The set of controls to include in the request.
268   *                           It may be {@code null} or empty if there should
269   *                           not be any request controls.
270   */
271  public GenerateTOTPSharedSecretExtendedRequest(final String authenticationID,
272               final ASN1OctetString staticPassword, final Control... controls)
273  {
274    super(GENERATE_TOTP_SHARED_SECRET_REQUEST_OID,
275         encodeValue(authenticationID, staticPassword), controls);
276
277    this.authenticationID = authenticationID;
278    this.staticPassword   = staticPassword;
279  }
280
281
282
283  /**
284   * Creates a new generate TOTP shared secret extended request that is decoded
285   * from the provided generic extended request.
286   *
287   * @param  request  The generic extended request to decode as a generate TOTP
288   *                  shared secret request.
289   *
290   * @throws  LDAPException  If a problem is encountered while attempting to
291   *                         decode the provided request.
292   */
293  public GenerateTOTPSharedSecretExtendedRequest(final ExtendedRequest request)
294         throws LDAPException
295  {
296    super(request);
297
298    final ASN1OctetString value = request.getValue();
299    if (value == null)
300    {
301      throw new LDAPException(ResultCode.DECODING_ERROR,
302           ERR_GEN_TOTP_SECRET_REQUEST_NO_VALUE.get());
303    }
304
305    try
306    {
307      String authID = null;
308      ASN1OctetString staticPW = null;
309      for (final ASN1Element e :
310           ASN1Sequence.decodeAsSequence(value.getValue()).elements())
311      {
312        switch (e.getType())
313        {
314          case TYPE_AUTHENTICATION_ID:
315            authID = ASN1OctetString.decodeAsOctetString(e).stringValue();
316            break;
317          case TYPE_STATIC_PASSWORD:
318            staticPW = ASN1OctetString.decodeAsOctetString(e);
319            break;
320          default:
321            throw new LDAPException(ResultCode.DECODING_ERROR,
322                 ERR_GEN_TOTP_SECRET_REQUEST_UNRECOGNIZED_TYPE.get(
323                      StaticUtils.toHex(e.getType())));
324        }
325      }
326
327      if ((authID == null) && (staticPW == null))
328      {
329        throw new LDAPException(ResultCode.DECODING_ERROR,
330             ERR_GEN_TOTP_SECRET_REQUEST_NEITHER_AUTHN_ID_NOR_PW.get());
331      }
332
333      authenticationID = authID;
334      staticPassword   = staticPW;
335    }
336    catch (final LDAPException le)
337    {
338      Debug.debugException(le);
339      throw le;
340    }
341    catch (final Exception e)
342    {
343      Debug.debugException(e);
344      throw new LDAPException(ResultCode.DECODING_ERROR,
345           ERR_GEN_TOTP_SECRET_REQUEST_ERROR_DECODING_VALUE.get(
346                StaticUtils.getExceptionMessage(e)),
347           e);
348    }
349  }
350
351
352
353  /**
354   * Encodes the provided password as an ASN.1 octet string suitable for
355   * inclusion in the encoded request.
356   *
357   * @param  password  The password to be encoded.  It may be {@code null} if
358   *                   no password should be included.  If it is
359   *                   non-{@code null}, then it must be a string or a byte
360   *                   array.
361   *
362   * @return  The encoded password, or {@code null} if no password was given.
363   */
364  private static ASN1OctetString encodePassword(final Object password)
365  {
366    if (password == null)
367    {
368      return null;
369    }
370    else if (password instanceof byte[])
371    {
372      return new ASN1OctetString(TYPE_STATIC_PASSWORD, (byte[]) password);
373    }
374    else
375    {
376      return new ASN1OctetString(TYPE_STATIC_PASSWORD,
377           String.valueOf(password));
378    }
379  }
380
381
382
383  /**
384   * Encodes the provided information into an ASN.1 octet string suitable for
385   * use as the value of this extended request.
386   *
387   * @param  authenticationID  The authentication ID to use to identify the user
388   *                           for whom to generate the TOTP shared secret.  It
389   *                           should be a string in the form "dn:" followed by
390   *                           the DN of the target user, or "u:" followed by
391   *                           the username.  It may be {@code null} if the TOTP
392   *                           shared secret is to be generated for the
393   *                           authorization identity for the operation, and
394   *                           only if the {@code staticPassword} is
395   *                           non-{@code null}).
396   * @param  staticPassword    The static password of the user for whom to
397   *                           generate the TOTP shared secret.  It may be
398   *                           {@code null} only if the {@code authenticationID}
399   *                           is non-{@code null}, is different from the
400   *                           operation's authorization identity, and the
401   *                           operation's authorization identity has the
402   *                           password-reset privilege.
403   *
404   * @return  The ASN.1 octet string containing the encoded request value.
405   */
406  private static ASN1OctetString encodeValue(final String authenticationID,
407                                      final ASN1OctetString staticPassword)
408  {
409    if (authenticationID == null)
410    {
411      Validator.ensureTrue((staticPassword != null),
412           "If the authentication ID is null, the static password must be " +
413                "non-null.");
414    }
415
416    final ArrayList<ASN1Element> elements = new ArrayList<>(2);
417
418    if (authenticationID != null)
419    {
420      elements.add(
421           new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
422    }
423
424    if (staticPassword != null)
425    {
426      elements.add(staticPassword);
427    }
428
429    return new ASN1OctetString(new ASN1Sequence(elements).encode());
430  }
431
432
433
434  /**
435   * Retrieves the authentication ID that identifies the user for whom to
436   * generate the TOTP shared secret, if provided.
437   *
438   * @return  The authentication ID that identifies the target user, or
439   *          {@code null} if the shared secret is to be generated for the
440   *          authorization identity associated with the extended request.
441   */
442  public String getAuthenticationID()
443  {
444    return authenticationID;
445  }
446
447
448
449  /**
450   * Retrieves the string representation of the static password for the target
451   * user, if provided.
452   *
453   * @return  The string representation of the static password for the target
454   *          user, or {@code null} if no static password was provided.
455   */
456  public String getStaticPasswordString()
457  {
458    if (staticPassword == null)
459    {
460      return null;
461    }
462    else
463    {
464      return staticPassword.stringValue();
465    }
466  }
467
468
469
470  /**
471   * Retrieves the bytes that comprise the static password for the target user,
472   * if provided.
473   *
474   * @return  The bytes that comprise the static password for the target user,
475   *          or {@code null} if no static password was provided.
476   */
477  public byte[] getStaticPasswordBytes()
478  {
479    if (staticPassword == null)
480    {
481      return null;
482    }
483    else
484    {
485      return staticPassword.getValue();
486    }
487  }
488
489
490
491  /**
492   * {@inheritDoc}
493   */
494  @Override()
495  protected GenerateTOTPSharedSecretExtendedResult process(
496                 final LDAPConnection connection, final int depth)
497            throws LDAPException
498  {
499    return new GenerateTOTPSharedSecretExtendedResult(
500         super.process(connection, depth));
501  }
502
503
504
505  /**
506   * {@inheritDoc}
507   */
508  @Override()
509  public GenerateTOTPSharedSecretExtendedRequest duplicate()
510  {
511    return duplicate(getControls());
512  }
513
514
515
516  /**
517   * {@inheritDoc}
518   */
519  @Override()
520  public GenerateTOTPSharedSecretExtendedRequest duplicate(
521                                                      final Control[] controls)
522  {
523    final GenerateTOTPSharedSecretExtendedRequest r =
524         new GenerateTOTPSharedSecretExtendedRequest(authenticationID,
525              staticPassword, controls);
526    r.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
527    return r;
528  }
529
530
531
532  /**
533   * {@inheritDoc}
534   */
535  @Override()
536  public String getExtendedRequestName()
537  {
538    return INFO_GEN_TOTP_SECRET_REQUEST_NAME.get();
539  }
540
541
542
543  /**
544   * {@inheritDoc}
545   */
546  @Override()
547  public void toString(final StringBuilder buffer)
548  {
549    buffer.append("GenerateTOTPSharedSecretExtendedRequest(");
550
551    if (authenticationID != null)
552    {
553      buffer.append("authenticationID='");
554      buffer.append(authenticationID);
555      buffer.append("', ");
556    }
557
558    buffer.append("staticPasswordProvided=");
559    buffer.append(staticPassword != null);
560
561    final Control[] controls = getControls();
562    if (controls.length > 0)
563    {
564      buffer.append(", controls={");
565      for (int i=0; i < controls.length; i++)
566      {
567        if (i > 0)
568        {
569          buffer.append(", ");
570        }
571
572        buffer.append(controls[i]);
573      }
574      buffer.append('}');
575    }
576
577    buffer.append(')');
578  }
579}