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