Understanding OAuth 2.0 key rotation in AM
With OAuth2 being the defacto authorization model many of our customers use, we made a few improvements to how AM handles the use of secrets in v6.5 that is released later this year. The nightly build features some neat improvements in the secrets management API.
The API has been overhauled, to make it simpler to use, simpler to integrate and more secure. As you'd expect. A neat focus was on simplifying the use case of key rotation. Rotation is an essential part of deployment models - either as a reaction to a breach (and implementing the 3 R's paradigm) or a simple best practice. Here I'll show a simple demo for rotating an RSA key used to sign OAuth2 stateless access tokens.
Firstly few intro bits regarding the new Secrets Management setup. We now have a new global configure option, for Secret Stores.
Here we see two out of the box key stores configured. The basic Java keytore.jceks and a default-passwords-keystore used for bootstrapping access.
The default-keystore config, is useful for testing and dev, where you can quickly access the local file system based Java keystore. The config for this is pretty straightforward. The entrypass and storepass settings are de-referenced in the default-passwords-store, where these encrypted values are read from the filesystem to bootstrap access.
In production, many customers tho are likely to want to integrate with PKCS#11 fronted Hardware Security Modules (HSM's) or even a cloud vault.
Within the keystore configuration, there is a new mappings tab. This tab is the interesting aspect with respect to rotation. Here we can add in active and multiple secondary keys for specific purposes. These purposes allow focused use of keys within the secrets API. A general good practice is to have very focused use of key material, which is declared and can't be mis-configured.
Each secret id maps to the internal secrets API. The active alias is the one currently in active circulation. Clicking within an id, shows that other secondary alias's can be setup. If for example, verification of an access_token fails with the active key, the secondary ones are tried. Depending on what token type is presented to AM, if a JWT, AM can quickly match the kid in the header and map the appropriate secondary keys in the list.
The alias names are mapped into whatever is in the keystore.
So what does this mean in reality? Well let me setup my OAuth2 provider to use the detault.rsa.sig.key signing alias for my stateless access_tokens. Within the realm level OAuth2 service, click on the new Secrets tab. This shows the default mappings come from my keystore. On the Advanced tab, make sure to change the signing algorighm to be RS256.
After switching my provider to use stateless JWT's and not stateful server side tokens, I go through and issue myself a bearer token payload...with a nice access_token.
Lots of letters in that one. Net-net, if I quickly decode the stateless access_token header, I see it was signed with a key id of the following:
If I now introspect the token against the ../oauth2/introspect endpoint, all is good and the token is good to go.
So, now I want to introduce a new signing key to the situation. There are numerous ways to generate keys, so do this the way you feel is best. I created a simple script, that creates a 2048 bit RSA private key, then imports into my AM ../openam/keystore.jceks file.
If I do a keytool -list on that keystore, I will see the my new key called newrsasigningkey:
So far so good. I now simply update the mapping in my keystore config to use my new key and also make the new key my active key.
This basically means, that any new access_tokens being issued, will be signed with the new key, and any inbound token that needs verifying, will try the new key first, then fall back to use the original test key second. Simply allowing existing tokens signed with the old key to be validated correctly.
An access_token issued with the newrsasigningkey will have a new key id in the JWT header:
However, both the first and second access_tokens issued can be verified by the ../introspect endpoint.
Another neat bi-product, is that all of the configuration done via the UI above, could be done simply by using the native REST API's. Easily viewable in the API explorer. Simply copy and paste the necessary code widget into your app.
NB - worth noting, that when you create the new test key, make sure that the storepass entered via the keytool, matches that in the entrypass value in AM.