mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 17:13:21 +00:00 
			
		
		
		
	GUACAMOLE-96: Add TOTP generator implementation based on reference implementation from IETF.
This commit is contained in:
		| @@ -0,0 +1,402 @@ | ||||
| /* | ||||
|  * 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"), | ||||
|  | ||||
|         /** | ||||
|          * TOTP mode which generates hashes using SHA256. TOTP in SHA256 mode | ||||
|          * requires 256-bit keys. | ||||
|          */ | ||||
|         SHA256("HmacSHA256"), | ||||
|  | ||||
|         /** | ||||
|          * TOTP mode which generates hashes using SHA512. TOTP in SHA512 mode | ||||
|          * requires 512-bit keys. | ||||
|          */ | ||||
|         SHA512("HmacSHA512"); | ||||
|  | ||||
|         /** | ||||
|          * 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; | ||||
|  | ||||
|         /** | ||||
|          * 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(). | ||||
|          */ | ||||
|         private Mode(String algorithmName) { | ||||
|             this.algorithmName = algorithmName; | ||||
|         } | ||||
|  | ||||
|         /** | ||||
|          * 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; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user