What are Keycloak realm keys?
Keycloak's authentication protocols make use of private and public keys for signing and encrypting, as described in the official documentation. These keys are realm-specific, and by default managed internally in Keycloak. So, when you create a realm using the Keycloak Admin API, kcadm.sh or manually using the Web UI, new keypair(s) get generated automatically. These are called "Managed keys". However, you can also use custom realm keys in Keycloak, which is what this article is about. Using custom realm keys allow you, for example, to use a single keypair in multiple realms. When you import a custom private key, Keycloak uses it to automatically create a certificate. If you want, you can pass both a private key and a certificate signed with that private key instead. That way you can bypass Keycloak's automatic certificate generation, should you need to.
Internally Keycloak handles realm keys as "components" and stores them the COMPONENT_CONFIGS table in the database. That's why you won't see any "keypair" or "key" methods in the REST API, nor can you find the keys as properties of the realm. While kcadm.sh does have a "get keys" method, keys can only be added, deleted or modifies as components.
The currently used key - managed or custom - is selected based on the priority of all active keys. So, in this context active does not automatically mean that a realm keypair is being used, just that it is has not been explicitly made passive. In other words an active key can be used to sign or encrypt, whereas a passive key can only be used to validate signatures or encrypt.
Creating custom realm keys
While managed keys are convenient, you can use custom realm keys in Keycloak. The official documentation on how to do this via kcadm and/or the API is missing, so this blog post aims to fill that void. Here we use kcadm.sh, but the process is very similar for raw API calls.
The first step is to create a keypair (borrowed from here):
$ openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
The next step is to create a JSON payload, which should look like this:
{
"name" : "rsa-shared",
"providerId" : "rsa",
"providerType" : "org.keycloak.keys.KeyProvider",
"parentId" : "Test",
"config" : {
"privateKey": ["-----BEGIN PRIVATE KEY-----\n<private-key-body>\n-----END PRIVATE KEY-----"],
"certificate" : [],
"active" : [ "true" ],
"priority" : [ "123" ],
"enabled" : [ "true" ],
"algorithm" : [ "RS256" ]
}
}
A couple of notes regarding the payload:
- The name field is the human-readable name of the key
- The parentId should match the "id" property of the realm the keypair should belong to, which is essentially the realm name.
- The priority field determines which key Keycloak actually uses at any given time
- The privateKey as created above contains the whole keypair (private + public).Keycloak automatically extracts the public key from the contents of this parameter. You do not have to, nor should you, define a publicKey field: if you do, you will not be able to modify some of the paraneters under the "config" hash, such as "enabled" or "priority", while modifying "active" does work. This may have to do with the Keycloak config parser choking at "publicKey" and ignoring the values of parameters that come after it.
- You can probably also pass a certificate signed with the private key. This makes Keycloak use your own certificate instead of creating a certificate automatically using your private key.
To apply the payload use
$ /opt/keycloak/bin/kcadm.sh create components -r test -f payload-add-key.json --no-config --server http://localhost:8080/auth --realm master --user admin --password changeme
Getting custom realm key's id
Keycloak uses unique, typically machine-generated identifiers for various resources: this also applies to custom realm keys in Keycloak. According to kcadm.sh documentation you need the key's "Provider ID" to modify or delete it. This is true, but also very confusing: when looking at the keys in JSON you see a property called "providerId", whose value can be "rsa", "rsa-generated" or such. Using that as part of the URL in Admin REST API calls will just fail. Instead, you should use the key's id as part of the URL and ignore its providerId property completely.
To get the ID for a key list all realm keys and check the value of "id" for your custom realm key:
/opt/keycloak/bin/kcadm.sh get keys -r test --no-config --server http://localhost:8080/auth --realm master --user admin --password changeme
Armed with this information you can now update and remove your custom keys.
Modifying custom realm keys
You can modify custom realm keys in Keycloak with kcadm.sh. The first and easier option is to pass the changes as a value to the -s parameter:
/opt/keycloak/bin/kcadm.sh update components/<provider-id> -r test -s 'config.active=["false"]' --no-config --server http://localhost:8080/auth --realm Test --user admin --password changeme
Alternatively you can make the modification(s) using a JSON payload. In fact, when you're working directly with the Admin REST API that's your only option. Note that in that case the payload you use has to include the full representation of the key object. For example:
{ "id":"<provider-id>",
"name":"rsa-shared",
"providerId":"rsa",
"providerType":"org.keycloak.keys.KeyProvider",
"parentId":"Test",
"config": {
"publicKey" : ["-----BEGIN PUBLIC KEY-----\n<public-key-body>\n-----END PUBLIC KEY-----"],
"privateKey": ["-----BEGIN PRIVATE KEY-----\n<private-key-body>\n-----END PRIVATE KEY-----"],
"active":["true"],
"priority":["101"],
"enabled":["true"],
"algorithm":["RS256"]
}
}
Once you've crafter the payload you can update the key object:
/opt/keycloak/bin/kcadm.sh update components/<provider-id> -r test -f update.json --no-config --server http://localhost:8080/auth --realm Test --user admin --password changeme
Removing custom realm keys
To remove a custom realm key from Keycloak delete the component like this:
/opt/keycloak/bin/kcadm.sh delete components/<provider-id> -r test --no-config --server http://localhost:8080/auth --realm master --user admin --password changeme
Ansible module for managing Keycloak realm keys
If you use Ansible for managing Keycloak you can use Puppeteers' keycloak_realm_key module in the puppeteers.keycloak collection. Here's sample usage:
- name: Manage Keycloak realm key
keycloak_realm_key:
name: custom
state: present
parent_id: master
provider_id: "rsa"
auth_keycloak_url: "http://localhost:8080/auth"
auth_username: keycloak
auth_password: keycloak
auth_realm: master
config:
private_key: "{{ private_key }}"
enabled: true
active: true
priority: 120
algorithm: "RS256"
Note that the private key should be string with literal linefeed characters ('\n') instead of actual linefeeds.
Addendum: displaying details of a realm certificate
As I mentioned above, Keycloak realm keys always have a certificate associated with them. To have a look at the certificate click on the "Certificate" field and copy and paste the base64-encoded content to a file between the certificate markers and save the results to a file:
-----BEGIN CERTIFICATE-----
<put certificate content here>
-----END CERTIFICATE-----
To view certificate details use openssl:
openssl x509 -in <file> -noout -text
There does not seem to be anything special about the managed Keycloak keys or certificates. For example Keycloak does not add any X509v3 extensions to the certificate.
Addendum: notes on custom key usage
Keycloak handles imported realm keys somewhat differently from managed realm keys. If you activate a custom realm key, you can only use it for signing (SIG). You cannot use it for encyption (ENC). When you open a managed realm key, you can determine whether it is used for signing or encryption. That said, this may be an issue with the Admin Console in some Keycloak versions (e.g. 15.0.2) rather than an actual hard limitation.
I tried importing an actual certificate which had X509 Key Usage set to allow encryption and signing, but that did not help at all. So, Keycloak does not seem to determine key usage from the certificate fields. If you want to experiment with this, though, here are quick instructions. First create an OpenSSL config file, here test.cnf:
[ keycloak ]
keyUsage = digitalSignature, dataEncipherment
For details on keyUsage look here. Next create a private key, a CSR and a certificate:
openssl req -newkey rsa:2048 -nodes -keyout domain.key -out domain.csr
openssl x509 -extfile test.cnf -extensions keycloak -signkey domain.key -in domain.csr -req -days 365 -out domain.crt
The -extensions field determines the section in the config file to get the configuration from. It is probably redundant, as OpenSSL should default to using the settings in the first section anyways.
Once you have the private key and the certificate, you can import them to Keycloak. Then, if you view the certificate you should see the X509v3 Key Usage section there:
X509v3 extensions:
X509v3 Key Usage:
Digital Signature, Data Encipherment
As I mentioned above, this does not seem to affect Keycloak's idea of what the key should or can be used for.