GUACAMOLE-96: Add configuration parameters for details of TOTP generation. Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/a422fdf9 Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/a422fdf9 Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/a422fdf9
Branch: refs/heads/master Commit: a422fdf9c235e898d5c05499cef638501beb6508 Parents: 170a11b Author: Michael Jumper <mjum...@apache.org> Authored: Mon Nov 20 14:29:03 2017 -0800 Committer: Michael Jumper <mjum...@apache.org> Committed: Sun Feb 4 19:45:18 2018 -0800 ---------------------------------------------------------------------- .../totp/TOTPAuthenticationProviderModule.java | 2 + .../auth/totp/UserVerificationService.java | 33 +++- .../auth/totp/conf/ConfigurationService.java | 161 +++++++++++++++++++ .../auth/totp/conf/TOTPModeProperty.java | 62 +++++++ .../auth/totp/form/AuthenticationCodeField.java | 42 ++--- 5 files changed, 274 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java index 757a412..e72beec 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProviderModule.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.totp; import com.google.inject.AbstractModule; import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; import org.apache.guacamole.net.auth.AuthenticationProvider; @@ -71,6 +72,7 @@ public class TOTPAuthenticationProviderModule extends AbstractModule { bind(Environment.class).toInstance(environment); // Bind TOTP-specific services + bind(ConfigurationService.class); bind(UserVerificationService.class); } http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java index a44b6ee..987d4ca 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java @@ -20,6 +20,8 @@ package org.apache.guacamole.auth.totp; import com.google.common.io.BaseEncoding; +import com.google.inject.Inject; +import com.google.inject.Provider; import java.security.InvalidKeyException; import java.util.Collections; import java.util.HashMap; @@ -28,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleUnsupportedException; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.auth.totp.form.AuthenticationCodeField; import org.apache.guacamole.form.Field; import org.apache.guacamole.net.auth.AuthenticatedUser; @@ -67,6 +70,18 @@ public class UserVerificationService { private static final BaseEncoding BASE32 = BaseEncoding.base32(); /** + * Service for retrieving configuration information. + */ + @Inject + private ConfigurationService confService; + + /** + * Provider for AuthenticationCodeField instances. + */ + @Inject + private Provider<AuthenticationCodeField> codeFieldProvider; + + /** * Retrieves and decodes the base32-encoded TOTP key associated with user * having the given UserContext. If no TOTP key is associated with the user, * a random key is generated and associated with the user. If the extension @@ -100,7 +115,8 @@ public class UserVerificationService { if (secret == null) { // Generate random key for user - UserTOTPKey generated = new UserTOTPKey(username, TOTPGenerator.Mode.SHA1.getRecommendedKeyLength()); + TOTPGenerator.Mode mode = confService.getMode(); + UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength()); if (setKey(context, generated)) return generated; @@ -223,25 +239,32 @@ public class UserVerificationService { // If no TOTP provided, request one if (code == null) { + AuthenticationCodeField field = codeFieldProvider.get(); + // If the user hasn't completed enrollment, request that they do - if (!key.isConfirmed()) + if (!key.isConfirmed()) { + field.exposeKey(key); throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( - Collections.<Field>singletonList(new AuthenticationCodeField(key)) + Collections.<Field>singletonList(field) )); + } // Otherwise simply request the user's authentication code throw new GuacamoleInsufficientCredentialsException( "LOGIN.INFO_TOTP_REQUIRED", new CredentialsInfo( - Collections.<Field>singletonList(new AuthenticationCodeField()) + Collections.<Field>singletonList(field) )); } try { + // Get generator based on user's key and provided configuration + TOTPGenerator totp = new TOTPGenerator(key.getSecret(), + confService.getMode(), confService.getDigits()); + // Verify provided TOTP against value produced by generator - TOTPGenerator totp = new TOTPGenerator(key.getSecret(), TOTPGenerator.Mode.SHA1, 6); if (code.equals(totp.generate()) || code.equals(totp.previous())) { // Record key as confirmed, if it hasn't already been so recorded http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java new file mode 100644 index 0000000..8658849 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/ConfigurationService.java @@ -0,0 +1,161 @@ +/* + * 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.auth.totp.conf; + +import com.google.inject.Inject; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.environment.Environment; +import org.apache.guacamole.properties.IntegerGuacamoleProperty; +import org.apache.guacamole.properties.StringGuacamoleProperty; +import org.apache.guacamole.totp.TOTPGenerator; + +/** + * Service for retrieving configuration information regarding the TOTP + * authentication extension. + */ +public class ConfigurationService { + + /** + * The Guacamole server environment. + */ + @Inject + private Environment environment; + + /** + * The human-readable name of the entity issuing user accounts. By default, + * this will be "Apache Guacamole". + */ + private static final StringGuacamoleProperty TOTP_ISSUER = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "totp-issuer"; } + + }; + + /** + * The number of digits which should be included in each generated TOTP + * code. By default, this will be 6. + */ + private static final IntegerGuacamoleProperty TOTP_DIGITS= + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "totp-digits"; } + + }; + + /** + * The duration that each generated code should remain valid, in seconds. + * By default, this will be 30. + */ + private static final IntegerGuacamoleProperty TOTP_PERIOD = + new IntegerGuacamoleProperty() { + + @Override + public String getName() { return "totp-period"; } + + }; + + /** + * The hash algorithm that should be used to generate TOTP codes. By + * default, this will be "sha1". Legal values are "sha1", "sha256", and + * "sha512". + */ + private static final TOTPModeProperty TOTP_MODE = + new TOTPModeProperty() { + + @Override + public String getName() { return "totp-mode"; } + + }; + + /** + * Returns the human-readable name of the entity issuing user accounts. If + * not specified, "Apache Guacamole" will be used by default. + * + * @return + * The human-readable name of the entity issuing user accounts. + * + * @throws GuacamoleException + * If the "totp-issuer" property cannot be read from + * guacamole.properties. + */ + public String getIssuer() throws GuacamoleException { + return environment.getProperty(TOTP_ISSUER, "Apache Guacamole"); + } + + /** + * Returns the number of digits which should be included in each generated + * TOTP code. If not specified, 6 will be used by default. + * + * @return + * The number of digits which should be included in each generated + * TOTP code. + * + * @throws GuacamoleException + * If the "totp-digits" property cannot be read from + * guacamole.properties. + */ + public int getDigits() throws GuacamoleException { + + // Validate legal number of digits + int digits = environment.getProperty(TOTP_DIGITS, 6); + if (digits < 6 || digits > 8) + throw new GuacamoleServerException("TOTP codes may have no fewer " + + "than 6 digits and no more than 8 digits."); + + return digits; + + } + + /** + * Returns the duration that each generated code should remain valid, in + * seconds. If not specified, 30 will be used by default. + * + * @return + * The duration that each generated code should remain valid, in + * seconds. + * + * @throws GuacamoleException + * If the "totp-period" property cannot be read from + * guacamole.properties. + */ + public int getPeriod() throws GuacamoleException { + return environment.getProperty(TOTP_PERIOD, 30); + } + + /** + * Returns the hash algorithm that should be used to generate TOTP codes. If + * not specified, SHA1 will be used by default. + * + * @return + * The hash algorithm that should be used to generate TOTP codes. + * + * @throws GuacamoleException + * If the "totp-mode" property cannot be read from + * guacamole.properties. + */ + public TOTPGenerator.Mode getMode() throws GuacamoleException { + return environment.getProperty(TOTP_MODE, TOTPGenerator.Mode.SHA1); + } + +} http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java new file mode 100644 index 0000000..bfe3ef3 --- /dev/null +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/conf/TOTPModeProperty.java @@ -0,0 +1,62 @@ +/* + * 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.auth.totp.conf; + +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleServerException; +import org.apache.guacamole.properties.GuacamoleProperty; +import org.apache.guacamole.totp.TOTPGenerator; + +/** + * A GuacamoleProperty whose value is a TOTP generation method. The string + * values "sha1", "sha256", and "sha512" are each parsed to their corresponding + * values within the TOTPGenerator.Mode enum. All other string values result in + * parse errors. + */ +public abstract class TOTPModeProperty + implements GuacamoleProperty<TOTPGenerator.Mode> { + + @Override + public TOTPGenerator.Mode parseValue(String value) + throws GuacamoleException { + + // If no value provided, return null. + if (value == null) + return null; + + // SHA1 + if (value.equals("sha1")) + return TOTPGenerator.Mode.SHA1; + + // SHA256 + if (value.equals("sha256")) + return TOTPGenerator.Mode.SHA256; + + // SHA512 + if (value.equals("sha512")) + return TOTPGenerator.Mode.SHA512; + + // The provided value is not legal + throw new GuacamoleServerException("TOTP mode must be one of " + + "\"sha1\", \"sha256\", or \"sha512\"."); + + } + +} http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/a422fdf9/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java index 1be5f27..e0333dd 100644 --- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java +++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/form/AuthenticationCodeField.java @@ -20,6 +20,7 @@ package org.apache.guacamole.auth.totp.form; import com.google.common.io.BaseEncoding; +import com.google.inject.Inject; import com.google.zxing.BarcodeFormat; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; @@ -32,6 +33,7 @@ import javax.ws.rs.core.UriBuilder; import javax.xml.bind.DatatypeConverter; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.totp.UserTOTPKey; +import org.apache.guacamole.auth.totp.conf.ConfigurationService; import org.apache.guacamole.form.Field; import org.codehaus.jackson.annotate.JsonProperty; @@ -67,30 +69,33 @@ public class AuthenticationCodeField extends Field { private static final BaseEncoding BASE32 = BaseEncoding.base32(); /** + * Service for retrieving configuration information. + */ + @Inject + private ConfigurationService confService; + + /** * The TOTP key to expose to the user for the sake of enrollment, if any. * If no such key should be exposed to the user, this will be null. */ - private final UserTOTPKey key; + private UserTOTPKey key; /** * Creates a new field which prompts the user for an authentication code - * generated via TOTP, and provide the user with their TOTP key to - * facilitate enrollment. - * - * @param key - * The TOTP key to expose to the user for the sake of enrollment. + * generated via TOTP. The user's TOTP key is not exposed for enrollment. */ - public AuthenticationCodeField(UserTOTPKey key) { + public AuthenticationCodeField() { super(PARAMETER_NAME, FIELD_TYPE_NAME); - this.key = key; } /** - * Creates a new field which prompts the user for an authentication code - * generated via TOTP. The user's TOTP key is not exposed for enrollment. + * Exposes the given key to facilitate enrollment. + * + * @param key + * The TOTP key to expose to the user for the sake of enrollment. */ - public AuthenticationCodeField() { - this(null); + public void exposeKey(UserTOTPKey key) { + this.key = key; } /** @@ -114,20 +119,15 @@ public class AuthenticationCodeField extends Field { if (key == null) return null; - // FIXME: Pull from configuration - String issuer = "Some Issuer"; - String algorithm = "SHA1"; - String digits = "6"; - String period = "30"; - // Format "otpauth" URL (see https://github.com/google/google-authenticator/wiki/Key-Uri-Format) + String issuer = confService.getIssuer(); return UriBuilder.fromUri("otpauth://totp/") .path(issuer + ":" + key.getUsername()) .queryParam("secret", BASE32.encode(key.getSecret())) .queryParam("issuer", issuer) - .queryParam("algorithm", algorithm) - .queryParam("digits", digits) - .queryParam("period", period) + .queryParam("algorithm", confService.getMode()) + .queryParam("digits", confService.getDigits()) + .queryParam("period", confService.getPeriod()) .build(); }