The reason for producing this wiki is to document configuration of AM and IG token handling, with regards to the necessary key creation, keystore handling and AM and IG configuration required to secure them. This follows issues with finding the correct keystore configuration, procedures and keys in completing feature (pertaining to stateless access-tokens). The PR associated with this JIRA is also very informative on how to configure the environments.
A keystore is a repository for securely storing:
This repository may be a specifically formatted text file or backed by some other mechanism. keystore access is available through the java.security.KeyStore
class.
Keys are stored in a keystore with an associated "alias" that is used for identifying the key. The keystore may be password protected (with a storepass) and each private or secret key may be individually protected with its own password (keypass). There is no need to password-protect public keys.
A truststore is a keystore specifically for managing remote authentication (see JSSE Ref Guide). It's use is not important in the context of this discussion, so it won't be covered any further.
keytool
is a java supplied command-line tool for performing operations on a keystore, such as creating keys, listing keys and importing and exporting.
More information on KeyStore
s can be found here:
A KeyStore
, as mentioned, may be formatted in a specific way, according to the its type:
When using the keytool, it is necessary to specify the -storetype
if not using JKS, otherwise you will get unhelpful messages about keystore formats and tampering.
More useful information on keystore types:
AM 6.0 supports the following client requests (Response Type):
code
. Specifies that the client application requests an authorization code grant.
token
. Specifies that the client application requests an implicit grant type and requests a token from the API.
id_token
. Specifies that the client application requests an ID token.
code token
. Specifies that the client application requests an access token, access token type, and an authorization code.
token id_token
. Specifies that the client application requests an access token, access token type, and an ID token.
code id_token
. Specifies that the client application requests an authorization code and an ID token.
code token id_token
. Specifies that the client application requests an authorization code, access token, access token type, and an ID token.
AM comes with two pre-configured KeyStores
: keystore.jks
and keystore.jceks
. Which is used can be configured through the AM console:
For full AM KeyStore configuration, see the AM Setup and Maintenance Guide.
Deployment (top-level menu) → Servers → http://.../openam → Security (left-hand menu) → Key Store
path/to/am/boot.json
.path/to/am/.storepass
path/to/am/.keypass
- default keypass used ("changeit").AM KeyStore
s come complete with provided test keys, as documented in the AM Setup and Maintenance Guide (specifically Table 5.1):
Supplied sample keys are
com.forgerock.openam.functionaltest.api.keys.TestKey
(openam-functional-test-apis) sets up the keystore.jceks
keys.rsajwtsigningkey
which can be used for JWT signing.com.sun.identity.setup.TestKeys
(openam-core) serves to set up some test keys for demo purposes:SecretKey
directenctest
- is anticipated to test token encryption:directenctest
is expected to be the HMAC encryption key for testing encryption but seemingly has an algorithm of RAW and cannot be imported into a PKCS12 keystore.SecretKey
selfservingsigntest
Other notes of interest:
jwk_uri endpoint:
curl "http://openam.example.com:8082/openam/oauth2/connect/jwk_uri" | jq |
It is necessary to restart AM after any key change - as AM caches keystore content on load. |
IG doesn't expressly need the configuration of any KeyStore
s, except if required by a particular Filter
or component (e.g. the new StatelessAccessTokenResolver
).
In the case that IG must use AM provided keys (e.g. the above example) then the correct keystore type must be created and AM-generated keys must be made available to it. In the case of then either a signing key (for verification) or an encryption key (for decryption) must be provided.
Using the example of the StatelessAccessTokenResolver
, AM would be configured to produce access tokens that are either Encrypted or Signed JWTs (Encrypted takes precedence in AM config). As such, depending on how the JWT is secured, we would need to generate an encryption key or a signature key in the AM KeyStore
. Again, depending on how the JWT is secured, we would then need to import the respective key into the IG keystore:
SecretKey
- then we would need to import the self-same SecretKey
into the IG keystore.Some points to note - or emphasise - here:
SecretKey
(symmetric) so IG must use this same key to decrypt it.SecretKey
s. JKS keystores cannot store SecretKey
s so AM has to be configured to use a JCEKS keystore at a minimum.SecretKey
from the AM keystore and import it into the IG keystore.StatelessAccessResolver
, PKCS12 was used as the IG keystore.With this in mind, the following snippets show how to configure a signature or encryption key in an AM keystore (JCEKS) and import the corresponding key into an IG keystore (JCEKS or PKCS12).
The following code-block illustrates how to export a signature certificate from AM (keystore.jks) to an IG PKCS12 keystore (or a JCEKS keystore would be applicable):
# create verify-key03 (RSA 2048) in AM JKS keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -genkey -alias verify-key03 \ -dname "CN=openig.example.com, OU=example, O=com, L=fr, ST=fr, C=fr" \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openam/openam-embedded-DJ/openam/keystore.jceks" \ -storetype JCEKS \ -storepass "qWPzxXdIF0IaD/6Q9Bp7vr32oUK0H8h8" \ -keypass changeit \ -keyalg RSA -keysize 2048 # export verify-key03 to .pem /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -exportcert -rfc -alias verify-key03 \ -file "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/openig-container/apache-tomcat-8.0.46/conf/verify-key03-cert.pem" \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openam/openam-embedded-DJ/openam/keystore.jceks" \ -storetype JCEKS \ -storepass "qWPzxXdIF0IaD/6Q9Bp7vr32oUK0H8h8" \ -keypass changeit # import verify-key03 .pem to IG PKCS12 keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -import -trustcacerts -rfc -alias verify-key03 \ -file "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/openig-container/apache-tomcat-8.0.46/conf/verify-key03-cert.pem" \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/ig_instance_dir/config/IG_keystore.p12" \ -storetype PKCS12 \ -storepass "keystore" # list content of IG PKCS12 to confirm key present /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -list \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/ig_instance_dir/config/IG_keystore.p12" \ -storetype PKCS12 \ -storepass keystore |
The following code-block illustrates how to export an encryption SecretKey
from AM (keystore.jks) to an IG PKCS12 keystore (or a JCEKS keystore would be applicable):
# create secret key enckey07 (RSA 2048) in AM JKS keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -genseckey -alias enckey07 \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openam/openam-embedded-DJ/openam/keystore.jceks" \ -storetype JCEKS \ -storepass "qWPzxXdIF0IaD/6Q9Bp7vr32oUK0H8h8" \ -keypass changeit \ -keyalg AES \ -keysize 256 # export enckey07 to .pem - using keytool exportseckey --> !!!doesn't work!!! /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -exportseckey -alias enckey07 \ -file "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/openig-container/apache-tomcat-8.0.46/conf/enckey03-secretkey.pem" \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openam/openam-embedded-DJ/openam/keystore.jceks" \ -storetype JCEKS \ -storepass "qWPzxXdIF0IaD/6Q9Bp7vr32oUK0H8h8" \ -keypass changeit # list content of AM keystore.jceks to confirm key present /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -list -v \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openam/openam-embedded-DJ/openam/keystore.jceks" \ -storetype JCEKS \ -storepass "qWPzxXdIF0IaD/6Q9Bp7vr32oUK0H8h8" # import enckey07 key (direct from keystore) to IG PKCS12 keystore /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -importkeystore \ -srcalias enckey07 \ -srckeystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openam/openam-embedded-DJ/openam/keystore.jceks" \ -srcstoretype JCEKS \ -srcstorepass "qWPzxXdIF0IaD/6Q9Bp7vr32oUK0H8h8" \ -destalias enckey07 \ -destkeystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/ig_instance_dir/config/IG_keystore.p12" \ -deststoretype PKCS12 \ -deststorepass "keystore" \ -destkeypass "keystore" # list content of IG PKCS12 to confirm key present /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/keytool -list -v \ -keystore "/Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/ig_instance_dir/config/IG_keystore.p12" \ -storetype PKCS12 \ -storepass "keystore" |
This assumes the keystore is configured correctly:
Additionally:
OAuth2 Provider configuration: Realm → Configure OAuth2 Provider
|
It is also necessary to add appropriate scopes. For example:
"email|Your email address", "openid|", "address|Your postal address", "phone|Your telephone number(s)", "profile|Your personal information" |
|
|
OAuth2 Client configuration: Realm → Applications → OAuth2
|
Additional notes:
Signing and Encryption
tab includes a lot of token and "algorithm fields". For access tokens, these do not need to be configured. For instance:IG configuration KeyStore
heaplet would reference the above IG keystore and keystore type. For example:
"heap": [ { ... }, { "config": { "type": "PKCS12", "password": "keystore", "url": "file:///Users/wayne.morrison/dev/pyforge/results/20180723-114228/Filters/openig/ig_instance_dir/config/IG_keystore.p12" }, "name": "IgP12KeyStore", "type": "KeyStore" }, ... |
Specific key configuration would be added in the Filter
or other component configuration. For example, for StatelessAccessTokenResolver
:
{ "type" : "StatelessAccessTokenResolver", "config" : { "issuer" : "http://openam.example.com:8082/openam/oauth2/realms/root/realms/filters_realm", "tokenName" : "access_token", "signature" : { "alias" : "verify-key03", "password" : "keystore", "keystore" : "IgP12KeyStore" }, "_encryption" : { "alias" : "enckey07", "password" : "keystore", "keystore" : "IgP12KeyStore" } } } |
SecretKey
to use to decrypt the access token (if encrypted).You can obtain a token from AM (oauth2/access_token endpoint) using the following:
http -a test_OAuth2ResourceServerFilter:password POST openam.example.com:8082/openam/oauth2/realms/root/realms/filters_realm/access_token username=demo password=changeit grant_type=password scope=openid --form | jq '.["access_token"]' # capture it in $TOKEN for later use TOKEN=`http -a test_OAuth2ResourceServerFilter:password POST openam.example.com:8082/openam/oauth2/realms/root/realms/filters_realm/access_token username=demo password=changeit grant_type=password scope=openid --form | jq '.["access_token"]' | tr -d '"'` |
Please refer to Sample StatelessAccessTokenResolver
setup and configuration (PR) for more thorough instructions on testing (upon which this document was based).
SecretKey
SecretKey
keytool
operation fails with error indicating incorrect format or that tampering has occurred:java.io.IOException: Invalid keystore format
-storetype
setting to ensure you're performing the operation on an expected keystore type.-srcstoretype
and -deststoretype
settings are correct for the source and target keystores.ID Token Signing Algorithm
is not being used then it is set to "none" (not left empty).