You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@guacamole.apache.org by GitBox <gi...@apache.org> on 2020/11/29 14:52:33 UTC

[GitHub] [guacamole-client] necouchman commented on a change in pull request #576: GUACAMOLE-1218: Include "guacamole-auth-json" within Apache Guacamole.

necouchman commented on a change in pull request #576:
URL: https://github.com/apache/guacamole-client/pull/576#discussion_r530374243



##########
File path: extensions/guacamole-auth-json/README.md
##########
@@ -0,0 +1,189 @@
+guacamole-auth-json
+===================
+
+guacamole-auth-json is an authentication extension for [Apache
+Guacamole](http://guacamole.apache.org/) which authenticates users using JSON
+which has been signed using **HMAC/SHA-256** and encrypted with **128-bit AES
+in CBC mode**. This JSON contains all information describing the user being
+authenticated, as well as any connections they have access to.
+
+Configuring Guacamole to accept encrypted JSON
+----------------------------------------------
+
+To verify and decrypt the received signed and encrypted JSON, a secret key must
+be generated which will be shared by both the Guacamole server and systems that
+will generate the JSON data. As guacamole-auth-json uses 128-bit AES, this key
+must be 128 bits.
+
+An easy way of generating such a key is to echo a passphrase through the
+"md5sum" utility. This is the technique OpenSSL itself uses to generate 128-bit
+keys from passphrases. For example:
+
+    $ echo -n "ThisIsATest" | md5sum
+    4c0b569e4c96df157eee1b65dd0e4d41  -
+
+The generated key must then be saved within `guacamole.properties` as the full
+32-digit hex value using the `json-secret-key` property:
+
+    json-secret-key: 4c0b569e4c96df157eee1b65dd0e4d41
+
+JSON format
+-----------
+
+The general format of the JSON (prior to being encrypted, signed, and sent to
+Guacamole), is as follows:
+
+    {
+
+        "username" : "arbitraryUsername",
+        "expires" : TIMESTAMP,
+        "connections" : {
+
+            "Connection Name" : {
+                "protocol" : "PROTOCOL",
+                "parameters" : {
+                    "name1" : "value1",
+                    "name2" : "value2",
+                    ...
+                }
+            },
+
+            ...
+
+        }
+
+    }
+
+where `TIMESTAMP` is a standard UNIX epoch timestamp with millisecond
+resolution (the number of milliseconds since midnight of January 1, 1970 UTC)
+and `PROTOCOL` is the internal name of any of Guacamole's supported protocols,
+such as `vnc`, `rdp`, or `ssh`.
+
+The JSON will cease to be accepted as valid after the server time passes the
+timestamp. If no timestamp is specified, the data will not expire.
+
+The top-level JSON object which must be submitted to Guacamole has the
+following properties:
+
+Property name | Type     | Description
+--------------|----------|------------
+`username`    | `string` | The unique username of the user authenticated by the JSON. If the user is anonymous, this should be the empty string (`""`).
+`expires`     | `number` | The absolute time after which the JSON should no longer be accepted, even if the signature is valid, as a standard UNIX epoch timestamp with millisecond resolution (the number of milliseconds since midnight of January 1, 1970 UTC).
+`connections` | `object` | The set of connections which should be exposed to the user by their corresponding, unique names. If no connections will be exposed to the user, this can simply be an empty object (`{}`).
+
+Each normal connection defined within each submitted JSON object has the
+following properties:
+
+Property name | Type     | Description
+--------------|----------|------------
+`id`          | `string` | An optional opaque value which uniquely identifies this connection across all other connections which may be active at any given time. This property is only required if you wish to allow the connection to be shared or shadowed.
+`protocol`    | `string` | The internal name of a supported protocol, such as `vnc`, `rdp`, or `ssh`.
+`parameters`  | `object` | An object representing the connection parameter name/value pairs to apply to the connection, as documented in the [Guacamole manual](https://guacamole.apache.org/doc/gug/configuring-guacamole.html#connection-configuration).
+
+Connections which share or shadow other connections use a `join` property
+instead of a `protocol` property, where `join` contains the value of the `id`
+property of the connection being joined:
+
+Property name | Type     | Description
+--------------|----------|------------
+`id`          | `string` | An optional opaque value which uniquely identifies this connection across all other connections which may be active at any given time. This property is only required if you wish to allow the connection to be shared or shadowed. (Yes, a connection which shadows another connection may itself be shadowed.)
+`join`        | `string` | The opaque ID given within the `id` property of the connection being joined (shared / shadowed).
+`parameters`  | `object` | An object representing the connection parameter name/value pairs to apply to the connection, as documented in the [Guacamole manual](https://guacamole.apache.org/doc/gug/configuring-guacamole.html#connection-configuration). Most of the connection configuration is inherited from the connection being joined. In general, the only property relevant to joining connections is `read-only`.
+
+If a connection is configured to join another connection, that connection will
+only be usable if the connection being joined is currently active. If two
+connections are established having the same `id` value, only the last
+connection will be joinable using the given `id`.
+
+Generating encrypted JSON
+-------------------------
+
+To authenticate a user with the above JSON format, the JSON must be both signed
+and encrypted using the same 128-bit secret key specified with the
+`json-secret-key` within `guacamole.properties`:
+
+1. Generate JSON in the format described above
+2. Sign the JSON using the secret key (the same 128-bit key stored within
+   `guacamole.properties` with the `json-secret-key` property) with
+   **HMAC/SHA-256**. Prepend the binary result of the signing process to the
+   plaintext JSON that was signed.
+3. Encrypt the result of (2) above using **AES in CBC mode**, with the initial
+   vector (IV) set to all zero bytes.
+4. Encode the encrypted result using base64.
+5. POST the encrypted result to the `/api/tokens` REST endpoint as the value of
+   an HTTP parameter named `data` (or include it in the URL of any Guacamole
+   page as a query parameter named `data`).

Review comment:
       I'm not sure if it's worth noting, here, or not, that query parameters are (more) limited in the amount of data they can accept (versus POSTing HTTP parameters), so if the size of the JSON data is sufficiently large the GET query parameter method may not work. I ran into this when writing the SAML module. It may be too much detail for this, just popped into my mind.

##########
File path: extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/CryptoService.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.json;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+
+/**
+ * Service for handling cryptography-related operations, such as decrypting
+ * encrypted data.
+ */
+public class CryptoService {
+
+    /**
+     * The length of all signatures, in bytes.
+     */
+    public static final int SIGNATURE_LENGTH = 32;
+
+    /**
+     * The name of the key generation algorithm used for decryption.
+     */
+    private static final String DECRYPTION_KEY_GENERATION_ALGORITHM_NAME = "AES";
+
+    /**
+     * The name of the cipher transformation that should be used to decrypt any
+     * String provided to decrypt().
+     */
+    private static final String DECRYPTION_CIPHER_NAME = "AES/CBC/PKCS5Padding";
+
+    /**
+     * The name of the key generation algorithm used for verifying signatures.
+     */
+    private static final String SIGNATURE_KEY_GENERATION_ALGORITHM_NAME = "HmacSHA256";
+
+    /**
+     * The name of the MAC algorithm used for verifying signatures.
+     */
+    private static final String SIGNATURE_MAC_ALGORITHM_NAME = "HmacSHA256";
+
+    /**
+     * IV which is all null bytes (all binary zeroes). Usually, using a null IV
+     * is a horrible idea. As our plaintext will always be prepended with the
+     * HMAC signature of the rest of the message, we are effectively using the
+     * HMAC signature itself as the IV. For our purposes, where the encrypted
+     * value becomes an authentication token, this is OK.
+     */
+    private static final IvParameterSpec NULL_IV = new IvParameterSpec(new byte[] {
+        0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0
+    });
+
+    /**
+     * Creates a new key suitable for decryption using the provided raw key
+     * bytes. The algorithm used to generate this key is dictated by
+     * DECRYPTION_KEY_GENERATION_ALGORITHM_NAME and must match the algorithm
+     * used by decrypt().
+     *
+     * @param keyBytes
+     *     The raw bytes from which the encryption/decryption key should be
+     *     generated.
+     *
+     * @return
+     *     A new key suitable for encryption or decryption, generated from the
+     *     given bytes.
+     */
+    public SecretKey createEncryptionKey(byte[] keyBytes) {
+        return new SecretKeySpec(keyBytes, DECRYPTION_KEY_GENERATION_ALGORITHM_NAME);
+    }
+
+    /**
+     * Creates a new key suitable for signature verification using the provided
+     * raw key bytes. The algorithm used to generate this key is dictated by
+     * SIGNATURE_KEY_GENERATION_ALGORITHM_NAME and must match the algorithm
+     * used by sign().
+     *
+     * @param keyBytes
+     *     The raw bytes from which the signature verification key should be
+     *     generated.
+     *
+     * @return
+     *     A new key suitable for signature verification, generated from the
+     *     given bytes.
+     */
+    public SecretKey createSignatureKey(byte[] keyBytes) {
+        return new SecretKeySpec(keyBytes, SIGNATURE_KEY_GENERATION_ALGORITHM_NAME);
+    }
+
+    /**
+     * Decrypts the given ciphertext using the provided key, returning the
+     * resulting plaintext. If any error occurs during decryption at all, a
+     * GuacamoleException is thrown. The IV used for the decryption process is
+     * a null IV (all binary zeroes).
+     *
+     * @param key
+     *     The key to use to decrypt the provided ciphertext.
+     *
+     * @param cipherText
+     *     The ciphertext to decrypt.
+     *
+     * @return
+     *     The plaintext which results from decrypting the ciphertext with the
+     *     provided key.
+     *
+     * @throws GuacamoleException
+     *     If any error at all occurs during decryption.
+     */
+    public byte[] decrypt(Key key, byte[] cipherText) throws GuacamoleException {
+
+        try {
+
+            // Init cipher for descryption using secret key
+            Cipher cipher = Cipher.getInstance(DECRYPTION_CIPHER_NAME);
+            cipher.init(Cipher.DECRYPT_MODE, key, NULL_IV);
+
+            // Perform decryption
+            return cipher.doFinal(cipherText);
+
+        }
+
+        // Rethrow all decryption failures identically
+        catch (InvalidAlgorithmParameterException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (NoSuchPaddingException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (InvalidKeyException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (IllegalBlockSizeException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (BadPaddingException e) {
+            throw new GuacamoleServerException(e);
+        }

Review comment:
       Any reason not to use the multi-catch, here?

##########
File path: extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/UserDataService.java
##########
@@ -0,0 +1,373 @@
+/*
+ * 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.json.user;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.bind.DatatypeConverter;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.json.ConfigurationService;
+import org.apache.guacamole.auth.json.CryptoService;
+import org.apache.guacamole.auth.json.RequestValidationService;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
+import org.apache.guacamole.net.auth.simple.SimpleDirectory;
+import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet;
+import org.apache.guacamole.net.auth.simple.SimpleUser;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service for deriving Guacamole extension API data from UserData objects.
+ */
+@Singleton
+public class UserDataService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(UserDataService.class);
+
+    /**
+     * ObjectMapper for deserializing UserData objects.
+     */
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    /**
+     * Blacklist of single-use user data objects which have already been used.
+     */
+    private final UserDataBlacklist blacklist = new UserDataBlacklist();

Review comment:
       Any thoughts/feelings about avoiding use of the term "blacklist", both here and as the name of the `UserDataBlacklist` class?
   
   https://thenextweb.com/dd/2020/07/13/linux-kernel-will-no-longer-use-terms-blacklist-and-slave/
   
   I'm not one to get overly worked up about this one way or the other, and it is certainly still broadly-used in the computing realm in ways that don't necessarily have racial meaning or implication; on the other hand, given the history of the term and, as noted in the page above, the fact that there are some movements to try to move away from that, maybe we should do the same?

##########
File path: extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/AuthenticationProviderService.java
##########
@@ -0,0 +1,116 @@
+/*
+ * 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.json;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.json.user.AuthenticatedUser;
+import org.apache.guacamole.auth.json.user.UserContext;
+import org.apache.guacamole.auth.json.user.UserData;
+import org.apache.guacamole.auth.json.user.UserDataService;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+
+/**
+ * Service providing convenience functions for the JSONAuthenticationProvider.
+ */
+public class AuthenticationProviderService {
+
+    /**
+     * Service for deriving Guacamole extension API data from UserData objects.
+     */
+    @Inject
+    private UserDataService userDataService;
+
+    /**
+     * Provider for AuthenticatedUser objects.
+     */
+    @Inject
+    private Provider<AuthenticatedUser> authenticatedUserProvider;
+
+    /**
+     * Provider for UserContext objects.
+     */
+    @Inject
+    private Provider<UserContext> userContextProvider;
+
+    /**
+     * Returns an AuthenticatedUser representing the user authenticated by the
+     * given credentials.
+     *
+     * @param credentials
+     *     The credentials to use for authentication.
+     *
+     * @return
+     *     An AuthenticatedUser representing the user authenticated by the
+     *     given credentials.
+     *
+     * @throws GuacamoleException
+     *     If an error occurs while authenticating the user, or if access is
+     *     denied.
+     */
+    public AuthenticatedUser authenticateUser(Credentials credentials)
+            throws GuacamoleException {
+
+        // Pull UserData from credentials, if possible
+        UserData userData = userDataService.fromCredentials(credentials);
+        if (userData == null)
+            throw new GuacamoleInvalidCredentialsException("Permission denied.", CredentialsInfo.EMPTY);
+
+        // Produce AuthenticatedUser associated with derived UserData
+        AuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
+        authenticatedUser.init(credentials, userData);
+        return authenticatedUser;
+
+    }
+
+    /**
+     * Returns a UserContext object initialized with data accessible to the
+     * given AuthenticatedUser.
+     *
+     * @param authenticatedUser
+     *     The AuthenticatedUser to retrieve data for.
+     *
+     * @return
+     *     A UserContext object initialized with data accessible to the given
+     *     AuthenticatedUser.
+     *
+     * @throws GuacamoleException
+     *     If the UserContext cannot be created due to an error.
+     */
+    public UserContext getUserContext(org.apache.guacamole.net.auth.AuthenticatedUser authenticatedUser)
+            throws GuacamoleException {
+
+        // The JSONAuthenticationProvider only provides data for users it has
+        // authenticated itself
+        if (!(authenticatedUser instanceof AuthenticatedUser))
+            return null;

Review comment:
       If I'm reading this code/comment correctly, it is probably worth mentioning in the documentation - either the `README.md` file within this extension, or whatever GUG chapter is generated for this later (or both) - that, by design, this module will essentially not "stack" with other authentication modules.

##########
File path: extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/user/UserDataService.java
##########
@@ -0,0 +1,373 @@
+/*
+ * 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.json.user;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.bind.DatatypeConverter;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.auth.json.ConfigurationService;
+import org.apache.guacamole.auth.json.CryptoService;
+import org.apache.guacamole.auth.json.RequestValidationService;
+import org.apache.guacamole.net.auth.Connection;
+import org.apache.guacamole.net.auth.Credentials;
+import org.apache.guacamole.net.auth.Directory;
+import org.apache.guacamole.net.auth.User;
+import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
+import org.apache.guacamole.net.auth.simple.SimpleDirectory;
+import org.apache.guacamole.net.auth.simple.SimpleObjectPermissionSet;
+import org.apache.guacamole.net.auth.simple.SimpleUser;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Service for deriving Guacamole extension API data from UserData objects.
+ */
+@Singleton
+public class UserDataService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(UserDataService.class);

Review comment:
       As with other class above, any reason this should or should not be `static`, in keeping consistency with other Guacamole code? I have no particular preference, just wondering.

##########
File path: extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/RequestValidationService.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.json;
+
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.Collection;
+import javax.servlet.http.HttpServletRequest;
+import org.apache.guacamole.GuacamoleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.web.util.matcher.IpAddressMatcher;
+
+/**
+ * Service for testing the validity of received HTTP requests.
+ */
+public class RequestValidationService {
+
+    /**
+     * Logger for this class.
+     */
+    private final Logger logger = LoggerFactory.getLogger(RequestValidationService.class);

Review comment:
       I think the `Logger` implementations in most other classes are also `static` - any reason this deviates from that practice?

##########
File path: extensions/guacamole-auth-json/src/main/java/org/apache/guacamole/auth/json/CryptoService.java
##########
@@ -0,0 +1,215 @@
+/*
+ * 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.json;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import org.apache.guacamole.GuacamoleException;
+import org.apache.guacamole.GuacamoleServerException;
+
+/**
+ * Service for handling cryptography-related operations, such as decrypting
+ * encrypted data.
+ */
+public class CryptoService {
+
+    /**
+     * The length of all signatures, in bytes.
+     */
+    public static final int SIGNATURE_LENGTH = 32;
+
+    /**
+     * The name of the key generation algorithm used for decryption.
+     */
+    private static final String DECRYPTION_KEY_GENERATION_ALGORITHM_NAME = "AES";
+
+    /**
+     * The name of the cipher transformation that should be used to decrypt any
+     * String provided to decrypt().
+     */
+    private static final String DECRYPTION_CIPHER_NAME = "AES/CBC/PKCS5Padding";
+
+    /**
+     * The name of the key generation algorithm used for verifying signatures.
+     */
+    private static final String SIGNATURE_KEY_GENERATION_ALGORITHM_NAME = "HmacSHA256";
+
+    /**
+     * The name of the MAC algorithm used for verifying signatures.
+     */
+    private static final String SIGNATURE_MAC_ALGORITHM_NAME = "HmacSHA256";
+
+    /**
+     * IV which is all null bytes (all binary zeroes). Usually, using a null IV
+     * is a horrible idea. As our plaintext will always be prepended with the
+     * HMAC signature of the rest of the message, we are effectively using the
+     * HMAC signature itself as the IV. For our purposes, where the encrypted
+     * value becomes an authentication token, this is OK.
+     */
+    private static final IvParameterSpec NULL_IV = new IvParameterSpec(new byte[] {
+        0, 0, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0
+    });
+
+    /**
+     * Creates a new key suitable for decryption using the provided raw key
+     * bytes. The algorithm used to generate this key is dictated by
+     * DECRYPTION_KEY_GENERATION_ALGORITHM_NAME and must match the algorithm
+     * used by decrypt().
+     *
+     * @param keyBytes
+     *     The raw bytes from which the encryption/decryption key should be
+     *     generated.
+     *
+     * @return
+     *     A new key suitable for encryption or decryption, generated from the
+     *     given bytes.
+     */
+    public SecretKey createEncryptionKey(byte[] keyBytes) {
+        return new SecretKeySpec(keyBytes, DECRYPTION_KEY_GENERATION_ALGORITHM_NAME);
+    }
+
+    /**
+     * Creates a new key suitable for signature verification using the provided
+     * raw key bytes. The algorithm used to generate this key is dictated by
+     * SIGNATURE_KEY_GENERATION_ALGORITHM_NAME and must match the algorithm
+     * used by sign().
+     *
+     * @param keyBytes
+     *     The raw bytes from which the signature verification key should be
+     *     generated.
+     *
+     * @return
+     *     A new key suitable for signature verification, generated from the
+     *     given bytes.
+     */
+    public SecretKey createSignatureKey(byte[] keyBytes) {
+        return new SecretKeySpec(keyBytes, SIGNATURE_KEY_GENERATION_ALGORITHM_NAME);
+    }
+
+    /**
+     * Decrypts the given ciphertext using the provided key, returning the
+     * resulting plaintext. If any error occurs during decryption at all, a
+     * GuacamoleException is thrown. The IV used for the decryption process is
+     * a null IV (all binary zeroes).
+     *
+     * @param key
+     *     The key to use to decrypt the provided ciphertext.
+     *
+     * @param cipherText
+     *     The ciphertext to decrypt.
+     *
+     * @return
+     *     The plaintext which results from decrypting the ciphertext with the
+     *     provided key.
+     *
+     * @throws GuacamoleException
+     *     If any error at all occurs during decryption.
+     */
+    public byte[] decrypt(Key key, byte[] cipherText) throws GuacamoleException {
+
+        try {
+
+            // Init cipher for descryption using secret key
+            Cipher cipher = Cipher.getInstance(DECRYPTION_CIPHER_NAME);
+            cipher.init(Cipher.DECRYPT_MODE, key, NULL_IV);
+
+            // Perform decryption
+            return cipher.doFinal(cipherText);
+
+        }
+
+        // Rethrow all decryption failures identically
+        catch (InvalidAlgorithmParameterException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (NoSuchAlgorithmException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (NoSuchPaddingException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (InvalidKeyException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (IllegalBlockSizeException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (BadPaddingException e) {
+            throw new GuacamoleServerException(e);
+        }
+
+    }
+
+    /**
+     * Signs the given arbitrary data using the provided key, returning the
+     * resulting signature. If any error occurs during signing at all, a
+     * GuacamoleException is thrown.
+     *
+     * @param key
+     *     The key to use to sign the provided data.
+     *
+     * @param data
+     *     The arbitrary data to sign.
+     *
+     * @return
+     *     The signature which results from signing the arbitrary data with the
+     *     provided key.
+     *
+     * @throws GuacamoleException
+     *     If any error at all occurs during signing.
+     */
+    public byte[] sign(Key key, byte[] data) throws GuacamoleException {
+
+        try {
+
+            // Init MAC for signing using secret key
+            Mac mac = Mac.getInstance(SIGNATURE_MAC_ALGORITHM_NAME);
+            mac.init(key);
+
+            // Sign provided data
+            return mac.doFinal(data);
+
+        }
+
+        // Rethrow all signature failures identically
+        catch (NoSuchAlgorithmException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (InvalidKeyException e) {
+            throw new GuacamoleServerException(e);
+        }
+        catch (IllegalStateException e) {
+            throw new GuacamoleServerException(e);
+        }

Review comment:
       And here?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org