PingGateway 2024.9

Encrypted tokens with KeyStoreSecretStore

This page shows how to validate encrypted access tokens with the StatelessAccessTokenResolver using a JwkSetSecretStore.

Set up encryption keys

  1. Locate the following directories for keys, keystores, and certificates, and in a terminal create variables for them:

    • Directory where the keystore is created: keystore_directory

    • AM keystore directory: am_keystore_directory

    • PingGateway keystore directory: ig_keystore_directory

  2. Set up keys for AM:

    1. Generate the encryption key:

      $ keytool -genseckey \
      -alias encryption-key \
      -dname "CN=ig.example.com, OU=example, O=com, L=fr, ST=fr, C=fr" \
      -keystore "$am_keystore_directory/AM_keystore.p12" \
      -storetype PKCS12 \
      -storepass "password" \
      -keyalg AES \
      -keysize 256
    2. List the keys in the AM keystore:

      $ keytool -list \
      -v \
      -keystore "$am_keystore_directory/AM_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: encryption-key
    3. Add a file called keystore.pass, with the content password:

      $ cd $am_keystore_directory
      $ echo -n 'password' > keystore.pass
      Make sure the password file contains only the password, with no trailing spaces or carriage returns.

      The filename corresponds to the secret ID of the store password and entry password for the KeyStoreSecretStore.

    4. Restart AM.

  3. Set up keys for PingGateway:

    1. Import encryption-key into the PingGateway keystore, with the alias decryption-key:

      $ keytool -importkeystore \
      -srcalias encryption-key \
      -srckeystore "$am_keystore_directory/AM_keystore.p12" \
      -srcstoretype PKCS12 \
      -srcstorepass "password" \
      -destkeystore "$ig_keystore_directory/IG_keystore.p12" \
      -deststoretype PKCS12 \
      -destalias decryption-key \
      -deststorepass "password" \
      -destkeypass "password"
    2. List the keys in the PingGateway keystore:

      $ keytool -list \
      -v \
      -keystore "$ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: decryption-key
    3. In the PingGateway configuration, set an environment variable for the keystore password:

      $ export KEYSTORE_SECRET_ID='cGFzc3dvcmQ='
    4. Restart PingGateway.

Validate tokens

  1. Set up AM:

    1. Set up AM as described in Validate tokens.

    2. Add a mapping for the encryption keystore:

      1. Select Secret Stores > keystoresecretstore.

      2. Select the Mappings tab, and add a mapping with the following values:

        • Secret Label : am.services.oauth2.stateless.token.encryption

        • Alias : encryption-key

    3. Enable token encryption on the OAuth 2.0 Authorization Provider:

      1. Select Services > OAuth2 Provider.

      2. On the Advanced tab, select Encrypt Client-Side Tokens.

  2. Set up PingGateway:

    1. Set up PingGateway for HTTPS, as described in Configure PingGateway for TLS (server-side).

    2. Add the following route to PingGateway, replacing ig_keystore_directory:

      • Linux

      • Windows

      $HOME/.openig/config/routes/rs-stateless-encrypted.json
      %appdata%\OpenIG\config\routes\rs-stateless-encrypted.json
      {
        "name": "rs-stateless-encrypted",
        "condition": "${find(request.uri.path, '/rs-stateless-encrypted')}",
        "heap": [
          {
            "name": "SystemAndEnvSecretStore-1",
            "type": "SystemAndEnvSecretStore"
          },
          {
            "name": "KeyStoreSecretStore-1",
            "type": "KeyStoreSecretStore",
            "config": {
              "file": "<ig_keystore_directory>/IG_keystore.p12",
              "storeType": "PKCS12",
              "storePasswordSecretId": "keystore.secret.id",
              "entryPasswordSecretId": "keystore.secret.id",
              "secretsProvider": "SystemAndEnvSecretStore-1",
              "mappings": [
                {
                  "secretId": "stateless.access.token.decryption.key",
                  "aliases": [ "decryption-key" ]
                }
              ]
            }
          }
        ],
        "handler": {
          "type": "Chain",
          "capture": "all",
          "config": {
            "filters": [ {
              "name": "OAuth2ResourceServerFilter-1",
              "type": "OAuth2ResourceServerFilter",
              "config": {
                "scopes": [ "myscope" ],
                "requireHttps": false,
                "accessTokenResolver": {
                  "type": "StatelessAccessTokenResolver",
                  "config": {
                    "secretsProvider": "KeyStoreSecretStore-1",
                    "issuer": "http://am.example.com:8088/openam/oauth2",
                    "decryptionSecretId": "stateless.access.token.decryption.key"
                  }
                }
              }
            } ],
            "handler": {
              "type": "StaticResponseHandler",
              "config": {
                "status": 200,
                "headers": {
                  "Content-Type": [ "text/html; charset=UTF-8" ]
                },
                "entity": "<html><body><h2>Decoded access_token: ${contexts.oauth2.accessToken.info}</h2></body></html>"
              }
            }
          }
        }
      }

      Notice the following features of the route compared to rs-stateless-signed.json from Validate tokens.

      • The route matches requests to /rs-stateless-encrypted.

      • The OAuth2ResourceServerFilter and KeyStoreSecretStore refer to the configuration for a decryption key instead of a verification key.

  3. Test the setup

    1. Get an access token for the demo user, using the scope myscope:

      $ mytoken=$(curl -s \
      --user "client-application:password" \
      --data "grant_type=password&username=demo&password=Ch4ng31t&scope=myscope" \
      http://am.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
    2. Display the token:

      $ echo ${mytoken}

      Note that the token is structured as an encrypted token.

    3. Access the route by providing the token returned in the previous step:

      $ curl -v \
      --cacert /path/to/secrets/ig.example.com-certificate.pem \
      --header "Authorization: Bearer ${mytoken}" \
      https://ig.example.com:8443/rs-stateless-encrypted
      
      ...
      Decoded access_token: {
      sub=demo,
      cts=OAUTH2_STATELESS_GRANT,
      ...