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