Rotating 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 some 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 Rotate Keys
Regular key rotation is a security best-practice, 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.
Key Rotation Steps
The following steps outline the process for key rotation and revocation for keys managed by a KeyStoreSecretStore or HsmSecretStore:
Create new asymmetric keys for signing and encryption, using OpenSSL, Keytool, or another key creation mechanism.
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.
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.
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.
Deprovision the message producer, with the public portion of the old encryption key, and the private portion of old signing key.
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 before you provision the new keys. The system is unusable until new keys are provisioned.
Key Rotation for JwkSetSecretStore
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, see 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.
Rotating Keys For Stateless Access_Tokens Signed With a KeyStoreSecretStore
This example extends the example in "Validating Signed Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore" to rotate the keys that sign an access_token and verify the signature.
Before you start, set up and test the example in "Validating Signed Access_Tokens With the StatelessAccessTokenResolver and KeyStoreSecretStore".
Set up the new keys:
Generate a new private key called
signature-key-new
, and a corresponding public certificate calledx509certificate-new.pem
:$
openssl req -x509 \ -newkey rsa:2048 \ -nodes \ -subj "/CN=openig.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'
Convert the private key and certificate files into a new PKCS12 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
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
Import the new keystore into
keystore.p12
, so thatkeystore.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 ...
List the keys in
keystore.p12
, to make sure that it contains the new and old keys:$
keytool -list \ -keystore "keystore_directory/keystore-new.p12" \ -storepass "password" \ -storetype PKCS12
... Your keystore contains 2 entries Alias name: signature-key Alias name: signature-key-new
Set up AM:
Copy the updated keystore to AM:
Copy
keystore.p12
to AM:$
cp keystore_directory/keystore.p12 am_keystore_directory/AM_keystore.p12
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
Restart AM to update the keystore cache.
Update the KeyStoreSecretStore on AM:
In AM, select Secret Stores > keystoresecretstore.
Select the Mappings tab, and in
am.services.oauth2.stateless.signing.RSA
add the aliassignature-key-new
.The mapping now contains two aliases, but the alias
signature-key
is still the active alias. AM still usessignature-key
to sign tokens.Drag
signature-key-new
abovesignature-key
.AM now uses
signature-key-new
to sign tokens.
Set up IG:
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
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
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, andverification-key-new
, the new key. However, AM signs with the old key.
Test the setup:
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://openam.example.com:8088/openam/oauth2/access_token | jq -r ".access_token")
Display the token:
$
echo ${mytoken}
Access the route by providing the token returned in the previous step:
$
curl -v http://openig.example.com:8080/rs-stateless-signed-ksss --header "Authorization: Bearer ${mytoken}"
... Decoded access_token: { sub=demo, cts=OAUTH2_STATELESS_GRANT, ...
Remove
signature-key
from the AM keystore:Delete the key from the keystore:
$
keytool -delete -signature-key
List the keys in the AM keystore to make sure that
signature-key
is removed:$
keytool -list \ -keystore "am_keystore_directory/AM_keystore-new.p12" \ -storepass "password" \ -storetype PKCS12
Restart AM.
Remove
verification-key
from the IG keystore:Delete the key from the keystore:
$
keytool -delete -verification-key
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
In AM, delete the mapping for
signature-key
fromkeystoresecretstore
.In IG, delete the mapping for
verification-key
from the routers-stateless-signed-ksss.json
. If the RouterscanInterval
is disabled, restart IG to reload the route.
Rotating Keys In a Shared JWT Session
This section builds on the example in "Sharing 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.
Test the setup with the existing key,
symmetric-key
:Access instance 1 to generate a session:
$
curl -v http://openig.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)
Using the JWT cookie returned in the previous step, access instance 2:
$
curl -v http://openig.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.
Using the JWT cookie again, access instance 3:
$
curl -v http://openig.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.
Commission a new key:
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
Make sure that 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 ...
Add the key alias to
instance1-loadbalancer.json
,instance2-retrieve-session-username.json
, andinstance3-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 issymmetric-key-new
.Test the setup again, as described in step 1, and make sure that instances 2 and 3 can still access the session information.
Make the new key the active key for generating sessions:
In
instance1-loadbalancer.json
, change the order of the keys to makesymmetric-key-new
the active key, andsymmetric-key
the valid key:"mappings": [{ "secretId": "jwtsession.encryption.secret.id", "aliases": ["symmetric-key-new", "symmetric-key"] }]
Don't change
instance2-retrieve-session-username.json
orinstance3-retrieve-session-username.json
.Test the setup again, as described in step 1, and make sure that 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 makesymmetric-key-new
the active key.
Decommission the old key:
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.Remove the old key,
symmetric-key
, from the keystore:Delete
symmetric-key
:$
keytool \ -delete \ -alias symmetric-key \ -keystore /path/to/secrets/jwtsessionkeystore.pkcs12 \ -storepass password \ -storetype PKCS12 \ -keypass password
Make sure that 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 ...
Test the setup again, as described in step 1, and make sure that instances 2 and 3 can still access the session information.