001/*
002 * Copyright 2012-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2012-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) 2015-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.text.DecimalFormat;
041import javax.crypto.Mac;
042import javax.crypto.SecretKey;
043import javax.crypto.spec.SecretKeySpec;
044
045import com.unboundid.ldap.sdk.LDAPException;
046import com.unboundid.ldap.sdk.ResultCode;
047import com.unboundid.util.Debug;
048import com.unboundid.util.StaticUtils;
049import com.unboundid.util.ThreadSafety;
050import com.unboundid.util.ThreadSafetyLevel;
051
052import static com.unboundid.ldap.sdk.unboundidds.UnboundIDDSMessages.*;
053
054
055
056/**
057 * This class provides support for a number of one-time password algorithms.
058 * <BR>
059 * <BLOCKQUOTE>
060 *   <B>NOTE:</B>  This class, and other classes within the
061 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
062 *   supported for use against Ping Identity, UnboundID, and
063 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
064 *   for proprietary functionality or for external specifications that are not
065 *   considered stable or mature enough to be guaranteed to work in an
066 *   interoperable way with other types of LDAP servers.
067 * </BLOCKQUOTE>
068 * <BR>
069 * Supported algorithms include:
070 * <UL>
071 *   <LI>HOTP -- The HMAC-based one-time password algorithm described in
072 *       <A HREF="http://www.ietf.org/rfc/rfc4226.txt">RFC 4226</A>.</LI>
073 *   <LI>TOTP -- The time-based one-time password algorithm described in
074 *       <A HREF="http://www.ietf.org/rfc/rfc6238.txt">RFC 6238</A>.</LI>
075 * </UL>
076 */
077@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
078public final class OneTimePassword
079{
080  /**
081   * The default number of digits to include in generated HOTP passwords.
082   */
083  public static final int DEFAULT_HOTP_NUM_DIGITS = 6;
084
085
086
087  /**
088   * The default time interval (in seconds) to use when generating TOTP
089   * passwords.
090   */
091  public static final int DEFAULT_TOTP_INTERVAL_DURATION_SECONDS = 30;
092
093
094
095  /**
096   * The default number of digits to include in generated TOTP passwords.
097   */
098  public static final int DEFAULT_TOTP_NUM_DIGITS = 6;
099
100
101
102  /**
103   * The name of the MAC algorithm that will be used to perform HMAC-SHA-1
104   * processing.
105   */
106  private static final String HMAC_ALGORITHM_SHA_1 = "HmacSHA1";
107
108
109
110  /**
111   * The name of the secret key spec algorithm that will be used to construct a
112   * secret key from the raw bytes that comprise it.
113   */
114  private static final String KEY_ALGORITHM_RAW = "RAW";
115
116
117
118  /**
119   * Prevent this utility class from being instantiated.
120   */
121  private OneTimePassword()
122  {
123    // No implementation required.
124  }
125
126
127
128  /**
129   * Generates a six-digit HMAC-based one-time-password using the provided
130   * information.
131   *
132   * @param  sharedSecret  The secret key shared by both parties that will be
133   *                       using the generated one-time password.
134   * @param  counter       The counter value that will be used in the course of
135   *                       generating the one-time password.
136   *
137   * @return  The zero-padded string representation of the resulting HMAC-based
138   *          one-time password.
139   *
140   * @throws  LDAPException  If an unexpected problem is encountered while
141   *                         attempting to generate the one-time password.
142   */
143  public static String hotp(final byte[] sharedSecret, final long counter)
144         throws LDAPException
145  {
146    return hotp(sharedSecret, counter, DEFAULT_HOTP_NUM_DIGITS);
147  }
148
149
150
151  /**
152   * Generates an HMAC-based one-time-password using the provided information.
153   *
154   * @param  sharedSecret  The secret key shared by both parties that will be
155   *                       using the generated one-time password.
156   * @param  counter       The counter value that will be used in the course of
157   *                       generating the one-time password.
158   * @param  numDigits     The number of digits that should be included in the
159   *                       generated one-time password.  It must be greater than
160   *                       or equal to six and less than or equal to eight.
161   *
162   * @return  The zero-padded string representation of the resulting HMAC-based
163   *          one-time password.
164   *
165   * @throws  LDAPException  If an unexpected problem is encountered while
166   *                         attempting to generate the one-time password.
167   */
168  public static String hotp(final byte[] sharedSecret, final long counter,
169                            final int numDigits)
170         throws LDAPException
171  {
172    try
173    {
174      // Ensure that the number of digits is between 6 and 8, inclusive, and
175      // get the appropriate modulus and decimal formatters to use.
176      final int modulus;
177      final DecimalFormat decimalFormat;
178      switch (numDigits)
179      {
180        case 6:
181          modulus = 1_000_000;
182          decimalFormat = new DecimalFormat("000000");
183          break;
184        case 7:
185          modulus = 10_000_000;
186          decimalFormat = new DecimalFormat("0000000");
187          break;
188        case 8:
189          modulus = 100_000_000;
190          decimalFormat = new DecimalFormat("00000000");
191          break;
192        default:
193          throw new LDAPException(ResultCode.PARAM_ERROR,
194               ERR_HOTP_INVALID_NUM_DIGITS.get(numDigits));
195      }
196
197
198      // Convert the provided counter to a 64-bit value.
199      final byte[] counterBytes = new byte[8];
200      counterBytes[0] = (byte) ((counter >> 56) & 0xFFL);
201      counterBytes[1] = (byte) ((counter >> 48) & 0xFFL);
202      counterBytes[2] = (byte) ((counter >> 40) & 0xFFL);
203      counterBytes[3] = (byte) ((counter >> 32) & 0xFFL);
204      counterBytes[4] = (byte) ((counter >> 24) & 0xFFL);
205      counterBytes[5] = (byte) ((counter >> 16) & 0xFFL);
206      counterBytes[6] = (byte) ((counter >> 8) & 0xFFL);
207      counterBytes[7] = (byte) (counter & 0xFFL);
208
209
210      // Generate an HMAC-SHA-1 of the given counter using the provided key.
211      final SecretKey k = new SecretKeySpec(sharedSecret, KEY_ALGORITHM_RAW);
212      final Mac m = Mac.getInstance(HMAC_ALGORITHM_SHA_1);
213      m.init(k);
214      final byte[] hmacBytes = m.doFinal(counterBytes);
215
216
217      // Generate a dynamic truncation of the resulting HMAC-SHA-1.
218      final int dtOffset = hmacBytes[19] & 0x0F;
219      final int dtValue  = (((hmacBytes[dtOffset] & 0x7F) << 24) |
220           ((hmacBytes[dtOffset+1] & 0xFF) << 16) |
221           ((hmacBytes[dtOffset+2] & 0xFF) << 8) |
222           (hmacBytes[dtOffset+3] & 0xFF));
223
224
225      // Use a modulus operation to convert the value into one that has at most
226      // the desired number of digits.
227      return decimalFormat.format(dtValue % modulus);
228    }
229    catch (final Exception e)
230    {
231      Debug.debugException(e);
232      throw new LDAPException(ResultCode.LOCAL_ERROR,
233           ERR_HOTP_ERROR_GENERATING_PW.get(StaticUtils.getExceptionMessage(e)),
234           e);
235    }
236  }
237
238
239
240  /**
241   * Generates a six-digit time-based one-time-password using the provided
242   * information and a 30-second time interval.
243   *
244   * @param  sharedSecret  The secret key shared by both parties that will be
245   *                       using the generated one-time password.
246   *
247   * @return  The zero-padded string representation of the resulting time-based
248   *          one-time password.
249   *
250   * @throws  LDAPException  If an unexpected problem is encountered while
251   *                         attempting to generate the one-time password.
252   */
253  public static String totp(final byte[] sharedSecret)
254         throws LDAPException
255  {
256    return totp(sharedSecret, System.currentTimeMillis(),
257         DEFAULT_TOTP_INTERVAL_DURATION_SECONDS, DEFAULT_TOTP_NUM_DIGITS);
258  }
259
260
261
262  /**
263   * Generates a six-digit time-based one-time-password using the provided
264   * information.
265   *
266   * @param  sharedSecret             The secret key shared by both parties that
267   *                                  will be using the generated one-time
268   *                                  password.
269   * @param  authTime                 The time (in milliseconds since the epoch,
270   *                                  as reported by
271   *                                  {@code System.currentTimeMillis} or
272   *                                  {@code Date.getTime}) at which the
273   *                                  authentication attempt occurred.
274   * @param  intervalDurationSeconds  The duration of the time interval, in
275   *                                  seconds, that should be used when
276   *                                  performing the computation.
277   * @param  numDigits                The number of digits that should be
278   *                                  included in the generated one-time
279   *                                  password.  It must be greater than or
280   *                                  equal to six and less than or equal to
281   *                                  eight.
282   *
283   * @return  The zero-padded string representation of the resulting time-based
284   *          one-time password.
285   *
286   * @throws  LDAPException  If an unexpected problem is encountered while
287   *                         attempting to generate the one-time password.
288   */
289  public static String totp(final byte[] sharedSecret, final long authTime,
290                            final int intervalDurationSeconds,
291                            final int numDigits)
292         throws LDAPException
293  {
294    // Make sure that the specified number of digits is between 6 and 8,
295    // inclusive.
296    if ((numDigits < 6) || (numDigits > 8))
297    {
298      throw new LDAPException(ResultCode.PARAM_ERROR,
299           ERR_TOTP_INVALID_NUM_DIGITS.get(numDigits));
300    }
301
302    try
303    {
304      final long timeIntervalNumber = authTime / 1000 / intervalDurationSeconds;
305      return hotp(sharedSecret, timeIntervalNumber, numDigits);
306    }
307    catch (final Exception e)
308    {
309      Debug.debugException(e);
310      throw new LDAPException(ResultCode.LOCAL_ERROR,
311           ERR_TOTP_ERROR_GENERATING_PW.get(StaticUtils.getExceptionMessage(e)),
312           e);
313    }
314  }
315}