You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@guacamole.apache.org by mj...@apache.org on 2017/10/27 17:50:46 UTC

[02/24] incubator-guacamole-client git commit: GUACAMOLE-362: Add support for CAS ClearPass, parsing and decrypting the password and assigning it a token.

GUACAMOLE-362: Add support for CAS ClearPass, parsing and decrypting the password and assigning it a token.


Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/1c4831dd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/1c4831dd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/1c4831dd

Branch: refs/heads/staging/0.9.14-incubating
Commit: 1c4831dd5191a9043edff7b771fef919a56281db
Parents: d955fbe
Author: Nick Couchman <vn...@apache.org>
Authored: Wed Aug 16 11:58:26 2017 -0400
Committer: Nick Couchman <ni...@yahoo.com>
Committed: Fri Oct 27 13:05:12 2017 -0400

----------------------------------------------------------------------
 .../auth/cas/AuthenticationProviderService.java | 103 ++++++++++++++++++-
 .../auth/cas/conf/CASGuacamoleProperties.java   |  13 +++
 .../auth/cas/conf/ConfigurationService.java     |  17 +++
 .../cas/ticket/TicketValidationService.java     |   9 +-
 4 files changed, 134 insertions(+), 8 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/1c4831dd/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java
index f3870a6..0422d30 100644
--- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java
+++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java
@@ -21,11 +21,26 @@ package org.apache.guacamole.auth.cas;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.spec.KeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
 import java.util.Arrays;
 import java.util.Enumeration;
+import javax.crypto.Cipher;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
+import javax.xml.bind.DatatypeConverter;
 import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.environment.LocalEnvironment;
 import org.apache.guacamole.form.Field;
 import org.apache.guacamole.net.auth.Credentials;
 import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
@@ -34,6 +49,9 @@ import org.apache.guacamole.auth.cas.conf.ConfigurationService;
 import org.apache.guacamole.auth.cas.form.CASTicketField;
 import org.apache.guacamole.auth.cas.ticket.TicketValidationService;
 import org.apache.guacamole.auth.cas.user.AuthenticatedUser;
+import org.jasig.cas.client.authentication.AttributePrincipal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Service providing convenience functions for the CAS AuthenticationProvider
@@ -42,6 +60,11 @@ import org.apache.guacamole.auth.cas.user.AuthenticatedUser;
 public class AuthenticationProviderService {
 
     /**
+     * Logger for this class.
+     */
+    private static final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class);
+
+    /**
      * Service for retrieving CAS configuration information.
      */
     @Inject
@@ -83,7 +106,13 @@ public class AuthenticationProviderService {
             String ticket = request.getParameter(CASTicketField.PARAMETER_NAME);
             if (ticket != null) {
                 AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
-                authenticatedUser.init(ticketService.processUsername(ticket), credentials);
+                AttributePrincipal principal = ticketService.validateTicket(ticket);
+                String username = principal.getName();
+                credentials.setUsername(username);
+                String clearPass = decryptPassword(principal.getAttributes().get("credential").toString());
+                if (clearPass != null && !clearPass.isEmpty())
+                    credentials.setPassword(clearPass);
+                authenticatedUser.init(username, credentials);
                 return authenticatedUser;
             }
         }
@@ -105,4 +134,76 @@ public class AuthenticationProviderService {
 
     }
 
+    /**
+     * Takes an encrypted string representing a password provided by
+     * the CAS ClearPass service and decrypts it using the private
+     * key configured for this extension.  Returns null if it is
+     * unable to decrypt the password.
+     *
+     * @param encryptedPassword
+     *     A string with the encrypted password provided by the
+     *     CAS service.
+     *
+     * @return
+     *     The decrypted password, or null if it is unable to
+     *     decrypt the password.
+     * @throws GuacamoleException
+     *     If unable to get Guacamole configuration data
+     */
+    private final String decryptPassword(String encryptedPassword)
+            throws GuacamoleException {
+
+        // If we get nothing, we return nothing.
+        if (encryptedPassword == null || encryptedPassword.isEmpty())
+            return null;
+
+        try {
+
+            // Open and read the file specified in the configuration.
+            File keyFile = new File(new LocalEnvironment().getGuacamoleHome(), confService.getClearpassKey().toString());
+            InputStream keyInput = new BufferedInputStream(new FileInputStream(keyFile));
+            final byte[] keyBytes = new byte[(int) keyFile.length()];
+            keyInput.read(keyBytes);
+            keyInput.close();
+      
+            // Set up decryption infrastructure
+            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+            KeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); 
+            final PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
+            final Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
+            final byte[] pass64 = DatatypeConverter.parseBase64Binary(encryptedPassword);
+            cipher.init(Cipher.DECRYPT_MODE, privateKey);
+
+            // Decrypt and return a new string.
+            final byte[] cipherData = cipher.doFinal(pass64);
+            return new String(cipherData);
+        }
+        catch (FileNotFoundException e) {
+            logger.error("ClearPass key file not found, password will not be decrypted.");
+            logger.debug("Error locating the ClearPass key file: {}", e.getMessage());
+            return null;
+        }
+        catch (IOException e) {
+            logger.error("Error reading ClearPass key file, password will not be decrypted.");
+            logger.debug("Error reading the ClearPass key file: {}", e.getMessage());
+            return null;
+        }
+        catch (NoSuchAlgorithmException e) {
+            logger.error("Unable to find the specified algorithm, password will not be decrypted.");
+            logger.debug("Algorithm was not found: {}", e.getMessage());
+            return null;
+        }
+        catch (InvalidKeyException e) {
+            logger.error("Invalid key was loaded, password will not be decrypted.");
+            logger.debug("The loaded key was invalid: {}", e.getMessage());
+            return null;
+        }
+        catch (Throwable t) {
+            logger.error("Error decrypting password, it will not be available as a token.");
+            logger.debug("Error in one of the components to decrypt the password: {}", t.getMessage());
+            return null;
+        }
+
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/1c4831dd/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java
index 00fb901..410e848 100644
--- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java
+++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/CASGuacamoleProperties.java
@@ -19,6 +19,7 @@
 
 package org.apache.guacamole.auth.cas.conf;
 
+import org.apache.guacamole.properties.FileGuacamoleProperty;
 import org.apache.guacamole.properties.StringGuacamoleProperty;
 
 /**
@@ -57,4 +58,16 @@ public class CASGuacamoleProperties {
 
     };
 
+    /**
+     * The location of the private key file used to retrieve the
+     * password if CAS is configured to support ClearPass.
+     */
+    public static final FileGuacamoleProperty CAS_CLEARPASS_KEY =
+            new FileGuacamoleProperty() {
+
+        @Override
+        public String getName() { return "cas-clearpass-key"; }
+
+    };
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/1c4831dd/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java
index 17be2d3..b2d74d5 100644
--- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java
+++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/conf/ConfigurationService.java
@@ -20,6 +20,7 @@
 package org.apache.guacamole.auth.cas.conf;
 
 import com.google.inject.Inject;
+import java.io.File;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.environment.Environment;
 
@@ -68,4 +69,20 @@ public class ConfigurationService {
         return environment.getRequiredProperty(CASGuacamoleProperties.CAS_REDIRECT_URI);
     }
 
+    /**
+     * Returns the path to the file that contains the private key
+     * used to decrypt the credential that is sent encrypted by CAS,
+     * or null if no key is defined.
+     *
+     * @return
+     *     The path to the private key to decrypt the ClearPass
+     *     credential returned by CAS.
+     *
+     * @throws GuacamoleException
+     *     If guacamole.properties cannot be parsed.
+     */
+    public File getClearpassKey() throws GuacamoleException {
+        return environment.getProperty(CASGuacamoleProperties.CAS_CLEARPASS_KEY);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/1c4831dd/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java
----------------------------------------------------------------------
diff --git a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java
index 9644c68..fca2ccf 100644
--- a/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java
+++ b/extensions/guacamole-auth-cas/src/main/java/org/apache/guacamole/auth/cas/ticket/TicketValidationService.java
@@ -57,9 +57,7 @@ public class TicketValidationService {
      *     If the ID ticket is not valid, the username claim type is missing, or
      *     guacamole.properties could not be parsed.
      */
-    public String processUsername(String ticket) throws GuacamoleException {
-
-        AttributePrincipal principal = null;
+    public AttributePrincipal validateTicket(String ticket) throws GuacamoleException {
 
         // Retrieve the configured CAS URL, establish a ticket validator,
         // and then attempt to validate the supplied ticket.  If that succeeds,
@@ -70,15 +68,12 @@ public class TicketValidationService {
         try {
             String confRedirectURI = confService.getRedirectURI();
             Assertion a = validator.validate(ticket, confRedirectURI);
-            principal = a.getPrincipal();
+            return a.getPrincipal();
         } 
         catch (TicketValidationException e) {
             throw new GuacamoleException("Ticket validation failed.", e);
         }
 
-        // Return the principal name as the username.
-        return principal.getName();
-
     }
 
 }