Installing support for encrypted JSON authentication

guacamole-auth-json is an authentication extension for Apache Guacamole which authenticates users using JSON which has been signed using HMAC/SHA-256 and encrypted with 128-bit AES in CBC mode. As this JSON contains all information describing the user being authenticated, including any connections they have access to, this extension can provide a simple means of integrating Glyptodon Enterprise with external applications. Glyptodon Enterprise packages encrypted JSON support within the glyptodon-guacamole-auth-json package:

$ sudo yum install glyptodon-guacamole-auth-json

Configuring Guacamole to accept encrypted JSON

Guacamole’s main configuration file, /etc/guacamole/guacamole.properties, must be modified to specify the secret key which the Guacamole server should use to decrypt and verify received JSON. Systems generating this JSON will also use this same key to encrypt and sign the JSON they generate.

$ sudo vi /etc/guacamole/guacamole.properties

As guacamole-auth-json uses 128-bit AES, this key must be 128 bits, specified as a 32-digit hexadecimal value using the json-secret-key property:

##
## [JSON-1] Shared JSON secret key
##
## A shared secret key is used by systems generating JSON data to encrypt and
## sign the JSON, and by the Guacamole server to verify and decrypt received
## data. This key must be 128 bits, specified with 32 hexadecimal digits.
##

#json-secret-key: 4c0b569e4c96df157eee1b65dd0e4d41

This key can be essentially anything as long as it is unpredictable. 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  -

If encrypted JSON will only ever be received from a known set of machines or private subnets, you may wish to further restrict acceptance of received JSON to only those trusted machines using the json-trusted-networks property:

##
## [JSON-2] Source network restrictions
##
## By default, received encrypted JSON will be accepted as long as it is valid
## and properly signed with the secret key. This can be further restricted to
## accept encrypted JSON only from machines which match a comma-separated list
## of trusted IP addresses and/or CIDR subnets.
##

#json-trusted-networks: 127.0.0.0/8, 10.0.0.0/8

Completing installation

Guacamole will generally only load new extensions and reread guacamole.properties during the startup process. To apply the configuration changes, Guacamole (and thus Tomcat) must be restarted:

$ sudo systemctl restart tomcat

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. This can be desirable, but should only be done after careful consideration. In most cases, it is critical that a timestamp is specified, limiting the use of the encrypted JSON to some reasonable time interval and preventing replay attacks.

The top-level JSON object which must be submitted to Guacamole has the following properties:

Property nameTypeDescription
usernamestringThe unique username of the user authenticated by the JSON. If the user is anonymous, this should be the empty string ("").
expiresnumberThe 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).
connectionsobjectThe 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 connection defined within each submitted JSON object has the following properties:

Property nameTypeDescription
protocolstringThe internal name of a supported protocol, such as vncrdp, or ssh.
parametersobjectAn object representing the connection parameter name/value pairs to apply to the connection, as documented in the Guacamole manual.

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).

For example, if Guacamole is running on localhost at /guacamole, and BASE64_RESULT is the result of the above process, the equivalent run of the "curl" utility would be:

$ curl --data-urlencode "data=BASE64_RESULT" http://localhost:8080/guacamole/api/tokens
Be sure to URL-encode the base64-encoded result prior to POSTing it to /api/tokens or including it in the URL. Base64 can contain both "+" and "=" characters, which have special meaning within URLs.

If the data is invalid in any way, if the signature does not match, if decryption or signature verification fails, or if the submitted data has expired, the REST service will return an invalid credentials error and fail without user-visible explanation. Details describing the error that occurred will be in the Tomcat logs via journalctl.

Reference implementation

The source includes a shell script, /usr/share/guacamole-auth-json/doc/encrypt-json.sh, which uses the OpenSSL command-line utility to encrypt and sign JSON in the manner that guacamole-auth-json requires. It is thoroughly commented and should work well as a reference implementation, for testing, and as a point of comparison for development. The script is run as:

$ /usr/share/guacamole-auth-json/doc/encrypt-json.sh HEX_ENCRYPTION_KEY file-to-sign-and-encrypt.json

For example, if you have a file called auth.json containing the following:

{
    "username" : "test",
    "expires" : "1446323765000",
    "connections" : {
        "My Connection" : {
            "protocol" : "rdp",
            "parameters" : {
                "hostname" : "10.10.209.63",
                "port" : "3389"
            }
        },
        "My OTHER Connection" : {
            "protocol" : "rdp",
            "parameters" : {
                "hostname" : "10.10.209.64",
                "port" : "3389"
            }
        }
    }
}

and you run:

$ /usr/share/guacamole-auth-json/doc/encrypt-json.sh 4C0B569E4C96DF157EEE1B65DD0E4D41 auth.json

You will receive the following output:

le2Ug6YIo4perD2GV17QtWvOdfSemVDDtCOdRYJlbdUf3fhN+63LpQa1RDkzU7Zc
DW3+OtyTCBGQ7OLO+HpG6pHNom76BXpmnHSRx1UdQ3WVZelPUXEDzxe74aN6DUP9
G9isXhBMdLUhZwEJf4k4Gpzt9MHAH5PufSKq3DO1UHnrRjdGbKKddug2BcuDrwJM
UJf1tRX9CAEC11/gWEwrHDOhH/abeyeDyElbaEG/oOY8EdoFNYgUsjI2x31OpCuB
sEv7FOFafL05wEoIFv0/pPft0DHk7GuvHBBCqXuK98yMEo3d0zD5D+IsOY8Rmm1+
0CoWkX22mqyRQMFS2fTp/fClCN4QLb0aNn+unweTimd2SXN9cjREmZknXf7Tj8oU
/FNXc37i0HEfG5aVgp5znMCwwRAOFnFhLqG3K2yaTRE+hLNBxltIjLfFmNG5TZZA
gUdKyuegsOd0KS5iHdW6tPI01AwfRO9y2z20t3flsgDp50EGWjT2/TTA5Nkjnnjk
JXNzCOfM7DCI/ioEz6Ga140qXfOX/g8SGiukpwt+j0ANI573TdVt7nsp7MZX2qKg
2GcoNqjBqQxqpqI5ZYz4KVfD4cYu8KDZ9MiFMzbUwwKNSzYxiep1KJwiG0HQThHg
oX2FJYOFCFcinQgGkUOaBJK1K0bo1ouaBSe4iGPjd54=

The resulting base64 data above, if submitted using the data parameter to Guacamole, will authenticate a user and grant them access to the connections described in the JSON (at least until the expires timestamp is reached, at which point the JSON will no longer be accepted).

The OpenSSL utility is not an explicit dependency of the glyptodon-guacamole-auth-json package as it's not inherently required by the actual Guacamole extension. If you do not already have the OpenSSL utility installed, you will need to install it before running the encrypt-json.sh reference implementation:

$ sudo yum install openssl