Identity Gateway 2024.3

Rotate keys

The following sections give an overview of how to manage rotation of encryption keys and signing keys, and include examples for key rotation based on use cases from the Gateway guide.

About key rotation

Key rotation is the process of generating a new version of a key, assigning that version as the active key to encrypt or sign new messages, or as a valid key to decrypt or validate messages, and then deprovisioning the old key.

Why and when to rotate keys

Regular key rotation is a security consideration that is sometimes required for internal business compliance. Regularly rotate keys to:

  • Limit the amount of data protected by a single key.

  • Reduce dependence on specific keys, making it easier to migrate to stronger algorithms.

  • Prepare for when a key is compromised. The first time you try key rotation shouldn’t be during a real-time recovery.

Key revocation is a type of key rotation, done exceptionally if you suspect that a key has been compromised. To decide when to revoke a key, consider the following points:

  • If limited use of the old keys can be tolerated, provision the new keys and then deprovision the old keys. Messages produced before the new keys are provisioned are impacted.

  • If use of the old keys cannot be tolerated, deprovision the old keys before you provision the new keys. The system is unusable until new keys are provisioned.

Steps for rotating symmetric keys

The following steps outline key rotation and revocation for symmetric keys managed by a KeyStoreSecretStore. For an example, refer to Rotate keys in a shared JWT session.

  1. Using OpenSSL, Keytool, or another key creation mechanism, create the new symmetric key. The keystore should contain the old key and the new key.

  2. Provision the new key.

    1. In the mappings property of KeyStoreSecretStore, add the alias for the new key after the alias for the old key. The new key is now valid. Because the old key is the first key in the list, it is the active key.

    2. Move the new key to be the first key in the list. The new key is now the active key.

  3. Deprovision the old key.

    To ensure that no messages or users are impacted, wait until messages encrypted or signed with the old key are out of the system before you deprovision the old key.

    1. In the mappings property of KeyStoreSecretStore, delete the alias for the old key. The old key can no longer be used.

    2. Using OpenSSL, Keytool, or another key creation mechanism, delete the old symmetric key.

Steps for rotating asymmetric keys

The following steps outline the process for key rotation and revocation for asymmetric keys managed by a KeyStoreSecretStore or HsmSecretStore. For an example, refer to Rotate keys for stateless access tokens signed with a KeyStoreSecretStore.

  1. Create new asymmetric keys for signing and encryption, using OpenSSL, Keytool, or another key creation mechanism.

  2. Provision the message consumer with the private portion of the new encryption key, and the public portion of the new signing key.

    The message consumer can now decrypt and verify messages with the old key and the new key.

  3. Provision the message producer, with the public portion of the new encryption key, and the private portion of the signing key. The message producer starts encrypting and signing messages with the new key, and stops using the old key.

  4. Deprovision the message consumer with the private portion of the old encryption key, and the public portion of the old signing key. The message consumer can no longer decrypt and verify messages with the old key.

    To ensure that no messages or users are impacted, wait until messages encrypted or signed with the corresponding old key are out of the system before you deprovision the old key.

  5. Deprovision the message producer, with the public portion of the old encryption key, and the private portion of old signing key.

Key rotation for keys in a JWK set

When keys are provided by a JWK Set from AM, the key rotation is transparent to IG. AM generates a key ID (kid) for each key it exposes at the jwk_uri. For more information, refer to Mapping and rotating secrets in AM’s Security guide.

When IG processes a request with a JWT containing a kid, IG uses the kid to identify the key in the JWK Set. If the kid is available at the jwk_uri on AM, IG processes the request. Otherwise, IG tries all compatible secrets from the JWK Set. If none of the secrets work, the JWT is rejected.

Rotate keys for stateless access tokens signed with a KeyStoreSecretStore

This example extends the example in Validate signed access tokens with the StatelessAccessTokenResolver and KeyStoreSecretStore to rotate the keys that sign an access token and verify the signature.

Rotate Keys For Stateless Access Tokens Signed With a KeyStoreSecretStore
  1. Set up the new keys:

    1. Generate a new private key called signature-key-new, and a corresponding public certificate called x509certificate-new.pem:

      $ openssl req -x509 \
      -newkey rsa:2048 \
      -nodes \
      -subj "/CN=ig.example.com/OU=example/O=com/L=fr/ST=fr/C=fr" \
      -keyout keystore_directory/signature-key-new.key \
      -out keystore_directory/x509certificate-new.pem \
      -days 365
      
      ... writing new private key to 'keystore_directory/signature-key-new.key'
    2. Convert the private key and certificate files into a new PKCS#12 keystore file:

      $ openssl pkcs12 \
      -export \
      -in keystore_directory/x509certificate-new.pem \
      -inkey keystore_directory/signature-key-new.key \
      -out keystore_directory/keystore-new.p12 \
      -passout pass:password \
      -name signature-key-new
    3. List the keys in the new keystore:

      $ keytool -list \
      -keystore "keystore_directory/keystore-new.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 1 entry
      Alias name: signature-key-new
    4. Import the new keystore into keystore.p12, so that keystore.p12 contains both keys:

      $ keytool -importkeystore
      -srckeystore keystore_directory/keystore-new.p12
      -srcstoretype pkcs12
      -srcstorepass password
      -destkeystore keystore_directory/keystore.p12
      -deststoretype pkcs12
      -deststorepass password
      
      Entry for alias signature-key-new successfully imported ...
    5. List the keys in keystore.p12, to make sure it contains the new and old keys:

      $ keytool -list \
      -keystore "keystore_directory/keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 2 entries
      Alias name: signature-key
      Alias name: signature-key-new
  2. Set up AM:

    1. Copy the updated keystore to AM:

      1. Copy keystore.p12 to AM:

        $ cp keystore_directory/keystore.p12 am_keystore_directory/AM_keystore.p12
      2. List the keys in the updated AM keystore:

        $ keytool -list \
        -keystore "am_keystore_directory/AM_keystore.p12" \
        -storepass "password" \
        -storetype PKCS12
        
        ...
        Your keystore contains 2 entries
        Alias name: signature-key
        Alias name: signature-key-new
      3. Restart AM to update the keystore cache.

    2. Update the KeyStoreSecretStore on AM:

      1. In AM, select Secret Stores > keystoresecretstore.

      2. Select the Mappings tab, and in am.services.oauth2.stateless .signing.RSA add the alias signature-key-new.

        The mapping now contains two aliases, but the alias signature-key is still the active alias. AM still uses signature-key to sign tokens.

      3. Drag signature-key-new above signature-key.

        AM now uses signature-key-new to sign tokens.

  3. Set up IG:

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

    2. Import the public certificate to the IG keystore, with the alias verification-key-new:

      $ keytool -import \
      -trustcacerts \
      -rfc \
      -alias verification-key-new \
      -file "keystore_directory/x509certificate-new.pem" \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storetype PKCS12 \
      -storepass "password"
      
      ...
      Trust this certificate? [no]:  yes
      Certificate was added to keystore
    3. List the keys in the IG keystore:

      $ keytool -list \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
      
      ...
      Your keystore contains 2 entries
      Alias name: verification-key
      Alias name: verification-key-new
    4. In rs-stateless-signed-ksss.json, edit the KeyStoreSecretStore mapping with the new verification key:

      "mappings": [
        {
          "secretId": "stateless.access.token.verification.key",
          "aliases": [ "verification-key", "verification-key-new" ]
        }
      ]

      If the Router scanInterval is disabled, restart IG to reload the route.

      IG can now check the authenticity of access tokens signed with verification-key, the old key, and verification-key-new, the new key. However, AM signs with the old key.

  4. 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}
    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-signed-ksss
      
      
      ...
      Decoded access_token: {
      sub=demo,
      cts=OAUTH2_STATELESS_GRANT,
      ...
Deprovision Old Keys
  1. Remove signature-key from the AM keystore:

    1. Delete the key from the keystore:

      $ keytool -delete \
      -keystore "am_keystore_directory/AM_keystore.p12" \
      -storepass "password" \
      -alias signature-key
    2. List the keys in the AM keystore to make sure signature-key is removed:

      $ keytool -list \
      -keystore "am_keystore_directory/AM_keystore-new.p12" \
      -storepass "password" \
      -storetype PKCS12
    3. Restart AM.

  2. Remove verification-key from the IG keystore:

    1. Delete the key from the keystore:

      $ keytool -delete \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -alias verification-key
    2. List the keys in the IG keystore to make sure that verification-key is removed:

      $ keytool -list \
      -keystore "ig_keystore_directory/IG_keystore.p12" \
      -storepass "password" \
      -storetype PKCS12
  3. In AM, delete the mapping for signature-key from keystoresecretstore.

  4. In IG, delete the mapping for verification-key from the route rs-stateless-signed-ksss.json. If the Router scanInterval is disabled, restart IG to reload the route.

Rotate keys in a shared JWT session

This section builds on the example in Share JWT Session Between Multiple Instances of IG to rotate a key used in a shared JWT session.

When a JWT session is shared between multiple instances of IG, the instances are able to share the session information for load balancing and failover.

Before you start, set up the example in Set up shared secrets for multiple instances of IG, where three instances of IG share a JwtSession and use the same authenticated encryption key. Instance 1 acts as a load balancer, and generates a session. Instances 2 and 3 access the session information.

  1. Test the setup with the existing key, symmetric-key:

    1. Access instance 1 to generate a session:

      $ curl -v http://ig.example.com:8001/log-in-and-generate-session
      
      GET /log-in-and-generate-session HTTP/1.1
      ...
      
      HTTP/1.1 200 OK
      Content-Length: 84
      Set-Cookie: IG=eyJ...HyI; Path=/; Domain=.example.com; HttpOnly
      ...
      Sam Carter logged IN. (JWT session generated)
    2. Using the JWT cookie returned in the previous step, access instance 2:

      $ curl -v http://ig.example.com:8001/webapp/browsing?one --header "cookie:IG=<JWT cookie>"
      
      GET /webapp/browsing?one HTTP/1.1
      ...
      cookie: IG=eyJ...QHyI
      ...
      HTTP/1.1 200 OK
      ...
      Hello, Sam Carter !! (instance2)

      Note that instance 2 can access the session info.

    3. Using the JWT cookie again, access instance 3:

      $ curl -v http://ig.example.com:8001/webapp/browsing?two --header "Cookie:IG=<JWT cookie>"
      
      GET /webapp/browsing?two HTTP/1.1
      ...
      cookie: IG=eyJ...QHyI
      ...
      HTTP/1.1 200 OK
      ...
      Hello, Sam Carter !! (instance3)

      Note that instance 3 can access the session info.

  2. Commission a new key:

    1. Generate a new encryption key, called symmetric-key-new, in the existing keystore:

      $ keytool \
      -genseckey \
      -alias symmetric-key-new
      -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
      -storepass password \
      -storetype PKCS12 \
      -keyalg HmacSHA512 \
      -keysize 512
    2. Make sure the keystore contains the old key and the new key:

      $ keytool \
      -list \
      -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
      -storepass password \
      -storetype PKCS12
      
      ...
      Your keystore contains 2 entries
      symmetric-key, ...
      symmetric-key-new ...
    3. Add the key alias to instance1-loadbalancer.json, instance2-retrieve-session-username.json, and instance3-retrieve-session-username.json, for each IG instance, as follows:

      "mappings": [{
        "secretId": "jwtsession.encryption.secret.id",
        "aliases": ["symmetric-key", "symmetric-key-new"]
      }]

      If the Router scanInterval is disabled, restart IG to reload the route.

      The active key is symmetric-key, and the valid key is symmetric-key-new.

    4. Test the setup again, as described in step 1, and make sure instances 2 and 3 can still access the session information.

  3. Make the new key the active key for generating sessions:

    1. In instance1-loadbalancer.json, change the order of the keys to make symmetric-key-new the active key, and symmetric-key the valid key:

      "mappings": [{
        "secretId": "jwtsession.encryption.secret.id",
        "aliases": ["symmetric-key-new", "symmetric-key"]
      }]

      Don’t change instance2-retrieve-session-username.json or instance3-retrieve-session-username.json.

    2. Test the setup again, as described in step 1, and make sure instances 2 and 3 can still access the session information.

      Instance 1 creates the session using the new active key, symmetric-key-new.

      Because symmetric-key-new is declared as a valid key in instances 2 and 3, the instances can still access the session. It isn’t necessary to make symmetric-key-new the active key.

  4. Decommission the old key:

    1. Remove the old key from all of the routes, as follows:

      "mappings": [{
        "secretId": "jwtsession.encryption.secret.id",
        "aliases": ["symmetric-key-new"]
      }]

      Key symmetric-key-new is the only key in the routes.

    2. Remove the old key, symmetric-key, from the keystore:

      1. Delete symmetric-key:

        $ keytool \
        -delete \
        -alias symmetric-key \
        -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
        -storepass password \
        -storetype PKCS12 \
        -keypass password
      2. Make sure the keystore contains only symmetric-key-new:

        $ keytool \
        -list \
        -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \
        -storepass password \
        -storetype PKCS12
        
        ...
        Your keystore contains 1 entry
        symmetric-key-new ...
    3. Test the setup again, as described in step 1, and make sure instances 2 and 3 can still access the session information.