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