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 Alcatel-Lucent 8661
064 *   server products.  These classes provide support for proprietary
065 *   functionality or for external specifications that are not considered stable
066 *   or mature enough to be guaranteed to work in an interoperable way with
067 *   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  // This is an ugly hack to prevent checkstyle from complaining about the
135  // imports for classes only referenced in the javadoc.  Checkstyle apparently
136  // doesn't recognize that so we just need to use it in some way in this class
137  // to placate checkstyle.
138  static
139  {
140    final RegisterYubiKeyOTPDeviceExtendedRequest rr = null;
141    final DeregisterYubiKeyOTPDeviceExtendedRequest dr = null;
142  }
143
144
145
146  // The static password for the user, if provided.
147  private final ASN1OctetString staticPassword;
148
149  // The message ID from the last LDAP message sent from this request.
150  private volatile int messageID = -1;
151
152  // The authentication ID for the user.
153  private final String authenticationID;
154
155  // The authorization ID for the bind request, if provided.
156  private final String authorizationID;
157
158  // The one-time password generated by a YubiKey device.
159  private final String yubiKeyOTP;
160
161
162
163  /**
164   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
165   * information.
166   *
167   * @param  authenticationID  The authentication ID for the bind request.  It
168   *                           must not be {@code null}, and must have the form
169   *                           "dn:" followed by the DN of the target user or
170   *                           "u:" followed by the the username of the target
171   *                           user.
172   * @param  authorizationID   The authorization ID for the bind request.  It
173   *                           may be {@code null} if the authorization identity
174   *                           should be the same as the authentication
175   *                           identity.
176   * @param  staticPassword    The static password for the user specified as the
177   *                           authentication identity.  It may be {@code null}
178   *                           if authentication should be performed using only
179   *                           the YubiKey OTP.
180   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
181   *                           device.  It must not be {@code null}.
182   * @param  controls          The set of controls to include in the bind
183   *                           request.  It may be {@code null} or empty if
184   *                           there should not be any request controls.
185   */
186  public UnboundIDYubiKeyOTPBindRequest(final String authenticationID,
187                                        final String authorizationID,
188                                        final String staticPassword,
189                                        final String yubiKeyOTP,
190                                        final Control... controls)
191  {
192    this(authenticationID, authorizationID, toASN1OctetString(staticPassword),
193         yubiKeyOTP, controls);
194  }
195
196
197
198  /**
199   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
200   * information.
201   *
202   * @param  authenticationID  The authentication ID for the bind request.  It
203   *                           must not be {@code null}, and must have the form
204   *                           "dn:" followed by the DN of the target user or
205   *                           "u:" followed by the the username of the target
206   *                           user.
207   * @param  authorizationID   The authorization ID for the bind request.  It
208   *                           may be {@code null} if the authorization identity
209   *                           should be the same as the authentication
210   *                           identity.
211   * @param  staticPassword    The static password for the user specified as the
212   *                           authentication identity.  It may be {@code null}
213   *                           if authentication should be performed using only
214   *                           the YubiKey OTP.
215   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
216   *                           device.  It must not be {@code null}.
217   * @param  controls          The set of controls to include in the bind
218   *                           request.  It may be {@code null} or empty if
219   *                           there should not be any request controls.
220   */
221  public UnboundIDYubiKeyOTPBindRequest(final String authenticationID,
222                                        final String authorizationID,
223                                        final byte[] staticPassword,
224                                        final String yubiKeyOTP,
225                                        final Control... controls)
226  {
227    this(authenticationID, authorizationID, toASN1OctetString(staticPassword),
228         yubiKeyOTP, controls);
229  }
230
231
232
233  /**
234   * Creates a new UNBOUNDID-YUBIKEY-OTP bind request with the provided
235   * information.
236   *
237   * @param  authenticationID  The authentication ID for the bind request.  It
238   *                           must not be {@code null}, and must have the form
239   *                           "dn:" followed by the DN of the target user or
240   *                           "u:" followed by the the username of the target
241   *                           user.
242   * @param  authorizationID   The authorization ID for the bind request.  It
243   *                           may be {@code null} if the authorization identity
244   *                           should be the same as the authentication
245   *                           identity.
246   * @param  staticPassword    The static password for the user specified as the
247   *                           authentication identity.  It may be {@code null}
248   *                           if authentication should be performed using only
249   *                           the YubiKey OTP.
250   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
251   *                           device.  It must not be {@code null}.
252   * @param  controls          The set of controls to include in the bind
253   *                           request.  It may be {@code null} or empty if
254   *                           there should not be any request controls.
255   */
256  private UnboundIDYubiKeyOTPBindRequest(final String authenticationID,
257                                         final String authorizationID,
258                                         final ASN1OctetString staticPassword,
259                                         final String yubiKeyOTP,
260                                         final Control... controls)
261  {
262    super(controls);
263
264    Validator.ensureNotNull(authenticationID);
265    Validator.ensureNotNull(yubiKeyOTP);
266
267    this.authenticationID = authenticationID;
268    this.authorizationID  = authorizationID;
269    this.staticPassword   = staticPassword;
270    this.yubiKeyOTP       = yubiKeyOTP;
271  }
272
273
274
275  /**
276   * Retrieves an ASN.1 octet string that represents the appropriate encoding
277   * for the provided password.
278   *
279   * @param  password  The password object to convert to an ASN.1 octet string.
280   *                   It may be {@code null} if no static password is required.
281   *                   Otherwise, it must either be a string or a byte array.
282   *
283   * @return  The ASN.1 octet string created from the provided password object,
284   *          or {@code null} if the provided password object was null.
285   */
286  private static ASN1OctetString toASN1OctetString(final Object password)
287  {
288    if (password == null)
289    {
290      return null;
291    }
292    else if (password instanceof byte[])
293    {
294      return new ASN1OctetString(TYPE_STATIC_PASSWORD, (byte[]) password);
295    }
296    else
297    {
298      return new ASN1OctetString(TYPE_STATIC_PASSWORD,
299           String.valueOf(password));
300    }
301  }
302
303
304
305  /**
306   * Creates a new UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the
307   * provided SASL credentials.
308   *
309   * @param  saslCredentials  The SASL credentials to decode in order to create
310   *                          the UNBOUNDID-YUBIKEY-OTP SASL bind request.  It
311   *                          must not be {@code null}.
312   * @param  controls         The set of controls to include in the bind
313   *                          request.  This may be {@code null} or empty if no
314   *                          controls should be included in the request.
315   *
316   * @return  The UNBOUNDID-YUBIKEY-OTP SASL bind request decoded from the
317   *          provided credentials.
318   *
319   * @throws  LDAPException  If the provided credentials cannot be decoded to a
320   *                         valid UNBOUNDID-YUBIKEY-OTP bind request.
321   */
322  public static UnboundIDYubiKeyOTPBindRequest decodeCredentials(
323                     final ASN1OctetString saslCredentials,
324                     final Control... controls)
325         throws LDAPException
326  {
327    try
328    {
329      ASN1OctetString staticPassword = null;
330      String authenticationID = null;
331      String authorizationID  = null;
332      String yubiKeyOTP = null;
333
334      for (final ASN1Element e :
335           ASN1Sequence.decodeAsSequence(saslCredentials.getValue()).elements())
336      {
337        switch (e.getType())
338        {
339          case TYPE_AUTHENTICATION_ID:
340            authenticationID =
341                 ASN1OctetString.decodeAsOctetString(e).stringValue();
342            break;
343          case TYPE_AUTHORIZATION_ID:
344            authorizationID =
345                 ASN1OctetString.decodeAsOctetString(e).stringValue();
346            break;
347          case TYPE_STATIC_PASSWORD:
348            staticPassword = ASN1OctetString.decodeAsOctetString(e);
349            break;
350          case TYPE_YUBIKEY_OTP:
351            yubiKeyOTP = ASN1OctetString.decodeAsOctetString(e).stringValue();
352            break;
353          default:
354            throw new LDAPException(ResultCode.DECODING_ERROR,
355                 ERR_YUBIKEY_OTP_DECODE_UNRECOGNIZED_CRED_ELEMENT.get(
356                      UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
357                      StaticUtils.toHex(e.getType())));
358        }
359      }
360
361      if (authenticationID == null)
362      {
363        throw new LDAPException(ResultCode.DECODING_ERROR,
364             ERR_YUBIKEY_OTP_DECODE_NO_AUTH_ID.get(
365                  UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
366      }
367
368      if (yubiKeyOTP == null)
369      {
370        throw new LDAPException(ResultCode.DECODING_ERROR,
371             ERR_YUBIKEY_OTP_NO_OTP.get(UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME));
372      }
373
374      return new UnboundIDYubiKeyOTPBindRequest(authenticationID,
375           authorizationID, staticPassword, yubiKeyOTP, controls);
376    }
377    catch (final LDAPException le)
378    {
379      Debug.debugException(le);
380      throw le;
381    }
382    catch (final Exception e)
383    {
384      Debug.debugException(e);
385      throw new LDAPException(ResultCode.DECODING_ERROR,
386           ERR_YUBIKEY_OTP_DECODE_ERROR.get(
387                UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME,
388                StaticUtils.getExceptionMessage(e)),
389           e);
390    }
391  }
392
393
394
395  /**
396   * Retrieves the authentication ID for the bind request.
397   *
398   * @return  The authentication ID for the bind request.
399   */
400  public String getAuthenticationID()
401  {
402    return authenticationID;
403  }
404
405
406
407  /**
408   * Retrieves the authorization ID for the bind request, if any.
409   *
410   * @return  The authorization ID for the bind request, or {@code null} if the
411   *          authorization identity should match the authentication identity.
412   */
413  public String getAuthorizationID()
414  {
415    return authorizationID;
416  }
417
418
419
420  /**
421   * Retrieves the string representation of the static password for the bind
422   * request, if any.
423   *
424   * @return  The string representation of the static password for the bind
425   *          request, or {@code null} if there is no static password.
426   */
427  public String getStaticPasswordString()
428  {
429    if (staticPassword == null)
430    {
431      return null;
432    }
433    else
434    {
435      return staticPassword.stringValue();
436    }
437  }
438
439
440
441  /**
442   * Retrieves the bytes that comprise the static password for the bind request,
443   * if any.
444   *
445   * @return  The bytes that comprise the static password for the bind request,
446   *          or {@code null} if there is no static password.
447   */
448  public byte[] getStaticPasswordBytes()
449  {
450    if (staticPassword == null)
451    {
452      return null;
453    }
454    else
455    {
456      return staticPassword.getValue();
457    }
458  }
459
460
461
462  /**
463   * Retrieves the YubiKey-generated one-time password to include in the bind
464   * request.
465   *
466   * @return  The YubiKey-generated one-time password to include in the bind
467   *          request.
468   */
469  public String getYubiKeyOTP()
470  {
471    return yubiKeyOTP;
472  }
473
474
475
476  /**
477   * Sends this bind request to the target server over the provided connection
478   * and returns the corresponding response.
479   *
480   * @param  connection  The connection to use to send this bind request to the
481   *                     server and read the associated response.
482   * @param  depth       The current referral depth for this request.  It should
483   *                     always be one for the initial request, and should only
484   *                     be incremented when following referrals.
485   *
486   * @return  The bind response read from the server.
487   *
488   * @throws  LDAPException  If a problem occurs while sending the request or
489   *                         reading the response.
490   */
491  @Override()
492  protected BindResult process(final LDAPConnection connection, final int depth)
493            throws LDAPException
494  {
495    messageID = InternalSDKHelper.nextMessageID(connection);
496    return sendBindRequest(connection, "", encodeCredentials(), getControls(),
497         getResponseTimeoutMillis(connection));
498  }
499
500
501
502  /**
503   * Retrieves an ASN.1 octet string containing the encoded credentials for this
504   * bind request.
505   *
506   * @return  An ASN.1 octet string containing the encoded credentials for this
507   *          bind request.
508   */
509  public ASN1OctetString encodeCredentials()
510  {
511    return encodeCredentials(authenticationID, authorizationID, staticPassword,
512         yubiKeyOTP);
513  }
514
515
516
517  /**
518   * Encodes the provided information into an ASN.1 octet string suitable for
519   * use as the SASL credentials for an UNBOUNDID-YUBIKEY-OTP bind request.
520   *
521   * @param  authenticationID  The authentication ID for the bind request.  It
522   *                           must not be {@code null}, and must have the form
523   *                           "dn:" followed by the DN of the target user or
524   *                           "u:" followed by the the username of the target
525   *                           user.
526   * @param  authorizationID   The authorization ID for the bind request.  It
527   *                           may be {@code null} if the authorization identity
528   *                           should be the same as the authentication
529   *                           identity.
530   * @param  staticPassword    The static password for the user specified as the
531   *                           authentication identity.  It may be {@code null}
532   *                           if authentication should be performed using only
533   *                           the YubiKey OTP.
534   * @param  yubiKeyOTP        The one-time password generated by the YubiKey
535   *                           device.  It must not be {@code null}.
536   *
537   * @return  An ASN.1 octet string suitable for use as the SASL credentials for
538   *          an UNBOUNDID-YUBIKEY-OTP bind request.
539   */
540  public static ASN1OctetString encodeCredentials(final String authenticationID,
541                                     final String authorizationID,
542                                     final ASN1OctetString staticPassword,
543                                     final String yubiKeyOTP)
544  {
545    Validator.ensureNotNull(authenticationID);
546    Validator.ensureNotNull(yubiKeyOTP);
547
548    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(4);
549    elements.add(new ASN1OctetString(TYPE_AUTHENTICATION_ID, authenticationID));
550
551    if (authorizationID != null)
552    {
553      elements.add(new ASN1OctetString(TYPE_AUTHORIZATION_ID, authorizationID));
554    }
555
556    if (staticPassword != null)
557    {
558      elements.add(new ASN1OctetString(TYPE_STATIC_PASSWORD,
559           staticPassword.getValue()));
560    }
561
562    elements.add(new ASN1OctetString(TYPE_YUBIKEY_OTP, yubiKeyOTP));
563
564    return new ASN1OctetString(new ASN1Sequence(elements).encode());
565  }
566
567
568
569  /**
570   * {@inheritDoc}
571   */
572  @Override()
573  public UnboundIDYubiKeyOTPBindRequest duplicate()
574  {
575    return duplicate(getControls());
576  }
577
578
579
580  /**
581   * {@inheritDoc}
582   */
583  @Override()
584  public UnboundIDYubiKeyOTPBindRequest duplicate(final Control[] controls)
585  {
586    final UnboundIDYubiKeyOTPBindRequest bindRequest =
587         new UnboundIDYubiKeyOTPBindRequest(authenticationID, authorizationID,
588              staticPassword, yubiKeyOTP, controls);
589    bindRequest.setResponseTimeoutMillis(getResponseTimeoutMillis(null));
590    return bindRequest;
591  }
592
593
594
595  /**
596   * {@inheritDoc}
597   */
598  @Override()
599  public String getSASLMechanismName()
600  {
601    return UNBOUNDID_YUBIKEY_OTP_MECHANISM_NAME;
602  }
603
604
605
606  /**
607   * {@inheritDoc}
608   */
609  @Override()
610  public int getLastMessageID()
611  {
612    return messageID;
613  }
614
615
616
617  /**
618   * {@inheritDoc}
619   */
620  @Override()
621  public void toString(final StringBuilder buffer)
622  {
623    buffer.append("UnboundYubiKeyOTPBindRequest(authenticationID='");
624    buffer.append(authenticationID);
625
626    if (authorizationID != null)
627    {
628      buffer.append("', authorizationID='");
629      buffer.append(authorizationID);
630    }
631
632    buffer.append("', staticPasswordProvided=");
633    buffer.append(staticPassword != null);
634
635    final Control[] controls = getControls();
636    if (controls.length > 0)
637    {
638      buffer.append(", controls={");
639      for (int i=0; i < controls.length; i++)
640      {
641        if (i > 0)
642        {
643          buffer.append(", ");
644        }
645
646        buffer.append(controls[i]);
647      }
648      buffer.append('}');
649    }
650
651    buffer.append(')');
652  }
653}