You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by vn...@apache.org on 2018/02/05 18:04:37 UTC
[19/21] guacamole-client git commit: GUACAMOLE-96: Block external
access to TOTP-internal attributes.
GUACAMOLE-96: Block external access to TOTP-internal attributes.
Project: http://git-wip-us.apache.org/repos/asf/guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/guacamole-client/commit/96e3d029
Tree: http://git-wip-us.apache.org/repos/asf/guacamole-client/tree/96e3d029
Diff: http://git-wip-us.apache.org/repos/asf/guacamole-client/diff/96e3d029
Branch: refs/heads/master
Commit: 96e3d029992ac09d27aac808c489779000fb6fe1
Parents: 2a894c4
Author: Michael Jumper <mj...@apache.org>
Authored: Mon Nov 20 16:15:01 2017 -0800
Committer: Michael Jumper <mj...@apache.org>
Committed: Sun Feb 4 19:45:18 2018 -0800
----------------------------------------------------------------------
.../auth/totp/TOTPAuthenticationProvider.java | 6 +-
.../totp/TOTPAuthenticationProviderModule.java | 1 +
.../apache/guacamole/auth/totp/UserTOTPKey.java | 148 ----------
.../auth/totp/UserVerificationService.java | 292 -------------------
.../auth/totp/form/AuthenticationCodeField.java | 2 +-
.../guacamole/auth/totp/user/TOTPUser.java | 102 +++++++
.../auth/totp/user/TOTPUserContext.java | 64 ++++
.../guacamole/auth/totp/user/UserTOTPKey.java | 148 ++++++++++
.../auth/totp/user/UserVerificationService.java | 281 ++++++++++++++++++
9 files changed, 601 insertions(+), 443 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
index 835ba87..28e2380 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/TOTPAuthenticationProvider.java
@@ -19,9 +19,11 @@
package org.apache.guacamole.auth.totp;
+import org.apache.guacamole.auth.totp.user.UserVerificationService;
import com.google.inject.Guice;
import com.google.inject.Injector;
import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.totp.user.TOTPUserContext;
import org.apache.guacamole.net.auth.AuthenticatedUser;
import org.apache.guacamole.net.auth.AuthenticationProvider;
import org.apache.guacamole.net.auth.Credentials;
@@ -104,7 +106,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider {
// User has been verified, and authentication should be allowed to
// continue
- return context;
+ return new TOTPUserContext(context);
}
@@ -112,7 +114,7 @@ public class TOTPAuthenticationProvider implements AuthenticationProvider {
public UserContext redecorate(UserContext decorated, UserContext context,
AuthenticatedUser authenticatedUser, Credentials credentials)
throws GuacamoleException {
- return context;
+ return new TOTPUserContext(context);
}
@Override
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/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 e72beec..94b7232 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
@@ -19,6 +19,7 @@
package org.apache.guacamole.auth.totp;
+import org.apache.guacamole.auth.totp.user.UserVerificationService;
import com.google.inject.AbstractModule;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
deleted file mode 100644
index 3de3785..0000000
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserTOTPKey.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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;
-
-import java.security.SecureRandom;
-import java.util.Random;
-
-/**
- * The key used to generate TOTP codes for a particular user.
- */
-public class UserTOTPKey {
-
- /**
- * Secure source of random bytes.
- */
- private static final Random RANDOM = new SecureRandom();
-
- /**
- * The username of the user associated with this key.
- */
- private final String username;
-
- /**
- * Whether the associated secret key has been confirmed by the user. A key
- * is confirmed once the user has successfully entered a valid TOTP
- * derived from that key.
- */
- private boolean confirmed;
-
- /**
- * The base32-encoded TOTP key associated with the user.
- */
- private byte[] secret;
-
- /**
- * Generates the given number of random bytes.
- *
- * @param length
- * The number of random bytes to generate.
- *
- * @return
- * A new array of exactly the given number of random bytes.
- */
- private static byte[] generateBytes(int length) {
- byte[] bytes = new byte[length];
- RANDOM.nextBytes(bytes);
- return bytes;
- }
-
- /**
- * Creates a new, unconfirmed, randomly-generated TOTP key having the given
- * length.
- *
- * @param username
- * The username of the user associated with this key.
- *
- * @param length
- * The length of the key to generate, in bytes.
- */
- public UserTOTPKey(String username, int length) {
- this(username, generateBytes(length), false);
- }
-
- /**
- * Creates a new UserTOTPKey containing the given key and having the given
- * confirmed state.
- *
- * @param username
- * The username of the user associated with this key.
- *
- * @param secret
- * The raw binary secret key to be used to generate TOTP codes.
- *
- * @param confirmed
- * true if the user associated with the key has confirmed that they can
- * successfully generate the corresponding TOTP codes (the user has
- * been "enrolled"), false otherwise.
- */
- public UserTOTPKey(String username, byte[] secret, boolean confirmed) {
- this.username = username;
- this.confirmed = confirmed;
- this.secret = secret;
- }
-
- /**
- * Returns the username of the user associated with this key.
- *
- * @return
- * The username of the user associated with this key.
- */
- public String getUsername() {
- return username;
- }
-
- /**
- * Returns the raw binary secret key to be used to generate TOTP codes.
- *
- * @return
- * The raw binary secret key to be used to generate TOTP codes.
- */
- public byte[] getSecret() {
- return secret;
- }
-
- /**
- * Returns whether the user associated with the key has confirmed that they
- * can successfully generate the corresponding TOTP codes (the user has
- * been "enrolled").
- *
- * @return
- * true if the user has confirmed that they can successfully generate
- * the TOTP codes generated by this key, false otherwise.
- */
- public boolean isConfirmed() {
- return confirmed;
- }
-
- /**
- * Sets whether the user associated with the key has confirmed that they
- * can successfully generate the corresponding TOTP codes (the user has
- * been "enrolled").
- *
- * @param confirmed
- * true if the user has confirmed that they can successfully generate
- * the TOTP codes generated by this key, false otherwise.
- */
- public void setConfirmed(boolean confirmed) {
- this.confirmed = confirmed;
- }
-
-}
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/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
deleted file mode 100644
index 851bb94..0000000
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/UserVerificationService.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * 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;
-
-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;
-import java.util.Map;
-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;
-import org.apache.guacamole.net.auth.Credentials;
-import org.apache.guacamole.net.auth.User;
-import org.apache.guacamole.net.auth.UserContext;
-import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
-import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
-import org.apache.guacamole.totp.TOTPGenerator;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Service for verifying the identity of a user using TOTP.
- */
-public class UserVerificationService {
-
- /**
- * Logger for this class.
- */
- private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
-
- /**
- * The name of the user attribute which stores the TOTP key.
- */
- private static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret";
-
- /**
- * The name of the user attribute defines whether the TOTP key has been
- * confirmed by the user, and the user is thus fully enrolled.
- */
- private static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
-
- /**
- * BaseEncoding instance which decoded/encodes base32.
- */
- 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
- * storing the user does not support storage of the TOTP key, null is
- * returned.
- *
- * @param context
- * The UserContext of the user whose TOTP key should be retrieved.
- *
- * @param username
- * The username of the user associated with the given UserContext.
- *
- * @return
- * The TOTP key associated with the user having the given UserContext,
- * or null if the extension storing the user does not support storage
- * of the TOTP key.
- *
- * @throws GuacamoleException
- * If a new key is generated, but the extension storing the associated
- * user fails while updating the user account.
- */
- private UserTOTPKey getKey(UserContext context,
- String username) throws GuacamoleException {
-
- // Retrieve attributes from current user
- User self = context.self();
- Map<String, String> attributes = context.self().getAttributes();
-
- // If no key is defined, attempt to generate a new key
- String secret = attributes.get(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
- if (secret == null) {
-
- // Generate random key for user
- TOTPGenerator.Mode mode = confService.getMode();
- UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength());
- if (setKey(context, generated))
- return generated;
-
- // Fail if key cannot be set
- return null;
-
- }
-
- // Parse retrieved base32 key value
- byte[] key;
- try {
- key = BASE32.decode(secret);
- }
-
- // If key is not valid base32, warn but otherwise pretend the key does
- // not exist
- catch (IllegalArgumentException e) {
- logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier());
- logger.debug("TOTP key is not valid base32.", e);
- return null;
- }
-
- // Otherwise, parse value from attributes
- boolean confirmed = "true".equals(attributes.get(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
- return new UserTOTPKey(username, key, confirmed);
-
- }
-
- /**
- * Attempts to store the given TOTP key within the user account of the user
- * having the given UserContext. As not all extensions will support storage
- * of arbitrary attributes, this operation may fail.
- *
- * @param context
- * The UserContext associated with the user whose TOTP key is to be
- * stored.
- *
- * @param key
- * The TOTP key to store.
- *
- * @return
- * true if the TOTP key was successfully stored, false if the extension
- * handling storage does not support storage of the key.
- *
- * @throws GuacamoleException
- * If the extension handling storage fails internally while attempting
- * to update the user.
- */
- private boolean setKey(UserContext context, UserTOTPKey key)
- throws GuacamoleException {
-
- // Get mutable set of attributes
- User self = context.self();
- Map<String, String> attributes = new HashMap<String, String>();
-
- // Set/overwrite current TOTP key state
- attributes.put(TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret()));
- attributes.put(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false");
- self.setAttributes(attributes);
-
- // Confirm that attributes have actually been set
- Map<String, String> setAttributes = self.getAttributes();
- if (!setAttributes.containsKey(TOTP_KEY_SECRET_ATTRIBUTE_NAME)
- || !setAttributes.containsKey(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME))
- return false;
-
- // Update user object
- try {
- context.getUserDirectory().update(self);
- }
- catch (GuacamoleUnsupportedException e) {
- logger.debug("Extension storage for user is explicitly read-only. "
- + "Cannot update attributes to store TOTP key.", e);
- return false;
- }
-
- // TOTP key successfully stored/updated
- return true;
-
- }
-
- /**
- * Verifies the identity of the given user using TOTP. If a authentication
- * code from the user's TOTP device has not already been provided, a code is
- * requested in the form of additional expected credentials. Any provided
- * code is cryptographically verified. If no code is present, or the
- * received code is invalid, an exception is thrown.
- *
- * @param context
- * The UserContext provided for the user by another authentication
- * extension.
- *
- * @param authenticatedUser
- * The user whose identity should be verified using TOTP.
- *
- * @throws GuacamoleException
- * If required TOTP-specific configuration options are missing or
- * malformed, or if the user's identity cannot be verified.
- */
- public void verifyIdentity(UserContext context,
- AuthenticatedUser authenticatedUser) throws GuacamoleException {
-
- // Ignore anonymous users
- String username = authenticatedUser.getIdentifier();
- if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
- return;
-
- // Ignore users which do not have an associated key
- UserTOTPKey key = getKey(context, username);
- if (key == null)
- return;
-
- // Pull the original HTTP request used to authenticate
- Credentials credentials = authenticatedUser.getCredentials();
- HttpServletRequest request = credentials.getRequest();
-
- // Retrieve TOTP from request
- String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME);
-
- // 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()) {
- field.exposeKey(key);
- throw new GuacamoleInsufficientCredentialsException(
- "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
- Collections.<Field>singletonList(field)
- ));
- }
-
- // Otherwise simply request the user's authentication code
- throw new GuacamoleInsufficientCredentialsException(
- "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
- 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
- if (code.equals(totp.generate()) || code.equals(totp.previous())) {
-
- // Record key as confirmed, if it hasn't already been so recorded
- if (!key.isConfirmed()) {
- key.setConfirmed(true);
- setKey(context, key);
- }
-
- // User has been verified
- return;
-
- }
-
- }
- catch (InvalidKeyException e) {
- logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
- logger.debug("TOTP key is not valid.", e);
- }
-
- // Provided code is not valid
- throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED");
-
- }
-
-}
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/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 c3ca207..764fe95 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
@@ -32,7 +32,7 @@ import java.net.URI;
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.user.UserTOTPKey;
import org.apache.guacamole.auth.totp.conf.ConfigurationService;
import org.apache.guacamole.form.Field;
import org.codehaus.jackson.annotate.JsonProperty;
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java
new file mode 100644
index 0000000..4199d43
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUser.java
@@ -0,0 +1,102 @@
+/*
+ * 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.user;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.guacamole.net.auth.DelegatingUser;
+import org.apache.guacamole.net.auth.User;
+
+/**
+ * TOTP-specific User implementation which wraps a User from another extension,
+ * hiding and blocking access to the core attributes used by TOTP.
+ */
+public class TOTPUser extends DelegatingUser {
+
+ /**
+ * The name of the user attribute which stores the TOTP key.
+ */
+ public static final String TOTP_KEY_SECRET_ATTRIBUTE_NAME = "guac-totp-key-secret";
+
+ /**
+ * The name of the user attribute defines whether the TOTP key has been
+ * confirmed by the user, and the user is thus fully enrolled.
+ */
+ public static final String TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME = "guac-totp-key-confirmed";
+
+ /**
+ * The User object wrapped by this TOTPUser.
+ */
+ private final User undecorated;
+
+ /**
+ * Wraps the given User object, hiding and blocking access to the core
+ * attributes used by TOTP.
+ *
+ * @param user
+ * The User object to wrap.
+ */
+ public TOTPUser(User user) {
+ super(user);
+ this.undecorated = user;
+ }
+
+ /**
+ * Returns the User object wrapped by this TOTPUser.
+ *
+ * @return
+ * The wrapped User object.
+ */
+ public User getUndecorated() {
+ return undecorated;
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+
+ // Create independent, mutable copy of attributes
+ Map<String, String> attributes =
+ new HashMap<String, String>(super.getAttributes());
+
+ // Do not expose any TOTP-related attributes outside this extension
+ attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+ attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME);
+
+ // Expose only non-TOTP attributes
+ return attributes;
+
+ }
+
+ @Override
+ public void setAttributes(Map<String, String> attributes) {
+
+ // Create independent, mutable copy of attributes
+ attributes = new HashMap<String, String>(attributes);
+
+ // Do not expose any TOTP-related attributes outside this extension
+ attributes.remove(TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+ attributes.remove(TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME);
+
+ // Set only non-TOTP attributes
+ super.setAttributes(attributes);
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java
new file mode 100644
index 0000000..980bbf7
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/TOTPUserContext.java
@@ -0,0 +1,64 @@
+/*
+ * 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.user;
+
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.net.auth.DecoratingDirectory;
+import org.apache.guacamole.net.auth.DelegatingUserContext;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+
+/**
+ * TOTP-specific UserContext implementation which wraps the UserContext of
+ * some other extension, providing (or hiding) additional data.
+ */
+public class TOTPUserContext extends DelegatingUserContext {
+
+ /**
+ * Creates a new TOTPUserContext which wraps the given UserContext,
+ * providing (or hiding) additional TOTP-specific data.
+ *
+ * @param userContext
+ * The UserContext to wrap.
+ */
+ public TOTPUserContext(UserContext userContext) {
+ super(userContext);
+ }
+
+ @Override
+ public Directory<User> getUserDirectory() throws GuacamoleException {
+ return new DecoratingDirectory<User>(super.getUserDirectory()) {
+
+ @Override
+ protected User decorate(User object) {
+ return new TOTPUser(object);
+ }
+
+ @Override
+ protected User undecorate(User object) {
+ assert(object instanceof TOTPUser);
+ return ((TOTPUser) object).getUndecorated();
+ }
+
+ };
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java
new file mode 100644
index 0000000..d7bc903
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserTOTPKey.java
@@ -0,0 +1,148 @@
+/*
+ * 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.user;
+
+import java.security.SecureRandom;
+import java.util.Random;
+
+/**
+ * The key used to generate TOTP codes for a particular user.
+ */
+public class UserTOTPKey {
+
+ /**
+ * Secure source of random bytes.
+ */
+ private static final Random RANDOM = new SecureRandom();
+
+ /**
+ * The username of the user associated with this key.
+ */
+ private final String username;
+
+ /**
+ * Whether the associated secret key has been confirmed by the user. A key
+ * is confirmed once the user has successfully entered a valid TOTP
+ * derived from that key.
+ */
+ private boolean confirmed;
+
+ /**
+ * The base32-encoded TOTP key associated with the user.
+ */
+ private byte[] secret;
+
+ /**
+ * Generates the given number of random bytes.
+ *
+ * @param length
+ * The number of random bytes to generate.
+ *
+ * @return
+ * A new array of exactly the given number of random bytes.
+ */
+ private static byte[] generateBytes(int length) {
+ byte[] bytes = new byte[length];
+ RANDOM.nextBytes(bytes);
+ return bytes;
+ }
+
+ /**
+ * Creates a new, unconfirmed, randomly-generated TOTP key having the given
+ * length.
+ *
+ * @param username
+ * The username of the user associated with this key.
+ *
+ * @param length
+ * The length of the key to generate, in bytes.
+ */
+ public UserTOTPKey(String username, int length) {
+ this(username, generateBytes(length), false);
+ }
+
+ /**
+ * Creates a new UserTOTPKey containing the given key and having the given
+ * confirmed state.
+ *
+ * @param username
+ * The username of the user associated with this key.
+ *
+ * @param secret
+ * The raw binary secret key to be used to generate TOTP codes.
+ *
+ * @param confirmed
+ * true if the user associated with the key has confirmed that they can
+ * successfully generate the corresponding TOTP codes (the user has
+ * been "enrolled"), false otherwise.
+ */
+ public UserTOTPKey(String username, byte[] secret, boolean confirmed) {
+ this.username = username;
+ this.confirmed = confirmed;
+ this.secret = secret;
+ }
+
+ /**
+ * Returns the username of the user associated with this key.
+ *
+ * @return
+ * The username of the user associated with this key.
+ */
+ public String getUsername() {
+ return username;
+ }
+
+ /**
+ * Returns the raw binary secret key to be used to generate TOTP codes.
+ *
+ * @return
+ * The raw binary secret key to be used to generate TOTP codes.
+ */
+ public byte[] getSecret() {
+ return secret;
+ }
+
+ /**
+ * Returns whether the user associated with the key has confirmed that they
+ * can successfully generate the corresponding TOTP codes (the user has
+ * been "enrolled").
+ *
+ * @return
+ * true if the user has confirmed that they can successfully generate
+ * the TOTP codes generated by this key, false otherwise.
+ */
+ public boolean isConfirmed() {
+ return confirmed;
+ }
+
+ /**
+ * Sets whether the user associated with the key has confirmed that they
+ * can successfully generate the corresponding TOTP codes (the user has
+ * been "enrolled").
+ *
+ * @param confirmed
+ * true if the user has confirmed that they can successfully generate
+ * the TOTP codes generated by this key, false otherwise.
+ */
+ public void setConfirmed(boolean confirmed) {
+ this.confirmed = confirmed;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/guacamole-client/blob/96e3d029/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
new file mode 100644
index 0000000..8264efd
--- /dev/null
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
@@ -0,0 +1,281 @@
+/*
+ * 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.user;
+
+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;
+import java.util.Map;
+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;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.UserContext;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+import org.apache.guacamole.totp.TOTPGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service for verifying the identity of a user using TOTP.
+ */
+public class UserVerificationService {
+
+ /**
+ * Logger for this class.
+ */
+ private final Logger logger = LoggerFactory.getLogger(UserVerificationService.class);
+
+ /**
+ * BaseEncoding instance which decoded/encodes base32.
+ */
+ 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
+ * storing the user does not support storage of the TOTP key, null is
+ * returned.
+ *
+ * @param context
+ * The UserContext of the user whose TOTP key should be retrieved.
+ *
+ * @param username
+ * The username of the user associated with the given UserContext.
+ *
+ * @return
+ * The TOTP key associated with the user having the given UserContext,
+ * or null if the extension storing the user does not support storage
+ * of the TOTP key.
+ *
+ * @throws GuacamoleException
+ * If a new key is generated, but the extension storing the associated
+ * user fails while updating the user account.
+ */
+ private UserTOTPKey getKey(UserContext context,
+ String username) throws GuacamoleException {
+
+ // Retrieve attributes from current user
+ User self = context.self();
+ Map<String, String> attributes = context.self().getAttributes();
+
+ // If no key is defined, attempt to generate a new key
+ String secret = attributes.get(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME);
+ if (secret == null) {
+
+ // Generate random key for user
+ TOTPGenerator.Mode mode = confService.getMode();
+ UserTOTPKey generated = new UserTOTPKey(username,mode.getRecommendedKeyLength());
+ if (setKey(context, generated))
+ return generated;
+
+ // Fail if key cannot be set
+ return null;
+
+ }
+
+ // Parse retrieved base32 key value
+ byte[] key;
+ try {
+ key = BASE32.decode(secret);
+ }
+
+ // If key is not valid base32, warn but otherwise pretend the key does
+ // not exist
+ catch (IllegalArgumentException e) {
+ logger.warn("TOTP key of user \"{}\" is not valid base32.", self.getIdentifier());
+ logger.debug("TOTP key is not valid base32.", e);
+ return null;
+ }
+
+ // Otherwise, parse value from attributes
+ boolean confirmed = "true".equals(attributes.get(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME));
+ return new UserTOTPKey(username, key, confirmed);
+
+ }
+
+ /**
+ * Attempts to store the given TOTP key within the user account of the user
+ * having the given UserContext. As not all extensions will support storage
+ * of arbitrary attributes, this operation may fail.
+ *
+ * @param context
+ * The UserContext associated with the user whose TOTP key is to be
+ * stored.
+ *
+ * @param key
+ * The TOTP key to store.
+ *
+ * @return
+ * true if the TOTP key was successfully stored, false if the extension
+ * handling storage does not support storage of the key.
+ *
+ * @throws GuacamoleException
+ * If the extension handling storage fails internally while attempting
+ * to update the user.
+ */
+ private boolean setKey(UserContext context, UserTOTPKey key)
+ throws GuacamoleException {
+
+ // Get mutable set of attributes
+ User self = context.self();
+ Map<String, String> attributes = new HashMap<String, String>();
+
+ // Set/overwrite current TOTP key state
+ attributes.put(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME, BASE32.encode(key.getSecret()));
+ attributes.put(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME, key.isConfirmed() ? "true" : "false");
+ self.setAttributes(attributes);
+
+ // Confirm that attributes have actually been set
+ Map<String, String> setAttributes = self.getAttributes();
+ if (!setAttributes.containsKey(TOTPUser.TOTP_KEY_SECRET_ATTRIBUTE_NAME)
+ || !setAttributes.containsKey(TOTPUser.TOTP_KEY_CONFIRMED_ATTRIBUTE_NAME))
+ return false;
+
+ // Update user object
+ try {
+ context.getUserDirectory().update(self);
+ }
+ catch (GuacamoleUnsupportedException e) {
+ logger.debug("Extension storage for user is explicitly read-only. "
+ + "Cannot update attributes to store TOTP key.", e);
+ return false;
+ }
+
+ // TOTP key successfully stored/updated
+ return true;
+
+ }
+
+ /**
+ * Verifies the identity of the given user using TOTP. If a authentication
+ * code from the user's TOTP device has not already been provided, a code is
+ * requested in the form of additional expected credentials. Any provided
+ * code is cryptographically verified. If no code is present, or the
+ * received code is invalid, an exception is thrown.
+ *
+ * @param context
+ * The UserContext provided for the user by another authentication
+ * extension.
+ *
+ * @param authenticatedUser
+ * The user whose identity should be verified using TOTP.
+ *
+ * @throws GuacamoleException
+ * If required TOTP-specific configuration options are missing or
+ * malformed, or if the user's identity cannot be verified.
+ */
+ public void verifyIdentity(UserContext context,
+ AuthenticatedUser authenticatedUser) throws GuacamoleException {
+
+ // Ignore anonymous users
+ String username = authenticatedUser.getIdentifier();
+ if (username.equals(AuthenticatedUser.ANONYMOUS_IDENTIFIER))
+ return;
+
+ // Ignore users which do not have an associated key
+ UserTOTPKey key = getKey(context, username);
+ if (key == null)
+ return;
+
+ // Pull the original HTTP request used to authenticate
+ Credentials credentials = authenticatedUser.getCredentials();
+ HttpServletRequest request = credentials.getRequest();
+
+ // Retrieve TOTP from request
+ String code = request.getParameter(AuthenticationCodeField.PARAMETER_NAME);
+
+ // 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()) {
+ field.exposeKey(key);
+ throw new GuacamoleInsufficientCredentialsException(
+ "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
+ Collections.<Field>singletonList(field)
+ ));
+ }
+
+ // Otherwise simply request the user's authentication code
+ throw new GuacamoleInsufficientCredentialsException(
+ "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
+ 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
+ if (code.equals(totp.generate()) || code.equals(totp.previous())) {
+
+ // Record key as confirmed, if it hasn't already been so recorded
+ if (!key.isConfirmed()) {
+ key.setConfirmed(true);
+ setKey(context, key);
+ }
+
+ // User has been verified
+ return;
+
+ }
+
+ }
+ catch (InvalidKeyException e) {
+ logger.warn("User \"{}\" is associated with an invalid TOTP key.", username);
+ logger.debug("TOTP key is not valid.", e);
+ }
+
+ // Provided code is not valid
+ throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED");
+
+ }
+
+}