001/*
002 * Copyright 2016-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2016-2018 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.ResultCode;
036import com.unboundid.ldap.sdk.SASLBindRequest;
037import com.unboundid.ldap.sdk.unboundidds.extensions.
038            DeregisterYubiKeyOTPDeviceExtendedRequest;
039import com.unboundid.ldap.sdk.unboundidds.extensions.
040            RegisterYubiKeyOTPDeviceExtendedRequest;
041import com.unboundid.util.Debug;
042import com.unboundid.util.NotMutable;
043import com.unboundid.util.StaticUtils;
044import com.unboundid.util.ThreadSafety;
045import com.unboundid.util.ThreadSafetyLevel;
046import com.unboundid.util.Validator;
047
048import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
049
050
051
052/**
053 * This class provides an implementation of a SASL bind request that may be used
054 * to authenticate to a Directory Server using the UNBOUNDID-YUBIKEY-OTP
055 * mechanism.  The credentials include at least an authentication ID and a
056 * one-time password generated by a YubiKey device.  The request may also
057 * include a static password (which may or may not be required by the server)
058 * and an optional authorization ID.
059 * <BR>
060 * <BLOCKQUOTE>
061 *   <B>NOTE:</B>  This class, and other classes within the
062 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
063 *   supported for use against Ping Identity, UnboundID, and
064 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
065 *   for proprietary functionality or for external specifications that are not
066 *   considered stable or mature enough to be guaranteed to work in an
067 *   interoperable way with other types of LDAP servers.
068 * </BLOCKQUOTE>
069 * <BR>
070 * The UNBOUNDID-YUBIKEY-OTP bind request MUST include SASL credentials with the
071 * following ASN.1 encoding:
072 * <BR><BR>
073 * <PRE>
074 *   UnboundIDYubiKeyCredentials ::= SEQUENCE {
075 *        authenticationID     [0] OCTET STRING,
076 *        authorizationID      [1] OCTET STRING OPTIONAL,
077 *        staticPassword       [2] OCTET STRING OPTIONAL,
078 *        yubiKeyOTP           [3] OCTET STRING,
079 *        ... }
080 * </PRE>
081 *
082 *
083 * @see  RegisterYubiKeyOTPDeviceExtendedRequest
084 * @see  DeregisterYubiKeyOTPDeviceExtendedRequest
085 */
086@NotMutable()
087@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
088public final class UnboundIDYubiKeyOTPBindRequest
089       extends SASLBindRequest
090{
091  /**
092   * The name for the UnboundID YubiKey SASL mechanism.
093   */
094  public static final String UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME =
095       "UNBOUNDID-YUBIKEY-OTP";
096
097
098
099  /**
100   * The BER type for the authentication ID element of the credentials sequence.
101   */
102  private static final byte TYPE_AUTHENTICATION_ID = (byte) 0x80;
103
104
105
106  /**
107   * The BER type for the authorization ID element of the credentials sequence.
108   */
109  private static final byte TYPE_AUTHORIZATION_ID = (byte) 0x81;
110
111
112
113  /**
114   * The BER type for the static password element of the credentials sequence.
115   */
116  private static final byte TYPE_STATIC_PASSWORD = (byte) 0x82;
117
118
119
120  /**
121   * The BER type for the YubiKey OTP element of the credentials sequence.
122   */
123  private static final byte TYPE_YUBIKEY_OTP = (byte) 0x83;
124
125
126
127  /**
128   * The serial version UID for this serializable class.
129   */
130  private static final long serialVersionUID = -6124016046606933247L;
131
132
133
134  // The static password for the user, if provided.
135  private final ASN1OctetString staticPassword;
136
137  // The message ID from the last LDAP message sent from this request.
138  private volatile int messageID = -1;
139
140  // The authentication ID for the user.
141  private final String authenticationID;
142
143  // The authorization ID for the bind request, if provided.
144  private final String authorizationID;
145
146  // The one-time password generated by a YubiKey device.
147  private final String yubiKeyOTP;
148
149
150
151  /**
152   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
153   * information.
154   *
155   * @param  authenticationID  The authentication ID for the bind request.  It
156   *                           must not be {@code null}, and must have the form
157   *                           "dn:" followed by the DN of the target user or
158   *                           "u:" followed by the the username of the target
159   *                           user.
160   * @param  authorizationID   The authorization ID for the bind request.  It
161   *                           may be {@code null} if the authorization identity
162   *                           should be the same as the authentication
163   *                           identity.
164   * @param  staticPassword    The static password for the user specified as the
165   *                           authentication identity.  It may be {@code null}
166   *                           if authentication should be performed using only
167   *                           the YubiKey OTP.
168   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
169   *                           device.  It must not be {@code null}.
170   * @param  controls          The set of controls to include in the bind
171   *                           request.  It may be {@code null} or empty if
172   *                           there should not be any request controls.
173   */
174  public UnboundIDYubiKeyOTPBindRequest(final String authenticationID,
175                                        final String authorizationID,
176                                        final String staticPassword,
177                                        final String yubiKeyOTP,
178                                        final Control... controls)
179  {
180    this(authenticationID, authorizationID, toASN1OctetString(staticPassword),
181         yubiKeyOTP, controls);
182  }
183
184
185
186  /**
187   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
188   * information.
189   *
190   * @param  authenticationID  The authentication ID for the bind request.  It
191   *                           must not be {@code null}, and must have the form
192   *                           "dn:" followed by the DN of the target user or
193   *                           "u:" followed by the the username of the target
194   *                           user.
195   * @param  authorizationID   The authorization ID for the bind request.  It
196   *                           may be {@code null} if the authorization identity
197   *                           should be the same as the authentication
198   *                           identity.
199   * @param  staticPassword    The static password for the user specified as the
200   *                           authentication identity.  It may be {@code null}
201   *                           if authentication should be performed using only
202   *                           the YubiKey OTP.
203   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
204   *                           device.  It must not be {@code null}.
205   * @param  controls          The set of controls to include in the bind
206   *                           request.  It may be {@code null} or empty if
207   *                           there should not be any request controls.
208   */
209  public UnboundIDYubiKeyOTPBindRequest(final String authenticationID,
210                                        final String authorizationID,
211                                        final byte[] staticPassword,
212                                        final String yubiKeyOTP,
213                                        final Control... controls)
214  {
215    this(authenticationID, authorizationID, toASN1OctetString(staticPassword),
216         yubiKeyOTP, controls);
217  }
218
219
220
221  /**
222   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
223   * information.
224   *
225   * @param  authenticationID  The authentication ID for the bind request.  It
226   *                           must not be {@code null}, and must have the form
227   *                           "dn:" followed by the DN of the target user or
228   *                           "u:" followed by the the username of the target
229   *                           user.
230   * @param  authorizationID   The authorization ID for the bind request.  It
231   *                           may be {@code null} if the authorization identity
232   *                           should be the same as the authentication
233   *                           identity.
234   * @param  staticPassword    The static password for the user specified as the
235   *                           authentication identity.  It may be {@code null}
236   *                           if authentication should be performed using only
237   *                           the YubiKey OTP.
238   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
239   *                           device.  It must not be {@code null}.
240   * @param  controls          The set of controls to include in the bind
241   *                           request.  It may be {@code null} or empty if
242   *                           there should not be any request controls.
243   */
244  private UnboundIDYubiKeyOTPBindRequest(final String authenticationID,
245                                         final String authorizationID,
246                                         final ASN1OctetString staticPassword,
247                                         final String yubiKeyOTP,
248                                         final Control... controls)
249  {
250    super(controls);
251
252    Validator.ensureNotNull(authenticationID);
253    Validator.ensureNotNull(yubiKeyOTP);
254
255    this.authenticationID = authenticationID;
256    this.authorizationID  = authorizationID;
257    this.staticPassword   = staticPassword;
258    this.yubiKeyOTP       = yubiKeyOTP;
259  }
260
261
262
263  /**
264   * Retrieves an ASN.1 octet string that represents the appropriate encoding
265   * for the provided password.
266   *
267   * @param  password  The password object to convert to an ASN.1 octet string.
268   *                   It may be {@code null} if no static password is required.
269   *                   Otherwise, it must either be a string or a byte array.
270   *
271   * @return  The ASN.1 octet string created from the provided password object,
272   *          or {@code null} if the provided password object was null.
273   */
274  private static ASN1OctetString toASN1OctetString(final Object password)
275  {
276    if (password == null)
277    {
278      return null;
279    }
280    else if (password instanceof byte[])
281    {
282      return new ASN1OctetString(TYPE_STATIC_PASSWORD, (byte[]) password);
283    }
284    else
285    {
286      return new ASN1OctetString(TYPE_STATIC_PASSWORD,
287           String.valueOf(password));
288    }
289  }
290
291
292
293  /**
294   * Creates a new UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the
295   * provided SASL credentials.
296   *
297   * @param  saslCredentials  The SASL credentials to decode in order to create
298   *                          the UNBOUNDID-YUBIKEY-OTP SASL bind request.  It
299   *                          must not be {@code null}.
300   * @param  controls         The set of controls to include in the bind
301   *                          request.  This may be {@code null} or empty if no
302   *                          controls should be included in the request.
303   *
304   * @return  The UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the
305   *          provided credentials.
306   *
307   * @throws  LDAPException  If the provided credentials cannot be decoded to a
308   *                         valid UNBOUNDID-YUBIKEY-OTP bind request.
309   */
310  public static UnboundIDYubiKeyOTPBindRequest decodeCredentials(
311                     final ASN1OctetString saslCredentials,
312                     final Control... controls)
313         throws LDAPException
314  {
315    try
316    {
317      ASN1OctetString staticPassword = null;
318      String authenticationID = null;
319      String authorizationID  = null;
320      String yubiKeyOTP = null;
321
322      for (final ASN1Element e :
323           ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements())
324      {
325        switch (e.getType())
326        {
327          case TYPE_AUTHENTICATION_ID:
328            authenticationID =
329                 ASN1OctetString.decodeAsOctetString(e).stringValue();
330            break;
331          case TYPE_AUTHORIZATION_ID:
332            authorizationID =
333                 ASN1OctetString.decodeAsOctetString(e).stringValue();
334            break;
335          case TYPE_STATIC_PASSWORD:
336            staticPassword = ASN1OctetString.decodeAsOctetString(e);
337            break;
338          case TYPE_YUBIKEY_OTP:
339            yubiKeyOTP = ASN1OctetString.decodeAsOctetString(e).stringValue();
340            break;
341          default:
342            throw new LDAPException(ResultCode.DECODING_ERROR,
343                 ERR_YUBIKEY_OTP_DECODE_UNRECOGNIZED_CRED_ELEMENT.get(
344                      UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
345                      StaticUtils.toHex(e.getType())));
346        }
347      }
348
349      if (authenticationID == null)
350      {
351        throw new LDAPException(ResultCode.DECODING_ERROR,
352             ERR_YUBIKEY_OTP_DECODE_NO_AUTH_ID.get(
353                  UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
354      }
355
356      if (yubiKeyOTP == null)
357      {
358        throw new LDAPException(ResultCode.DECODING_ERROR,
359             ERR_YUBIKEY_OTP_NO_OTP.get(UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
360      }
361
362      return new UnboundIDYubiKeyOTPBindRequest(authenticationID,
363           authorizationID, staticPassword, yubiKeyOTP, controls);
364    }
365    catch (final LDAPException le)
366    {
367      Debug.debugException(le);
368      throw le;
369    }
370    catch (final Exception e)
371    {
372      Debug.debugException(e);
373      throw new LDAPException(ResultCode.DECODING_ERROR,
374           ERR_YUBIKEY_OTP_DECODE_ERROR.get(
375                UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
376                StaticUtils.getExceptionMessage(e)),
377           e);
378    }
379  }
380
381
382
383  /**
384   * Retrieves the authentication ID for the bind request.
385   *
386   * @return  The authentication ID for the bind request.
387   */
388  public String getAuthenticationID()
389  {
390    return authenticationID;
391  }
392
393
394
395  /**
396   * Retrieves the authorization ID for the bind request, if any.
397   *
398   * @return  The authorization ID for the bind request, or {@code null} if the
399   *          authorization identity should match the authentication identity.
400   */
401  public String getAuthorizationID()
402  {
403    return authorizationID;
404  }
405
406
407
408  /**
409   * Retrieves the string representation of the static password for the bind
410   * request, if any.
411   *
412   * @return  The string representation of the static password for the bind
413   *          request, or {@code null} if there is no static password.
414   */
415  public String getStaticPasswordString()
416  {
417    if (staticPassword == null)
418    {
419      return null;
420    }
421    else
422    {
423      return staticPassword.stringValue();
424    }
425  }
426
427
428
429  /**
430   * Retrieves the bytes that comprise the static password for the bind request,
431   * if any.
432   *
433   * @return  The bytes that comprise the static password for the bind request,
434   *          or {@code null} if there is no static password.
435   */
436  public byte[] getStaticPasswordBytes()
437  {
438    if (staticPassword == null)
439    {
440      return null;
441    }
442    else
443    {
444      return staticPassword.getValue();
445    }
446  }
447
448
449
450  /**
451   * Retrieves the YubiKey-generated one-time password to include in the bind
452   * request.
453   *
454   * @return  The YubiKey-generated one-time password to include in the bind
455   *          request.
456   */
457  public String getYubiKeyOTP()
458  {
459    return yubiKeyOTP;
460  }
461
462
463
464  /**
465   * Sends this bind request to the target server over the provided connection
466   * and returns the corresponding response.
467   *
468   * @param  connection  The connection to use to send this bind request to the
469   *                     server and read the associated response.
470   * @param  depth       The current referral depth for this request.  It should
471   *                     always be one for the initial request, and should only
472   *                     be incremented when following referrals.
473   *
474   * @return  The bind response read from the server.
475   *
476   * @throws  LDAPException  If a problem occurs while sending the request or
477   *                         reading the response.
478   */
479  @Override()
480  protected BindResult process(final LDAPConnection connection, final int depth)
481            throws LDAPException
482  {
483    messageID = InternalSDKHelper.nextMessageID(connection);
484    return sendBindRequest(connection, "", encodeCredentials(), getControls(),
485         getResponseTimeoutMillis(connection));
486  }
487
488
489
490  /**
491   * Retrieves an ASN.1 octet string containing the encoded credentials for this
492   * bind request.
493   *
494   * @return  An ASN.1 octet string containing the encoded credentials for this
495   *          bind request.
496   */
497  public ASN1OctetString encodeCredentials()
498  {
499    return encodeCredentials(authenticationID, authorizationID, staticPassword,
500         yubiKeyOTP);
501  }
502
503
504
505  /**
506   * Encodes the provided information into an ASN.1 octet string suitable for
507   * use as the SASL credentials for an UNBOUNDID-YUBIKEY-OTP bind request.
508   *
509   * @param  authenticationID  The authentication ID for the bind request.  It
510   *                           must not be {@code null}, and must have the form
511   *                           "dn:" followed by the DN of the target user or
512   *                           "u:" followed by the the username of the target
513   *                           user.
514   * @param  authorizationID   The authorization ID for the bind request.  It
515   *                           may be {@code null} if the authorization identity
516   *                           should be the same as the authentication
517   *                           identity.
518   * @param  staticPassword    The static password for the user specified as the
519   *                           authentication identity.  It may be {@code null}
520   *                           if authentication should be performed using only
521   *                           the YubiKey OTP.
522   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
523   *                           device.  It must not be {@code null}.
524   *
525   * @return  An ASN.1 octet string suitable for use as the SASL credentials for
526   *          an UNBOUNDID-YUBIKEY-OTP bind request.
527   */
528  public static ASN1OctetString encodeCredentials(final String authenticationID,
529                                     final String authorizationID,
530                                     final ASN1OctetString staticPassword,
531                                     final String yubiKeyOTP)
532  {
533    Validator.ensureNotNull(authenticationID);
534    Validator.ensureNotNull(yubiKeyOTP);
535
536    final ArrayList<ASN1Element> elements = new ArrayList<>(4);
537    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
538
539    if (authorizationID != null)
540    {
541      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
542    }
543
544    if (staticPassword != null)
545    {
546      elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD,
547           staticPassword.getValue()));
548    }
549
550    elements.add(new ASN1OctetString(TYPE_YUBIKEY_OTP, yubiKeyOTP));
551
552    return new ASN1OctetString(new ASN1Sequence(elements).encode());
553  }
554
555
556
557  /**
558   * {@inheritDoc}
559   */
560  @Override()
561  public UnboundIDYubiKeyOTPBindRequest duplicate()
562  {
563    return duplicate(getControls());
564  }
565
566
567
568  /**
569   * {@inheritDoc}
570   */
571  @Override()
572  public UnboundIDYubiKeyOTPBindRequest duplicate(final Control[] controls)
573  {
574    final UnboundIDYubiKeyOTPBindRequest bindRequest =
575         new UnboundIDYubiKeyOTPBindRequest(authenticationID, authorizationID,
576              staticPassword, yubiKeyOTP, controls);
577    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
578    return bindRequest;
579  }
580
581
582
583  /**
584   * {@inheritDoc}
585   */
586  @Override()
587  public String getSASLMechanismName()
588  {
589    return UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME;
590  }
591
592
593
594  /**
595   * {@inheritDoc}
596   */
597  @Override()
598  public int getLastMessageID()
599  {
600    return messageID;
601  }
602
603
604
605  /**
606   * {@inheritDoc}
607   */
608  @Override()
609  public void toString(final StringBuilder buffer)
610  {
611    buffer.append("UnboundYubiKeyOTPBindRequest(authenticationID='");
612    buffer.append(authenticationID);
613
614    if (authorizationID != null)
615    {
616      buffer.append("', authorizationID='");
617      buffer.append(authorizationID);
618    }
619
620    buffer.append("', staticPasswordProvided=");
621    buffer.append(staticPassword != null);
622
623    final Control[] controls = getControls();
624    if (controls.length > 0)
625    {
626      buffer.append(", controls={");
627      for (int i=0; i < controls.length; i++)
628      {
629        if (i > 0)
630        {
631          buffer.append(", ");
632        }
633
634        buffer.append(controls[i]);
635      }
636      buffer.append('}');
637    }
638
639    buffer.append(')');
640  }
641}