| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.guacamole.totp; |
| |
| import com.google.common.primitives.Longs; |
| import java.security.InvalidKeyException; |
| import java.security.Key; |
| import java.security.NoSuchAlgorithmException; |
| import javax.crypto.Mac; |
| import javax.crypto.spec.SecretKeySpec; |
| |
| /* |
| * NOTE: This TOTP implementation is based on the TOTP reference implementation |
| * provided by the IETF Trust at: |
| * |
| * https://tools.ietf.org/id/draft-mraihi-totp-timebased-07.html#Section-Reference-Impl |
| */ |
| |
| /* |
| * Copyright (c) 2011 IETF Trust and the persons identified as authors |
| * of the code. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * - Neither the name of Internet Society, IETF or IETF Trust, nor the names |
| * of specific contributors, may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * Generator which uses the TOTP algorithm to generate authentication codes. |
| */ |
| public class TOTPGenerator { |
| |
| /** |
| * The default time to use as the basis for comparison when transforming |
| * provided TOTP timestamps into counter values required for HOTP, in |
| * seconds since midnight, 1970-01-01, UTC (UNIX epoch). |
| */ |
| public static final long DEFAULT_START_TIME = 0; |
| |
| /** |
| * The default frequency at which new TOTP codes should be generated (and |
| * old codes invalidated), in seconds. |
| */ |
| public static final long DEFAULT_TIME_STEP = 30; |
| |
| /** |
| * The TOTP generation mode. The mode dictates the hash function which |
| * should be used to generate authentication codes, as well as the required |
| * key size. |
| */ |
| private final Mode mode; |
| |
| /** |
| * The shared key to use to generate authentication codes. The size |
| * required for this key depends on the generation mode. |
| */ |
| private final Key key; |
| |
| /** |
| * The length of codes to generate, in digits. |
| */ |
| private final int length; |
| |
| /** |
| * The base time against which the timestamp specified for each TOTP |
| * should be compared to produce the corresponding HOTP counter value, in |
| * seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the value |
| * value referred to as "T0" in the TOTP specification. |
| */ |
| private final long startTime; |
| |
| /** |
| * The frequency that new TOTP codes should be generated and invalidated, |
| * in seconds. This is the value referred to as "X" in the TOTP |
| * specification. |
| */ |
| private final long timeStep; |
| |
| /** |
| * The operating mode for TOTP, defining the hash algorithm to be used. |
| */ |
| public enum Mode { |
| |
| /** |
| * TOTP mode which generates hashes using SHA1. TOTP in SHA1 mode |
| * requires 160-bit keys. |
| */ |
| SHA1("HmacSHA1", 20), |
| |
| /** |
| * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode |
| * requires 256-bit keys. |
| */ |
| SHA256("HmacSHA256", 32), |
| |
| /** |
| * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode |
| * requires 512-bit keys. |
| */ |
| SHA512("HmacSHA512", 64); |
| |
| /** |
| * The name of the HMAC algorithm which the TOTP implementation should |
| * use when operating in this mode, in the format required by |
| * Mac.getInstance(). |
| */ |
| private final String algorithmName; |
| |
| /** |
| * The recommended length of keys generated for TOTP in this mode, in |
| * bytes. Keys are recommended to be the same length as the hash |
| * involved. |
| */ |
| private final int recommendedKeyLength; |
| |
| /** |
| * Creates a new TOTP operating mode which is associated with the |
| * given HMAC algorithm. |
| * |
| * @param algorithmName |
| * The name of the HMAC algorithm which the TOTP implementation |
| * should use when operating in this mode, in the format required |
| * by Mac.getInstance(). |
| * |
| * @param recommendedKeyLength |
| * The recommended length of keys generated for TOTP in this mode, |
| * in bytes. |
| */ |
| private Mode(String algorithmName, int recommendedKeyLength) { |
| this.algorithmName = algorithmName; |
| this.recommendedKeyLength = recommendedKeyLength; |
| } |
| |
| /** |
| * Returns the name of the HMAC algorithm which the TOTP implementation |
| * should use when operating in this mode. The name returned will be |
| * in the format required by Mac.getInstance(). |
| * |
| * @return |
| * The name of the HMAC algorithm which the TOTP implementation |
| * should use. |
| */ |
| public String getAlgorithmName() { |
| return algorithmName; |
| } |
| |
| /** |
| * Returns the recommended length of keys generated for TOTP in this |
| * mode, in bytes. Keys are recommended to be the same length as the |
| * hash involved. |
| * |
| * @return |
| * The recommended length of keys generated for TOTP in this mode, |
| * in bytes. |
| */ |
| public int getRecommendedKeyLength() { |
| return recommendedKeyLength; |
| } |
| |
| } |
| |
| /** |
| * Creates a new TOTP generator which uses the given shared key to generate |
| * authentication codes. The provided generation mode dictates the size of |
| * the key required, while the given start time and time step dictate how |
| * timestamps provided for code generation are converted to the counter |
| * value used by HOTP (the algorithm which forms the basis of TOTP). |
| * |
| * @param key |
| * The shared key to use to generate authentication codes. |
| * |
| * @param mode |
| * The mode in which the TOTP algorithm should operate. |
| * |
| * @param length |
| * The length of the codes to generate, in digits. As required |
| * by the specification, this value MUST be at least 6 but no greater |
| * than 8. |
| * |
| * @param startTime |
| * The base time against which the timestamp specified for each TOTP |
| * should be compared to produce the corresponding HOTP counter value, |
| * in seconds since midnight, 1970-01-01, UTC (UNIX epoch). This is the |
| * value referred to as "T0" in the TOTP specification. |
| * |
| * @param timeStep |
| * The frequency that new TOTP codes should be generated and |
| * invalidated, in seconds. This is the value referred to as "X" in the |
| * TOTP specification. |
| * |
| * @throws InvalidKeyException |
| * If the provided key is invalid for the requested TOTP mode. |
| */ |
| public TOTPGenerator(byte[] key, Mode mode, int length, long startTime, |
| long timeStep) throws InvalidKeyException { |
| |
| // Validate length is within spec |
| if (length < 6 || length > 8) |
| throw new IllegalArgumentException("TOTP codes must be at least 6 " |
| + "digits and no more than 8 digits."); |
| |
| this.key = new SecretKeySpec(key, "RAW"); |
| this.mode = mode; |
| this.length = length; |
| this.startTime = startTime; |
| this.timeStep = timeStep; |
| |
| // Verify key validity |
| getMacInstance(this.mode, this.key); |
| |
| } |
| |
| /** |
| * Creates a new TOTP generator which uses the given shared key to generate |
| * authentication codes. The provided generation mode dictates the size of |
| * the key required. The start time and time step used to produce the |
| * counter value used by HOTP (the algorithm which forms the basis of TOTP) |
| * are set to the default values recommended by the TOTP specification (0 |
| * and 30 respectively). |
| * |
| * @param key |
| * The shared key to use to generate authentication codes. |
| * |
| * @param mode |
| * The mode in which the TOTP algorithm should operate. |
| * |
| * @param length |
| * The length of the codes to generate, in digits. As required |
| * by the specification, this value MUST be at least 6 but no greater |
| * than 8. |
| * |
| * @throws InvalidKeyException |
| * If the provided key is invalid for the requested TOTP mode. |
| */ |
| public TOTPGenerator(byte[] key, Mode mode, int length) |
| throws InvalidKeyException { |
| this(key, mode, length, DEFAULT_START_TIME, DEFAULT_TIME_STEP); |
| } |
| |
| /** |
| * Returns a new Mac instance which produces message authentication codes |
| * using the given secret key and the algorithm required by the given TOTP |
| * mode. |
| * |
| * @param mode |
| * The TOTP mode which dictates the HMAC algorithm to be used. |
| * |
| * @param key |
| * The secret key to use to produce message authentication codes. |
| * |
| * @return |
| * A new Mac instance which produces message authentication codes |
| * using the given secret key and the algorithm required by the given |
| * TOTP mode. |
| * |
| * @throws InvalidKeyException |
| * If the provided key is invalid for the requested TOTP mode. |
| */ |
| private static Mac getMacInstance(Mode mode, Key key) |
| throws InvalidKeyException { |
| |
| try { |
| Mac hmac = Mac.getInstance(mode.getAlgorithmName()); |
| hmac.init(key); |
| return hmac; |
| } |
| catch (NoSuchAlgorithmException e) { |
| throw new UnsupportedOperationException("Support for the HMAC " |
| + "algorithm required for TOTP in " + mode + " mode is " |
| + "missing.", e); |
| } |
| |
| } |
| |
| /** |
| * Calculates the HMAC for the given message using the key and algorithm |
| * provided when this TOTPGenerator was created. |
| * |
| * @param message |
| * The message to calculate the HMAC of. |
| * |
| * @return |
| * The HMAC of the given message. |
| */ |
| private byte[] getHMAC(byte[] message) { |
| |
| try { |
| return getMacInstance(mode, key).doFinal(message); |
| } |
| catch (InvalidKeyException e) { |
| |
| // As the key is verified during construction of the TOTPGenerator, |
| // this should never happen |
| throw new IllegalStateException("Provided key became invalid after " |
| + "passing validation.", e); |
| |
| } |
| |
| } |
| |
| /** |
| * Given an arbitrary integer value, returns a code containing the decimal |
| * representation of that value and exactly the given number of digits. If |
| * the given value has more than the desired number of digits, leading |
| * digits will be truncated to reduce the length. If the given value has |
| * fewer than the desired number of digits, leading zeroes will be added to |
| * increase the length. |
| * |
| * @param value |
| * The value to convert into a decimal code of the given length. |
| * |
| * @param length |
| * The number of digits to include in the code. |
| * |
| * @return |
| * A code containing the decimal value of the given integer, truncated |
| * or padded such that exactly the given number of digits are present. |
| */ |
| private String toCode(int value, int length) { |
| |
| // Convert value to simple integer string |
| String valueString = Integer.toString(value); |
| |
| // If the resulting string is too long, truncate to the last N digits |
| if (valueString.length() > length) |
| return valueString.substring(valueString.length() - length); |
| |
| // Otherwise, add zeroes until the desired length is reached |
| StringBuilder builder = new StringBuilder(length); |
| for (int i = valueString.length(); i < length; i++) |
| builder.append('0'); |
| |
| // Return the padded integer string |
| builder.append(valueString); |
| return builder.toString(); |
| |
| } |
| |
| /** |
| * Generates a TOTP code of the given length using the given absolute |
| * timestamp rather than the current system time. |
| * |
| * @param time |
| * The absolute timestamp to use to generate the TOTP code, in seconds |
| * since midnight, 1970-01-01, UTC (UNIX epoch). |
| * |
| * @return |
| * The TOTP code which corresponds to the given timestamp, having |
| * exactly the given length. |
| * |
| * @throws IllegalArgumentException |
| * If the given length is invalid as defined by the TOTP specification. |
| */ |
| public String generate(long time) { |
| |
| // Calculate HOTP counter value based on provided time |
| long counter = (time - startTime) / timeStep; |
| byte[] hash = getHMAC(Longs.toByteArray(counter)); |
| |
| // Calculate HOTP value as defined by section 5.2 of RFC 4226: |
| // https://tools.ietf.org/html/rfc4226#section-5.2 |
| int offset = hash[hash.length - 1] & 0xF; |
| int binary |
| = ((hash[offset] & 0x7F) << 24) |
| | ((hash[offset + 1] & 0xFF) << 16) |
| | ((hash[offset + 2] & 0xFF) << 8) |
| | (hash[offset + 3] & 0xFF); |
| |
| // Truncate or pad the value accordingly |
| return toCode(binary, length); |
| |
| } |
| |
| /** |
| * Generates a TOTP code of the given length using the current system time. |
| * |
| * @return |
| * The TOTP code which corresponds to the current system time, having |
| * exactly the given length. |
| * |
| * @throws IllegalArgumentException |
| * If the given length is invalid as defined by the TOTP specification. |
| */ |
| public String generate() { |
| return generate(System.currentTimeMillis() / 1000); |
| } |
| |
| /** |
| * Returns the TOTP code which would have been generated immediately prior |
| * to the code returned by invoking generate() with the given timestamp. |
| * |
| * @param time |
| * The absolute timestamp to use to generate the TOTP code, in seconds |
| * since midnight, 1970-01-01, UTC (UNIX epoch). |
| * |
| * @return |
| * The TOTP code which would have been generated immediately prior to |
| * the the code returned by invoking generate() with the given |
| * timestamp. |
| */ |
| public String previous(long time) { |
| return generate(Math.max(startTime, time - timeStep)); |
| } |
| |
| /** |
| * Returns the TOTP code which would have been generated immediately prior |
| * to the code currently being returned by generate(). |
| * |
| * @return |
| * The TOTP code which would have been generated immediately prior to |
| * the code currently being returned by generate(). |
| */ |
| public String previous() { |
| return previous(System.currentTimeMillis() / 1000); |
| } |
| |
| } |