IDM 7.3.1

Security

Secure ForgeRock® Identity Management deployments.

Out-of-the-box, IDM is set up for ease of development and deployment. When you deploy IDM in production, there are specific precautions you should take to minimize security breaches. This guide describes the IDM security mechanisms and strategies you can use to reduce risk and mitigate threats to IDM security.

ForgeRock Identity Platform™ serves as the basis for our simple and comprehensive Identity and Access Management solution. We help our customers deepen their relationships with their customers, and improve the productivity and connectivity of their employees and partners. For more information about ForgeRock and about the platform, refer to https://www.forgerock.com.

The ForgeRock Common REST API works across the platform to provide common ways to access web resources and collections of resources.

Secret stores, certificates, and keys

Encryption makes it possible to protect sensitive data. IDM depends on encryption to negotiate secure network connections, and to keep sensitive data confidential. Encryption in turn depends on keys. IDM stores keys in secret stores, and supports the following secret store types:

  • File-based keystores

  • Property-based secret stores

  • Hardware Security Modules (HSM)

In production environments, avoid using self-signed certificates and certificates associated with insecure ciphers.

Secret stores

Configure secret stores in your project’s conf/secrets.json file, which has the following default configuration:

{
  "stores": [
    {
      "name": "mainKeyStore",
      "class": "org.forgerock.openidm.secrets.config.FileBasedStore",
      "config": {
        "file": "&{openidm.keystore.location|&{idm.install.dir}/security/keystore.jceks}",
        "storetype": "&{openidm.keystore.type|JCEKS}",
        "providerName": "&{openidm.keystore.provider|SunJCE}",
        "storePassword": "&{openidm.keystore.password|changeit}",
        "mappings": [
          {
            "secretId" : "idm.default",
            "types": [ "ENCRYPT", "DECRYPT" ],
            "aliases": [ "&{openidm.config.crypto.alias|openidm-sym-default}" ]
          },
          ...
        ]
      }
    },
    {
      "name": "mainTrustStore",
      "class": "org.forgerock.openidm.secrets.config.FileBasedStore",
      "config": {
        "file": "&{openidm.truststore.location|&{idm.install.dir}/security/truststore}",
        "storetype": "&{openidm.truststore.type|JKS}",
        "providerName": "&{openidm.truststore.provider|SUN}",
        "storePassword": "&{openidm.truststore.password|changeit}",
        "mappings": [
        ]
      }
    }
  ],
  "populateDefaults": true
}

The mainKeyStore and mainTrustStore properties configure the default secret stores. IDM requires these properties in order to start up. Do not change the property names because they are also provided to third-party products that need a single keystore and a single truststore.

mainKeyStore

The main keystore references a Java Cryptography Extension Keystore (JCEKS) located at /path/to/openidm/security/keystore.jceks.

mainTrustStore

The main truststore references a file-based truststore located at /path/to/openidm/security/truststore.

populateDefaults

When IDM first starts up, it checks the secrets configuration. If "populateDefaults": true, IDM writes a number of encryption keys to the keystore, required to encrypt specific data.

You can manage these keystores and truststores using the keytool command, included in your Java installation. For information about the keytool command, refer to https://docs.oracle.com/en/java/javase/11/tools/keytool.html.

Each configured store has a name and class, and the following configuration properties:

file

For file-based secret stores, this property references the path to the store file, for example, &{idm.install.dir}/security/keystore.jceks}. Hardware security modules do not have a file property.

storetype

The type of secret store. IDM supports a number of store types, including JCEKS, JKS, PKCS #11, and PKCS #12.

providerName

Sets the name of the cryptographic service provider; for example, SunPKCS11 or softHSM. If no provider is specified, the JRE default is used.

storePassword

The password to the secret store. For the default IDM keystore and trustore, the password is changeit. You should change this password in a production deployment, as described in Changing the Default Keystore Password.

mappings

This object lets you map keys and certificates in the secret stores to specific encryption and decryption functionality in IDM. A secrets mapping object has the following structure:

{
    "secretId" : "idm.config.encryption",
    "types": [ "ENCRYPT", "DECRYPT" ],
    "aliases": [ "&{openidm.config.crypto.alias|openidm-sym-default}" ]
}
  • secretId lets you map a secret to one or more aliases and gives an indication of the secret’s purpose. For example, idm.config.encryption indicates the aliases that are used to encrypt and decrypt sensitive configuration properties.

  • types indicates what the keys are used for; for example, encryption and decryption of sensitive property values.

  • aliases are the key aliases in the secret store that are used for this purpose. You can add as many aliases as necessary. The first alias in the list determines which alias is the active one. Active secrets are used for signature generation and encryption.

    The aliases in the default keystore are described in Default keystore.

The default secret IDs and the aliases to which they are mapped are listed in Mapping SecretIDs to Key Aliases.

All these properties have a resolvable property value by default; for example &{openidm.keystore.location}, that allows you to use property value substitution. If no configuration expression has been set for a specific property, the value following the vertical bar (|) is used. In the following property, the password is changeit unless you have set a configuration expression in one of the property resolver locations:

"storePassword": "&{openidm.keystore.password|changeit}"

For more information, refer to Property Value Substitution.

Mapping secretIDs to key aliases

secretId alias Description

idm.default

openidm-sym-default

Encryption keystore for legacy JSON objects that do not contain a purpose value in their $crypto block

idm.config.encryption

openidm-sym-default

Encrypts configuration information

idm.password.encryption

openidm-sym-default

Encrypts managed user passwords

idm.jwt.session.module.encryption

openidm-localhost

Encrypts JWT session tokens

idm.jwt.session.module.signing

openidm-jwtsessionhmac-key

Signs JWT session tokens using HMAC

idm.selfservice.encryption

openidm-selfservice-key

Encrypts JWT self-service tokens

idm.selfservice.signing

selfservice

Signs JWT session tokens using RSA

idm.assignment.attribute.encryption

openidm-sym-default

Encrypts confidential assignment attributes

Default keystore

IDM generates a number of encryption keys in a JCEKS keystore the first time the server starts up. These keys map to the secrets defined in Mapping SecretIDs to Key Aliases. Note that the keystore, and the keys, are generated at startup and are not prepackaged. The keys are generated only if they do not already exist. You cannot specify custom aliases for these default keys.

To use a different keystore type, such as PKCS #12, create the keystore and generate the keys before you start IDM. This prevents IDM from generating the keys on startup. You can also convert the existing JCEKS keystore to a PKCS #12 keystore. If you use a different keystore type, you must edit the openidm.keystore.type property (in the conf/secrets.json file) to match the new type.

Use the keytool command to list the default encryption keys, as follows:

keytool \
-list \
-keystore /path/to/openidm/security/keystore.jceks \
-storepass changeit \
-storetype JCEKS
Keystore type: JCEKS
Keystore provider: SunJCE

Your keystore contains 5 entries

openidm-sym-default, Nov 5, 2019, SecretKeyEntry,
openidm-jwtsessionhmac-key, Nov 5, 2019, SecretKeyEntry,
selfservice, Nov 5, 2019, PrivateKeyEntry,
Certificate fingerprint (SHA-256): E9:0B:BA:FB:58:73:02:FC...:7B
openidm-selfservice-key, Nov 5, 2019, SecretKeyEntry,
openidm-localhost, Nov 5, 2019, PrivateKeyEntry,
Certificate fingerprint (SHA-256): 21:50:6C:90:C7:A7:F7:32...:1B

If you are using IDM in a cluster, you must share these keys among all nodes in the cluster. The easiest way to do this is to generate a keystore with the appropriate keys and share the keystore in some way; for example, by using a filesystem that is shared between the nodes.

Change the default keystore password

The default keystore password is changeit. You should change this password in a production environment.

Repeat this procedure on each node if you run multiple nodes in a cluster to ensure that the new password is present on all nodes.

  1. Shut down the server if it is running.

  2. Use the keytool command to change the keystore password. The following command changes the keystore password to newPassword:

    keytool \
    -storepasswd \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass changeit
    New keystore password: newPassword
    Re-enter new keystore password: newPassword
  3. Change the passwords of the default encryption keys.

    IDM uses a number of encryption keys, listed in Mapping SecretIDs to Key Aliases, whose passwords are also changeit by default. The passwords of each of these keys must match the password of the keystore.

    To get the list of keys in the keystore, run the following command:

    keytool \
    -list \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass newPassword
    Keystore type: JCEKS
    Keystore provider: SunJCE
    
    Your keystore contains 5 entries
    
    openidm-sym-default, May 4, 2021, SecretKeyEntry,
    selfservice, May 4, 2021, PrivateKeyEntry, Certificate fingerprint (SHA-256): fingerprint
    openidm-jwtsessionhmac-key, May 4, 2021, SecretKeyEntry,
    openidm-localhost, May 4, 2021, PrivateKeyEntry, Certificate fingerprint (SHA-256): fingerprint
    openidm-selfservice-key, May 4, 2021, SecretKeyEntry,

    Change the passwords of each default encryption key as follows:

    keytool \
    -keypasswd \
    -alias openidm-localhost \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass newPassword
    Enter key password for <openidm-localhost> changeit
    New key password for <openidm-localhost>: newPassword
    Re-enter new key password for <openidm-localhost>: newPassword
    keytool \
    -keypasswd \
    -alias openidm-sym-default \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass newPassword
    Enter key password for <openidm-sym-default> changeit
    New key password for <openidm-sym-default>: newPassword
    Re-enter new key password for <openidm-sym-default>: newPassword
    keytool \
    -keypasswd \
    -alias openidm-selfservice-key \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass newPassword
    Enter key password for <openidm-selfservice-key> changeit
    New key password for <openidm-selfservice-key>: newPassword
    Re-enter new key password for <openidm-selfservice-key>: newPassword
    keytool \
    -keypasswd \
    -alias selfservice \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass newPassword
    Enter key password for <selfservice> changeit
    New key password for <selfservice>: newPassword
    Re-enter new key password for <selfservice>: newPassword
    keytool \
    -keypasswd \
    -alias openidm-jwtsessionhmac-key \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype jceks \
    -storepass newPassword
    Enter key password for <openidm-jwtsessionhmac-key> changeit
    New key password for <openidm-jwtsessionhmac-key>: newPassword
    Re-enter new key password for <openidm-jwtsessionhmac-key>: newPassword
  4. Configure a new expression resolver file to store only the keystore password:

    1. Create a new directory in /path/to/openidm/resolver/ that will contain only the properties file for keystore passwords. For example:

      mkdir /path/to/openidm/resolver/keystore
      Substituted properties are not encrypted by default. You must therefore secure access to this directory, using the appropriate permissions.
    2. Set the IDM_ENVCONFIG_DIRS environment variable to include the new directory:

      export IDM_ENVCONFIG_DIRS=/path/to/openidm/resolver/,/path/to/openidm/resolver/keystore
    3. Create a .json or .properties file in that secure directory, that contains the new keystore password as a resolvable IDM property. For example, add one of the following files to that directory:

      • keystorepwd.properties

      • keystorepwd.json

      openidm.keystore.password=newPassword
      {
          "openidm" : {
              "keystore" : {
                  "password" : "newPassword"
              }
          }
      }
  5. Restart IDM.

Encryption key management

Most regulatory requirements mandate that the keys used to decrypt sensitive data be rotated out and replaced with new keys on a regular basis. The main purpose of rotating encryption keys is to reduce the amount of data encrypted with that key, so that the potential impact of a security breach with a specific key is reduced. You can update encryption keys in several ways, including the following:

Manual key rotation

IDM evaluates keys in secrets.json sequentially. For example, assume that you have added a new key named my-new-key to the keystore. To use this new key to encrypt passwords, you would include my-new-key as the first alias in the idm.password.encryption secret:

{
    "secretId" : "idm.password.encryption",
    "types": [ "ENCRYPT", "DECRYPT" ],
    "aliases": [ "my-new-key", "&{openidm.config.crypto.alias|openidm-sym-default}" ]
}

The properties that use this key (in this case, passwords) are re-encrypted with the new key the next time the managed object is updated. You do not need to restart the server.

If you rotate an encryption key, the active encryption key might not be the correct key to use for decryption of properties that have already been encrypted with a previous key.

You must therefore keep all applicable keys in secrets.json until every object that is encrypted with old keys have been updated with the latest key.

You can force key rotation on all managed objects by running the triggerSyncCheck action on the entire managed object data set. The triggerSyncCheck action examines the crypto blob of each object and updates the encrypted property with the correct key.

For example, the following command forces all managed user objects to use the new key:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--cacert ca-cert.pem \
--header "Content-Type: application/json" \
--request POST \
"https://localhost:8443/openidm/managed/user/?_action=triggerSyncCheck"
{
    "status": "OK",
    "countTriggered": 10
}

In a large managed object set, the triggerSyncCheck action can take a long time to run on only a single node. You should therefore avoid using this action if your data set is large. An alternative to running triggerSyncCheck over the entire data set is to iterate over the managed data set and call triggerSyncCheck on each individual managed object. You can call this action manually or by using a script.

The following example shows the manual commands that must be run to launch the triggerSyncCheck action on all managed users. The first command uses a query filter to return all managed user IDs. The second command iterates over the returned IDs calling triggerSyncCheck on each ID:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--cacert ca-cert.pem \
"https://localhost:8443/openidm/managed/user?_queryFilter=true&_fields=_id"
{
  "result": [
    {
      "_id": "9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb",
      "_rev": "000000004988917b"
    },
    {
      "_id": "55ef0a75-f261-47e9-a72b-f5c61c32d339",
      "_rev": "00000000dd89d671"
    },
    {
      "_id": "998a6181-d694-466a-a373-759a05840555",
      "_rev": "000000006fea54ad"
    },
    ...
  ]
}
curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Accept-API-Version: resource=1.0" \
--cacert ca-cert.pem \
--header "Content-Type: application/json" \
--request POST \
"https://localhost:8443/openidm/managed/user/9dce06d4-2fc1-4830-a92b-bd35c2f6bcbb?_action=triggerSyncCheck"

In large data sets, the most efficient way to achieve key rotation is to use the scheduler service to launch these commands. The following section shows how to use the scheduler service for this purpose.

Scheduled key rotation

This example uses a script to generate multiple scheduled tasks. Each scheduled task iterates over a subset of the managed object set (defined by the pageSize). The generated scheduled task then calls another script that launches the triggerSyncCheck action on each managed object in that subset.

To schedule key rotation, set up a similar schedule:

  1. Create a schedule configuration named schedule-triggerSyncCheck.json in your project’s conf directory. That schedule should look as follows:

    {
        "enabled" : true,
        "persisted" : true,
        "type" : "cron",
        "schedule" : "0 * * * * ? *",
        "concurrentExecution" : false,
        "invokeService" : "script",
        "invokeContext" : {
            "waitForCompletion" : false,
            "script": {
                "type": "text/javascript",
                "name": "sync/scheduleTriggerSyncCheck.js"
            },
            "input": {
                "pageSize": 2,
                "managedObjectPath" : "managed/user",
                "quartzSchedule" : "0 * * * * ? *"
            }
        }
    }

    You can change the following parameters of this schedule configuration to suit your deployment:

    pageSize

    The number of objects that each generated schedule will handle. This value should be high enough not to create too many schedules. The number of schedules that is generated is equal to the number of objects in the managed object store, divided by the page size.

    For example, if there are 500 managed users and a page size of 100, five schedules will be generated (500/100).

    managedObjectPath

    The managed object set over which the scheduler iterates. For example, managed/user if you want to iterate over the managed user object set.

    quartzSchedule

    The schedule at which these tasks should run. For example, to run the task every minute, this value would be 0 * * * * ? *.

  2. The schedule calls a scheduleTriggerSyncCheck.js script, located in a directory named project-dir/script/sync. Create the sync directory, and add the script:

    var managedObjectPath = object.managedObjectPath;
    var pageSize = object.pageSize;
    var quartzSchedule = object.quartzSchedule;
    
    var managedObjects = openidm.query(managedObjectPath, {
        "_queryFilter": "true",
        "_fields": "_id"
    });
    
    var numberOfManagedObjects = managedObjects.result.length;
    
    for (var i = 0; i < numberOfManagedObjects; i += pageSize) {
        var scheduleId = java.util.UUID.randomUUID().toString();
        var ids = managedObjects.result.slice(i, i + pageSize).map(function(obj) {
            return obj._id
        });
        var schedule = newSchedule(scheduleId, ids);
        openidm.create("/scheduler", scheduleId, schedule);
    }
    
    function newSchedule(scheduleId, ids) {
        var schedule = {
            "enabled": true,
            "persisted": true,
            "type": "cron",
            "schedule": quartzSchedule,
            "concurrentExecution": false,
            "invokeService": "script",
            "invokeContext": {
                "waitForCompletion": true,
                "script": {
                    "type": "text/javascript",
                    "name": "sync/triggerSyncCheck.js"
                },
                "input": {
                    "ids": ids,
                    "managedObjectPath": managedObjectPath,
                    "scheduleId": scheduleId
                }
            }
        };
        return schedule;
    }
  3. Each generated scheduled task calls a script named triggerSyncCheck.js. Create the script in your project’s script/sync directory:

    var ids = object.ids;
    var scheduleId = object.scheduleId;
    var managedObjectPath = object.managedObjectPath;
    
    for (var i = 0; i & lt; ids.length; i++) {
        openidm.action(managedObjectPath + "/" + ids[i], "triggerSyncCheck", {}, {});
    }
    
    openidm.delete("scheduler/" + scheduleId, null);
  4. Test the key rotation:

    1. Edit your project’s conf/managed.json file to return user passwords by default by setting "scope" : "public".

      "password" : {
          ...
          "encryption" : {
              "purpose" : "idm.password.encryption"
          },
          "scope" : "public",
          ...
      }

      Because passwords are not returned by default, you will not be able to refer to the new encryption on the password unless you change the property’s scope.

    2. Perform a GET request to return any managed user entry in your data set:

      curl \
      --header "X-OpenIDM-Username: openidm-admin" \
      --header "X-OpenIDM-Password: openidm-admin" \
      --header "Accept-API-Version: resource=1.0" \
      --cacert ca-cert.pem \
      --request GET \
      "https://localhost:8443/openidm/managed/user/ccd92204-aee6-4159-879a-46eeb4362807"
      {
        "_id" : "ccd92204-aee6-4159-879a-46eeb4362807",
        "_rev" : "0000000009441230",
        "preferences" : {
          "updates" : false,
          "marketing" : false
        },
        "mail" : "bjensen@example.com",
        "sn" : "Jensen",
        "givenName" : "Babs",
        "userName" : "bjensen",
        "password" : {
          "$crypto" : {
            "type" : "x-simple-encryption",
            "value" : {
              "cipher" : "AES/CBC/PKCS5Padding",
              "stableId" : "openidm-sym-default",
              "salt" : "CVrKDuzfzunXfTDbCwU1Rw==",
              "data" : "1I5tWT5aRH/12hf5DgofXA==",
              "keySize" : 16,
              "purpose" : "idm.password.encryption",
              "iv" : "LGE+jnC3ZtyvrE5pfuSvtA==",
              "mac" : "BEXQ1mftxA63dXhJO6dDZQ=="
            }
          }
        },
        "accountStatus" : "active",
        "effectiveRoles" : [ ],
        "effectiveAssignments" : [ ]
      }

      Notice that the user’s password is encrypted with the default encryption key (openidm-sym-default).

    3. Create a new encryption key in the IDM keystore:

      keytool \
      -genseckey \
      -alias my-new-key \
      -keyalg AES \
      -keysize 128 \
      -keystore /path/to/openidm/security/keystore.jceks \
      -storetype JCEKS
    4. Shut down the server for keystore to be reloaded.

    5. Change your project’s conf/managed.json file to change the encryption purpose for managed user passwords:

      "password" : {
          ...
          "encryption" : {
              "purpose" : "idm.password.encryption2"
          },
          "scope" : "public",
          ...
      }
    6. Add the corresponding purpose to the secrets.json file in the mainKeyStore code block:

      "idm.password.encryption2": {
        "types": [ "ENCRYPT", "DECRYPT" ],
        "aliases": [
          {
            "alias": "my-new-key"
          }
        ]
      }
    7. Restart the server and wait one minute for the scheduled task to start.

    8. Perform a GET request again to return the entry of the managed user that you returned previously:

      curl \
      --header "X-OpenIDM-Username: openidm-admin" \
      --header "X-OpenIDM-Password: openidm-admin" \
      --header "Accept-API-Version: resource=1.0" \
      --cacert ca-cert.pem \
      --request GET \
      "https://localhost:8443/openidm/managed/user/ccd92204-aee6-4159-879a-46eeb4362807"
      {
        "_id" : "ccd92204-aee6-4159-879a-46eeb4362807",
        "_rev" : "0000000009441230",
        "preferences" : {
          "updates" : false,
          "marketing" : false
        },
        "mail" : "bjensen@example.com",
        "sn" : "Jensen",
        "givenName" : "Babs",
        "userName" : "bjensen",
        "password" : {
          "$crypto" : {
            "type" : "x-simple-encryption",
            "value" : {
              "cipher" : "AES/CBC/PKCS5Padding",
              "stableId" : "my-new-key",
              "salt" : "CVrKDuzfzunXfTDbCwU1Rw==",
              "data" : "1I5tWT5aRH/12hf5DgofXA==",
              "keySize" : 16,
              "purpose" : "idm.password.encryption2",
              "iv" : "LGE+jnC3ZtyvrE5pfuSvtA==",
              "mac" : "BEXQ1mftxA63dXhJO6dDZQ=="
            }
          }
        },
        "accountStatus" : "active",
        "effectiveRoles" : [ ],
        "effectiveAssignments" : [ ]
      }

      The user password is now encrypted with my-new-key.

Change the active alias for managed object encryption

This example describes how to configure and then change the managed object encryption key with a scheduled task. You’ll create a new key, set up a managed user, add the key to secrets.json, restart IDM, run a triggerSyncCheck, and review the result.

  1. Create a new key for the IDM keystore in the security/keystore.jceks file:

    keytool \
    -genseckey \
    -alias my-new-key \
    -keyalg AES \
    -keysize 128 \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype JCEKS
  2. For the purpose of this example, in managed.json, set "scope" : "public" to expose the applied password encryption key.

  3. Create a managed user:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --cacert ca-cert.pem \
    --header "Content-Type: application/json" \
    --request PUT \
    --data '{
      "userName": "rsutter",
      "sn": "Sutter",
      "givenName": "Rick",
      "mail": "rick@example.com",
      "telephoneNumber": "6669876987",
      "description": "Another user",
      "country": "USA",
      "password": "Passw0rd"
    }' \
    "https://localhost:8443/openidm/managed/user/ricksutter"
  4. Add the newly created my-new-key alias to your conf/secrets.json file, in the idm.password.encryption code block:

    "idm.password.encryption": {
      "types": [ "ENCRYPT", "DECRYPT" ],
      "aliases": [ "my-new-key", "&{openidm.config.crypto.alias|openidm-sym-default}" ]
    }
  5. To apply the new key to your configuration, shut down and restart IDM.

  6. Force IDM to update the key for your users with the triggerSyncCheck action:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --cacert ca-cert.pem \
    --header "Content-Type: application/json" \
    --request POST \
    "https://localhost:8443/openidm/managed/user/?_action=triggerSyncCheck"
  7. Review the result for the newly created user, ricksutter:

    curl \
    --header "X-OpenIDM-Username: openidm-admin" \
    --header "X-OpenIDM-Password: openidm-admin" \
    --header "Accept-API-Version: resource=1.0" \
    --cacert ca-cert.pem \
    --request GET  \
    "https://localhost:8443/openidm/managed/user/ricksutter"
  8. In the output, you should see the new my-new-key encryption key applied to that user’s password:

    ...
     "password": {
       "$crypto": {
         "type": "x-simple-encryption",
         "value": {
           "cipher": "AES/CBC/PKCS5Padding",
           "stableId": "my-new-key",
           "salt": "bGyKG3PKmwHONOfxerr1Qg==",
           "data": "6vXZiJ3ZNN/UUnsrT7dTQw==",
           "keySize": 16,
           "purpose": "idm.password.encryption",
           "iv": "doAdtxfWfFbrPIIfubGi5g==",
           "mac": "OML6xd9qvDtD5AvMc1Tc3A=="
         }
       }
     },
    ...

CA-signed certificates

You can use existing CA-signed certificates to secure connections and data by importing the certificates into the keystore, and referencing them your boot.properties file. Use the keytool command to import an existing certificate into the keystore.

Import CA-signed certificates

The following process imports a CA-signed certificate into the keystore, with the alias example-com. Replace this alias with the alias of your certificate:

  1. Stop the server if it is running.

  2. Back up your existing openidm/security/keystore and openidm/security/truststore files.

  3. Use the keytool command to import your existing certificate into the keystore, substituting your specific information:

    example-cert.p12

    The name of your certificate file.

    srcstorepass

    The certificate password.

    example-com

    The existing certificate alias.

    destination keystore password

    The password for the keystore.

    keytool \
    -importkeystore \
    -srckeystore example-cert.p12 \
    -srcstoretype PKCS12 \
    -srcstorepass changeit \
    -srcalias example-com \
    -destkeystore keystore.jceks \
    -deststoretype JCEKS \
    -destalias openidm-localhost
    Importing keystore example-cert.p12 to keystore.jceks…​
    Enter destination keystore password: changeit

    The keytool command creates a trusted certificate entry with the specified alias and associates it with the imported certificate. The certificate is imported into the keystore with the alias openidm-localhost. If you want to use a different alias, you must modify your resolver/boot.properties file to reference that alias, as shown in the following step.

    The certificate entry password must be the same as the IDM keystore password. If the source certificate entry password is different from the target keystore password, use the -destkeypass option with the same value as the -deststorepass option to make the certificate password match the target keystore password. If you do not make these passwords the same, no error is generated when you import the certificate (or when you read the certificate entry in the destination keystore), but IDM will fail to start with the following exception:

    java.security.UnrecoverableKeyException: Given final block not properly padded.
  4. If you specified an alias other than openidm-localhost for the new certificate, change the value of openidm.https.keystore.cert.alias in your resolver/boot.properties file to that alias. For example, if your new certificate alias is example-com, change the boot.properties file as follows:

    openidm.https.keystore.cert.alias=example-com
  5. Restart the server.

Delete certificates

When using CA-signed certificates for encryption, it is a best practice to delete all unused default certificates from the keystore and truststore using the keytool command, as shown in the following examples:

  • To delete the openidm-localhost certificate from the keystore:

    keytool \
    -delete \
    -alias openidm-localhost \
    -keystore /path/to/openidm/security/keystore.jceks \
    -storetype JCEKS \
    -storepass changeit
  • To delete the openidm-localhost certificate from the truststore:

    keytool \
    -delete \
    -alias openidm-localhost \
    -keystore /path/to/openidm/security/truststore \
    -storepass changeit

You can use similar commands to delete custom certificates from the keystore and truststore, specifying the certificate alias in the request.

Delete root CA certificates

The Java and IDM truststore files include a number of root CA certificates. Although the probability of a compromised root CA certificate is low, it is a best practice to delete unused root CA certificates.

To review the list of root CA certificates in the IDM truststore:

keytool \
-list \
-keystore /path/to/openidm/security/truststore \
-storepass changeit

On UNIX/Linux systems, you can find additional lists of root CA certificates in files named cacerts. These include root CA certificates associated with your Java environment, typically located in the ${JAVA_HOME}/jre/lib/security/cacerts directory.

Before making changes to Java environment keystore files, verify any Java-related cacerts files are up-to-date and that you have a supported Java version installed.

You can delete root CA certificates with the keytool command. For example, to remove the hypothetical examplecomca2 certificate from the truststore:

keytool \
-delete \
-keystore /path/to/openidm/security/truststore \
-storepass changeit \
-alias examplecomca2

On Windows systems, you can manage certificates with the Microsoft Management Console (MMC) snap-in tool. For more information, refer to Working With Certificates.

Property-based secret stores

IDM servers can read keys and trusted certificates from properties that contain keys in Privacy-Enhanced Mail (PEM) format.

The following example configures a property-based secret store, and adds an RSA PEM secret whose purpose is to encrypt and decrypt managed user passwords:

  1. Add a PropertyBasedStore secret store definition to your conf/secrets.json file:

    {
        "name": "pemStore",
        "class": "org.forgerock.openidm.secrets.config.PropertyBasedStore",
        "config": {
            "format": "PEM",
            "algorithm": "RSA",
            "mappings": [
                {
                    "secretId": "idm.pem.purpose",
                    "types": [
                        "ENCRYPT",
                        "DECRYPT"
                    ]
                }
            ]
        }
    }
  2. Create an RSA PEM key:

    openssl genrsa -out private-key.pem 3072
  3. Display the private key. For example:

    more private-key.pem
    -----BEGIN RSA PRIVATE KEY-----
    MIIG4w…​lrDgao
    -----END RSA PRIVATE KEY-----
  4. Use a text editor to convert your certificate to a single line, replacing line breaks with newline characters (/n). For example, on UNIX systems:

    awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' private-key.pem
    -----BEGIN RSA PRIVATE KEY-----\nMIIG4w…​lrDgao\n-----END RSA PRIVATE KEY-----\n%
  5. Copy the single-line private key and paste it into your resolver/boot.properties file, as a value of the secretId that you specified in Step 1. For example:

    idm.pem.purpose=-----BEGIN RSA PRIVATE KEY-----\nMIIG4w...lrDgao\n-----END RSA PRIVATE KEY-----\n%
  6. Modify the encryption purpose for the managed user password in your managed object configuration to use the PropertyBaseStore secret store that you created in Step 1:

    "password" : {
        "title" : "Password",
        "description" : "Password",
        "type" : "string",
        "viewable" : false,
        "searchable" : false,
        "userEditable" : true,
        "encryption" : {
            "purpose" : "idm.pem.purpose",
            "cipher" : "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
        }
        ...
    }

    IDM now encrypts and decrypts passwords with the RSA PEM key.

Hardware security module (HSM)

This topic demonstrates how to use a PKCS #11 device, such as a hardware security module (HSM), to store the keys used to secure communications. IDM supports retrieval of secrets from HSMs either locally or over the network.

On Windows systems using the 64-bit JDK, the Sun PKCS #11 provider is available only from JDK version 1.8b49. If you want to use a PKCS #11 device on Windows, use the 32-bit version of the JDK, or upgrade your 64-bit JDK to version 1.8b49 or higher.

HSM configuration

This section assumes that you have access to an HSM device (or a software emulation of an HSM device, such as SoftHSM) and that the HSM provider has been configured and initialized.

The command-line examples in this section use SoftHSM for testing purposes. Before you start, set the correct environment variable for the SoftHSM configuration, for example:

export SOFTHSM2_CONF=/path/to/softhsm/2.0.0/etc/softhsm2.conf

Also initialize slot 0 on the provider, with a command similar to the following:

softhsm2-util --init-token --slot 0 --label "My token 1"

This token initialization requests two PINs—an SO PIN and a user PIN. You can use the SO PIN to reinitialize the token. The user PIN is provided to IDM so that it can interact with the token. Remember the values of these PINs because you will use them later in this section.

The PKCS #11 standard uses a configuration file to interact with the HSM device. The following example shows a basic configuration file for SoftHSM:

name = softHSM
library = /path/to/softhsm/2.0.0/lib/softhsm/libsofthsm2.so
slot = 1
attributes(generate, *, *) = {
   CKA_TOKEN = true
}
attributes(generate, CKO_CERTIFICATE, *) = {
   CKA_PRIVATE = false
}
attributes(generate, CKO_PUBLIC_KEY, *) = {
   CKA_PRIVATE = false
}
attributes(*, CKO_SECRET_KEY, *) = {
   CKA_PRIVATE = false
   CKA_EXTRACTABLE = true
}

Your HSM configuration file must include at least the following settings:

name

A suffix to identify the HSM provider. This example uses the softHSM provider.

library

The path to the PKCS #11 library.

slot

The slot number to use, specified as a string. Make sure that the slot you specify here has been initialized on the HSM device.

The attributes specify additional PKCS #11 attributes that are set by the HSM. For a complete list of these attributes, refer to the PKCS #11 Reference.

If you are using the JWT Session Module, you must set CKA_EXTRACTABLE = true for secret keys in your HSM configuration file. For example:

attributes(*, CKO_SECRET_KEY, *) = {
   CKA_PRIVATE = false
   CKA_EXTRACTABLE = true
}

The HSM provider must allow secret keys to be extractable because the authentication service serializes the JWT Session Module key and passes it to the authentication framework as a base 64-encoded string.

HSM default encryption keys

When IDM first starts up, it generates a number of encryption keys required to encrypt specific data. If you are using an HSM provider, you must generate these keys manually. The secret keys must use an HMAC algorithm.

This procedure assumes that your HSM configuration file is located at /path/to/hsm/hsm.conf.

  1. The openidm-sym-default key is the default symmetric key required to encrypt the configuration. The following command generates that key in the HSM provider. The -providerArg must point to the HSM configuration file described in HSM Configuration.

    keytool \
    -genseckey \
    -alias openidm-sym-default \
    -keyalg HmacSHA256 \
    -keysize 256 \
    -keystore NONE \
    -storetype PKCS11 \
    -providerClass sun.security.pkcs11.SunPKCS11 \
    -providerArg /path/to/hsm/hsm.conf
    Enter keystore password:

    Enter the password of your HSM device. If you are using SoftHSM, enter your user PIN as the keystore password.

  2. The openidm-selfservice-key is used by the Self-Service UI to encrypt managed user passwords and other sensitive data. Generate the openidm-selfservice-key key:

    keytool \
    -genseckey \
    -alias openidm-selfservice-key \
    -keyalg HmacSHA256 \
    -keysize 256 \
    -keystore NONE \
    -storetype PKCS11 \
    -providerClass sun.security.pkcs11.SunPKCS11 \
    -providerArg /path/to/hsm/hsm.conf
    Enter keystore password: user PIN

    Enter the password of your HSM device. If you are using SoftHSM, enter your user PIN as the keystore password.

  3. The openidm-jwtsessionhmac-key is used by the JWT session module to encrypt JWT session cookies. Generate the JWT session module key:

    keytool \
    -genseckey \
    -alias openidm-jwtsessionhmac-key \
    -keyalg HmacSHA256 \
    -keysize 256 \
    -keystore NONE \
    -storetype PKCS11 \
    -providerClass sun.security.pkcs11.SunPKCS11 \
    -providerArg /path/to/hsm/hsm.conf
    Enter keystore password: user PIN
  4. The openidm-localhost certificate is used to support SSL/TLS. Generate the certificate:

    keytool \
    -genkey \
    -alias openidm-localhost \
    -keyalg RSA \
    -keysize 2048 \
    -keystore NONE \
    -storetype PKCS11 \
    -providerClass sun.security.pkcs11.SunPKCS11 \
    -providerArg /path/to/hsm/hsm.conf
    Enter keystore password: user PIN
    What is your first and last name?
      [Unknown]:  localhost
    What is the name of your organizational unit?
      [Unknown]:
    What is the name of your organization?
      [Unknown]:  OpenIDM Self-Signed Certificate
    What is the name of your City or Locality?
      [Unknown]:
    What is the name of your State or Province?
      [Unknown]:
    What is the two-letter country code for this unit?
      [Unknown]:
    Is CN=localhost, OU=Unknown, O=OpenIDM Self-Signed Certificate, L=Unknown, ST=Unknown, C=Unknown correct?
      [no]:  yes
  5. The selfservice certificate secures requests from the End User UI. Generate the certificate:

    keytool \
    -genkey \
    -alias selfservice \
    -keyalg RSA \
    -keysize 2048 \
    -keystore NONE \
    -storetype PKCS11 \
    -providerClass sun.security.pkcs11.SunPKCS11 \
    -providerArg /path/to/hsm/hsm.conf
    Enter keystore password: user PIN
    What is your first and last name?
      [Unknown]:  localhost
    What is the name of your organizational unit?
      [Unknown]:
    What is the name of your organization?
      [Unknown]:  OpenIDM Self Service Certificate
    What is the name of your City or Locality?
      [Unknown]:
    What is the name of your State or Province?
      [Unknown]:
    What is the two-letter country code for this unit?
      [Unknown]:
    Is CN=localhost,O=OpenIDM Self Service Certificate,OU=None,L=None,ST=None,C=None?
      [no]:  yes
  6. If you are not using the HSM provider for the truststore, you must add the certificates generated in the previous two steps to the default IDM truststore.

    If you are using the HSM provider for the truststore, you can skip this step.

    To add the openidm-localhost certificate to the IDM truststore, export the certificate from the HSM provider, then import it into the truststore:

    keytool \
    -export \
    -alias openidm-localhost \
    -file exportedCert \
    -keystore NONE \
    -storetype PKCS11 \
    -providerClass sun.security.pkcs11.SunPKCS11 \
    -providerArg /path/to/hsm/hsm.conf
    Enter keystore password: user PIN
    Certificate stored in file exportedCert keytool \
    -import \
    -alias openidm-localhost \
    -file exportedCert \
    -keystore /path/to/openidm/security/truststore
    Enter keystore password: changeit
    Owner: CN=localhost, OU=Unknown, O=OpenIDM Self-Signed Certificate, L=…​
    Issuer: CN=localhost, OU=Unknown, O=OpenIDM Self-Signed Certificate, L=…​
    Serial number: 5d2554bd
    Valid from: Fri Aug 19 13:11:54 SAST 2016 until: Thu Nov 17 13:11:54 SAST 2016
    Certificate fingerprints:
    	 MD5:  F1:9B:72:7F:7B:79:58:29:75:85:82:EC:79:D8:F9:8D
    	 SHA1: F0:E6:51:75:AA:CB:14:3D:C5:E2:EB:E5:7C:87:C9:15:43:19:AF:36
    	 SHA256: 27:A5:B7:0E:94:9A:32:48:0C:22:0F:BB:7E:3C:22:2A:64:B5:45:24:14:70:...
    	 Signature algorithm name: SHA256withRSA
    	 Version: 3
    
    Extensions:
    
    #1: ObjectId: 2.5.29.14 Criticality=false
    SubjectKeyIdentifier [
    KeyIdentifier [
    0000: 7B 5A 26 53 61 44 C2 5A   76 E4 38 A8 52 6F F2 89  .Z&SaD.Zv.8.Ro..
    0010: 20 04 52 EE                                         .R.
    ]
    ]
    Trust this certificate? [no]:  yes
    Certificate was added to keystore

    The default truststore password is changeit.

Configure IDM to support an HSM provider

To enable IDM to use an HSM provider, make the following configuration changes:

  1. In your secret store configuration (conf/secrets.json ), change the mainKeyStore and mainTrustStore to reference the HSM. For example:

    {
      "stores": [
        {
          "name": "mainKeyStore",
          "class": "org.forgerock.openidm.secrets.config.HsmBasedStore",
          "config": {
            "storetype": "&{openidm.keystore.type|PKCS11}",
            "providerName": "&{openidm.keystore.provider|SunPKCS11-softHSM}",
            "storePassword": "&{openidm.keystore.password|changeit}",
            "mappings": [
              {
                "secretId" : "idm.default",
                "types": [ "ENCRYPT", "DECRYPT" ],
                "aliases": [ "&{openidm.config.crypto.alias|openidm-sym-default}" ]
              },
              {
                "secretId" : "idm.config.encryption",
                "types": [ "ENCRYPT", "DECRYPT" ],
                "aliases": [ "&{openidm.config.crypto.alias|openidm-sym-default}" ]
              },
              {
                "secretId" : "idm.password.encryption",
                "types": [ "ENCRYPT", "DECRYPT" ],
                "aliases": [ "&{openidm.config.crypto.alias|openidm-sym-default}" ]
              },
              {
                "secretId" : "idm.jwt.session.module.encryption",
                "types": [ "ENCRYPT", "DECRYPT" ],
                "aliases": [ "&{openidm.https.keystore.cert.alias|openidm-localhost}" ]
              },
              {
                "secretId" : "idm.jwt.session.module.signing",
                "types": [ "SIGN", "VERIFY" ],
                "aliases": [ "&{openidm.config.crypto.jwtsession.hmackey.alias|openidm-jwtsessionhmac-key}" ]
              },
              {
                "secretId" : "idm.selfservice.signing",
                "types": [ "SIGN", "VERIFY" ],
                "aliases": [ "selfservice" ]
              },
              {
                "secretId" : "idm.selfservice.encryption",
                "types": [ "ENCRYPT", "DECRYPT" ],
                "aliases": [ "&{openidm.config.crypto.selfservice.sharedkey.alias|openidm-selfservice-key}" ]
              }
            ]
          }
        },
        {
          "name": "mainTrustStore",
          "class": "org.forgerock.openidm.secrets.config.HsmBasedStore",
          "config": {
            "storetype": "&{openidm.keystore.type|PKCS11}",
            "providerName": "&{openidm.keystore.provider|SunPKCS11-softHSM}",
            "storePassword": "&{openidm.keystore.password|changeit}",
            "mappings": [
            ]
          }
        }
      ],
      "populateDefaults": false
    }
    The "populateDefaults": false turns off the default key generation. This setting is required for an HSM key provider.
  2. In the IDM Java security file (conf/java.security ), Specify the location of your PKCS #11 configuration file. For example:

    security.provider.14=SunPKCS11 /path/to/pkc11/config/pkcs11.conf

    Templates for the pkcs11.conf file are included in your PKCS package.

    You should now be able to start IDM with the keys in the HSM provider.

FIPS 140-2 compliance

To achieve FIPS 140-2 compliance, configure the Bouncy Castle FIPS libraries with IDM. This enables the use of the Bouncy Castle FIPS keystore and security provider in FIPS-approved mode.

Bouncy Castle FIPS is useful when dealing with government data, where meeting the FIPS 140-2 security requirement is necessary for regulatory compliance.

Bouncy Castle FIPS is less performant than other keystores. The destroyable keys cannot be cached and must be read from the keystore with every use.

To configure IDM to use Bouncy Castle FIPS:

Download the Bouncy Castle libraries

The IDM CLI does not work when using Bouncy Castle FIPS.

To use Bouncy Castle FIPS with IDM, download the libraries:

  1. Download the following libraries from Bouncy Castle to the server/machine where IDM is deployed:

    File Description

    bc-fips-latestVersionNumber.jar(1)

    Contains the Bouncy Castle FIPS security provider implementation.

    bcpkix-fips-latestVersionNumber.jar(2)

    Provides FIPS support for cert generation.

    bctls-fips-latestVersionNumber.jar(3)

    Provides TLS support using FIPS compliance.

    (1) The tested version is bc-fips-1.0.2.3.jar.

    (2) The tested version is bcpkix-fips-1.0.7.jar.

    (3) The tested version is bctls-fips-1.0.14.jar.

  2. Copy the downloaded files to /path/to/openidm/bundle.

  3. Restart IDM.

Enable the Bouncy Castle FIPS provider in the JVM

To enable the Bouncy Castle FIPS provider in your JVM, do one of the following:

Add Bouncy Castle providers to the existing JVM

If the existing JVM supports Bouncy Castle, then you can add the security providers to the JVM.

Add the Bouncy Castle security providers to $JAVA_HOME/conf/security/java.security:

security.provider.13=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All};
security.provider.14=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
The security.provider.13 and security.provider.14 keys are JVM specific and must be the next 2 values in the security providers list.

Add Bouncy Castle providers to IDM conf/java.security

If the existing JVM supports Bouncy Castle, then you can add the security providers to the /path/to/openidm/conf/java.security. This file provides additions to the $JAVA_HOME/conf/security/java.security file.

Add the Bouncy Castle security providers to /path/to/openidm/conf/java.security:

security.provider.13=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All};
security.provider.14=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
The security.provider.13 and security.provider.14 keys are JVM specific and must be the next 2 values in the security providers list.

Build a distribution of the JVM that supports Bouncy Castle

If the existing JVM doesn’t support Bouncy Castle, you must create a new JVM distribution.

  1. Build a new distribution:

    $JAVA_HOME/bin/jlink \
    --no-header-files \
    --no-man-pages \
    --compress=2 \
    --module-path /Users/bjensen/Downloads \
    --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.management.rmi,java.naming,java.net.http,java.prefs,java.rmi,java.scripting,java.se,java.security.jgss,java.security.sasl,java.smartcardio,java.sql,java.sql.rowset,java.transaction.xa,java.xml,java.xml.crypto,jdk.accessibility,jdk.aot,jdk.attach,jdk.charsets,jdk.compiler,jdk.crypto.cryptoki,jdk.crypto.ec,jdk.dynalink,jdk.editpad,jdk.hotspot.agent,jdk.httpserver,jdk.internal.ed,jdk.internal.jvmstat,jdk.internal.le,jdk.internal.opt,jdk.internal.vm.ci,jdk.internal.vm.compiler,jdk.internal.vm.compiler.management,jdk.jartool,jdk.javadoc,jdk.jcmd,jdk.jconsole,jdk.jdeps,jdk.jdi,jdk.jdwp.agent,jdk.jfr,jdk.jlink,jdk.jshell,jdk.jsobject,jdk.jstatd,jdk.localedata,jdk.management,jdk.management.agent,jdk.management.jfr,jdk.naming.dns,jdk.naming.ldap,jdk.naming.rmi,jdk.net,jdk.pack,jdk.rmic,jdk.scripting.nashorn,jdk.scripting.nashorn.shell,jdk.sctp,jdk.security.auth,jdk.security.jgss,jdk.unsupported,jdk.unsupported.desktop,jdk.xml.dom,jdk.zipfs,org.bouncycastle.fips.core,org.bouncycastle.fips.tls \
    --output /location/to/bouncy/castle/jvm --ignore-signing-information

    A customized JVM is created at the output location /location/to/bouncy/castle/jvm you specified.

  2. Add the Bouncy Castle security providers to /location/to/bouncy/castle/jvm/conf/security/java.security:

    security.provider.13=org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider C:HYBRID;ENABLE{All};
    security.provider.14=org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
    The security.provider.13 and security.provider.14 keys are JVM specific and must be the next 2 values in the security providers list.

Create the IDM Bouncy Castle keystore and cryptographic keys

Before you create the cryptographic keys, you must Enable the Bouncy Castle FIPS provider in the JVM.

To create the necessary IDM cryptographic keys:

  1. Create the Bouncy Castle keystore. This can be done in conjunction with creating the first cryptographic key:

    keytool \
    -genseckey \
    -alias openidm-sym-default \
    -keyalg aes \
    -keysize 256 \
    -keystore /location/to/keystore.bcfks \
    -storepass changeit -storetype BCFKS \
    -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider

    This creates the openidm-sym-default key in a keystore called /location/to/keystore.bcfks while also creating that keystore if it does not exist.

    You must use the JVM specific keytool that the Bouncy Castle security provider uses.

    For example, if you enable the security providers in the system default JVM, you must use the system default keytool command. If you create a custom JVM, you must use the keytool command for where that JVM is located.

    The keytool command is in the bin directory of the JVM Java home.

    Failure to use the keytool command you configure for Bouncy Castle results in the following error:

    keytool error: java.lang.Exception: Provider "org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider" not found
  2. Create the remaining keys:

    Create the openidm-selfservice-key
    keytool \
    -genseckey \
    -alias openidm-selfservice-key \
    -keyalg aes \
    -keysize 256 \
    -keystore /location/to/keystore.bcfks \
    -storepass changeit \
    -storetype BCFKS \
    -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
    Create the openidm-jwtsessionhmac-key
    keytool \
    -genseckey \
    -alias openidm-jwtsessionhmac-key \
    -keyalg aes \
    -keysize 256 \
    -keystore /location/to/keystore.bcfks \
    -storepass changeit \
    -storetype BCFKS \
    -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
    Create the openidm-localhost key
    keytool \
    -genkey \
    -alias openidm-localhost \
    -keyalg RSA \
    -keysize 2048 \
    -keystore /location/to/keystore.bcfks \
    -storepass changeit \
    -storetype BCFKS \
    -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider
    Create the selfservice key
    keytool \
    -genkey \
    -alias selfservice \
    -keyalg RSA \
    -keysize 2048 \
    -keystore /location/to/keystore.bcfks \
    -storepass changeit \
    -storetype BCFKS \
    -provider org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider

Provide the JVM to IDM

If you create a custom JVM location, you must to provide that JVM to IDM in the /path/to/openidm/startup.sh file.

By default, IDM uses the system Java and falls back to using the JAVA_HOME if the system Java is not defined:

Default startup.sh file
if which java &>/dev/null; then
    JAVA=java
elif [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ];  then
    JAVA="$JAVA_HOME/bin/java"
else
    echo JAVA_HOME not available, Java is needed to run IDM
    echo Please install Java and set JAVA_HOME accordingly
    exit 1
fi

To configure IDM to use the JAVA_HOME you set, change the startup.sh file to the following:

Modified startup.sh file
if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/java" ];  then
    JAVA="$JAVA_HOME/bin/java"
else
    echo JAVA_HOME not available, Java is needed to run IDM
    echo Please install Java and set JAVA_HOME accordingly
    exit 1
fi

Configure the Bouncy Castle keystore in secrets.json

After you add the Bouncy Castle security providers and create the keystore and keys, you must replace the default IDM keystore with the new Bouncy Castle keystore in /path/to/openidm/conf/secrets.json:

        {
            "name" : "mainKeyStore",
            "class" : "org.forgerock.openidm.secrets.config.FileBasedStore",
            "config" : {
                "file" : "&{idm.install.dir}/security/keystore.bcfks",
                "storetype" : "BCFKS",
                "providerName" : "BCFIPS",
                "storePassword" : "changeit",
                "mappings" : [
                    {
                        "secretId" : "idm.default",
                        "types" : [
                            "ENCRYPT",
                            "DECRYPT"
                        ],
                        "aliases" : [
                            "&{openidm.config.crypto.alias|openidm-sym-default}"
                        ]
                    },
                    {
                        "secretId" : "idm.config.encryption",
                        "types" : [
                            "ENCRYPT",
                            "DECRYPT"
                        ],
                        "aliases" : [
                            "&{openidm.config.crypto.alias|openidm-sym-default}"
                        ]
                    },
                    {
                        "secretId" : "idm.password.encryption",
                        "types" : [
                            "ENCRYPT",
                            "DECRYPT"
                        ],
                        "aliases" : [
                            "&{openidm.config.crypto.alias|openidm-sym-default}"
                        ]
                    },
                    {
                        "secretId" : "idm.assignment.attribute.encryption",
                        "types" : [
                            "ENCRYPT",
                            "DECRYPT"
                        ],
                        "aliases" : [
                            "&{openidm.config.crypto.alias|openidm-sym-default}"
                        ]
                    }
                ]
            }
        },

IDM is now configured to start using the Bouncy Castle keystore.

Disable Bouncy Castle FIPS-approved mode

By default, IDM turns on Bouncy Castle in FIPS-approved mode. This makes Bouncy Castle FIPS 140-2 compliant.

IDM sets the configuration in /path/to/openidm/startup.sh and /path/to/openidm/bin/docker-entrypoint.sh using the following property:

org.bouncycastle.fips.approved_only=true

To disable FIPS-approved mode, change org.bouncycastle.fips.approved_only to false.

In startup.sh and docker-entrypoint.sh there is also the property org.bouncycastle.jca.enable_jks=true. This property enables the Java keystore (JKS format) for FIPS. In order to maintain compliance, the keystore can only be used for reading a Java keystore that contains certificates. IDM sets this property to true by default.

These settings must take place early in the IDM start process per Bouncy Castle’s documentation.

Passwords

IDM provides password management features that help you enforce password policies, limit the number of passwords users must remember, and allow users to reset and change their passwords.

Password policy

A password policy is a set of rules defining what sequence of characters constitutes an acceptable password. Acceptable passwords generally are too complex for users or automated programs to generate or guess.

Password policies set requirements for password length, character sets that passwords must contain, dictionary words and other values that passwords must not contain. Password policies also require that users not reuse old passwords, and that users change their passwords on a regular basis.

IDM enforces password policy rules as part of the general policy service. The default password policy applies the following rules to passwords as they are created and updated:

  • A password property is required for any user object.

  • The value of a password cannot be empty.

  • The password must include at least one capital letter.

  • The password must include at least one number.

  • The minimum length of a password is 8 characters.

  • The password cannot contain the user name, given name, or family name.

You can change these validation requirements, or include additional requirements, by configuring the policy for passwords.

Passwords are validated in several situations:

Password change and password reset

Password change refers to users changing their own passwords. Password reset refers to an administrator setting a user or account password on behalf of a user.

By default, IDM validates password values as they are provisioned.

Password recovery

Password recovery involves recovering a password or setting a new password when the password has been forgotten.

Password history

You can add validation to prevent reuse of previous password values. For more information, refer to Creating a Password History Policy.

Password expiration

You can use workflows to ensure that users are able to change expiring passwords or to reset expired passwords.

Password history policy

The sample described in Store multiple passwords for managed users shows how to set up a password history policy in a scenario where users have multiple different passwords across resources. You can use the scripts provided in that sample to set up a simple password history policy that prevents managed users from setting the same password that they used previously. The default scripts do not evaluate the current password.

To create a password history policy based on the scripts in the multiple passwords sample, make the following changes to your project:

  1. Copy the pwpolicy.js script from the multiple passwords sample to your project’s script directory:

    cp /path/to/openidm/samples/multiple-passwords/script/pwpolicy.js /path/to/openidm/my-project-dir/script/

    The pwpolicy.js script contains an is-new policy definition that compares a new field value with the list of historical values for that field.

    The is-new policy takes a historyLength parameter that specifies the number of historical values on which the policy should be enforced. This number must not exceed the historySize that you set in conf/managed.json to be passed to the onCreate and onUpdate scripts.

  2. Copy the onCreate-user-custom.js and onUpdate-user-custom.js scripts to your project’s script directory:

    cp samples/multiple-passwords/script/onCreate-user-custom.js /my-project-dir/script/
    cp samples/multiple-passwords/script/onUpdate-user-custom.js /my-project-dir/script/

    These scripts validate the password history policy when a managed user is created or updated.

  3. Update your policy configuration (conf/policy.json ) to reference the new policy definition by adding the policy script to the additionalFiles array:

    {
        "type" : "text/javascript",
        "file" : "policy.js",
        "additionalFiles": [ "script/pwpolicy.js" ],
        ...
    }
  4. Update your project’s conf/managed.json file as follows:

    1. Add a fieldHistory property to the managed user object:

      "fieldHistory" : {
          "title" : "Field History",
          "type" : "object",
          "viewable" : false,
          "searchable" : false,
          "userEditable" : false,
          "scope" : "private"
      }

      The value of this field is a map of field names to a list of historical values for that field. These lists of values are used by the is-new policy to determine if a new value has already been used.

    2. Update the managed user object to call the scripts when a user is created or updated:

      "name" : "user",
      "onCreate" : {
          "type" : "text/javascript",
          "file" : "script/onCreate-user-custom.js",
          "historyFields" : [
              "password"
          ],
          "historySize" : 4
      },
      "onUpdate" : {
          "type" : "text/javascript",
          "file" : "script/onUpdate-user-custom.js",
          "historyFields" : [
              "password"
          ],
          "historySize" : 4
      },
      ...
      If you have any other script logic that is executed on these events, you must update the scripts to include that logic, or add the password history logic to your current scripts.
    3. Add the is-new policy to the list of policies enforced on the password property of a managed user. Specify the number of historical values that the policy should check in historyLength property:

      "password" : {
         ...
         "policies" : [
             {
                 "policyId" : "at-least-X-capitals",
                 "params" : {
                     "numCaps" : 1
                 }
             },
             ...
             {
                 "policyId" : "is-new",
                 "params" : {
                     "historyLength" : 4
                 }
             },
             ...
         ]
      }

You should now be able to test the password history policy by creating a new managed user, and having that user update their password. If the user specifies the same password used within the previous four passwords, the update request is denied with a policy error.

Multiple passwords per linked resource

You can store multiple passwords in a single managed user entry to enable synchronization of different passwords on different external resources.

To store multiple passwords, extend the managed user schema to include additional properties for each target resource. You can set separate policies on each of these new properties, to ensure that the stored passwords adhere to the password policies of the specific external resources.

To use this custom managed object property and its policies to update passwords on an external resource, you must make the corresponding configuration and script changes in your deployment. For a detailed sample that implements multiple passwords, refer to Store multiple passwords for managed users. That sample can also help you set up password history policies.

Random passwords

In certain situations, you might want to generate a random password when users are created.

You can customize your user creation logic to include a randomly generated password that complies with the default password policy. This functionality is included in the default crypto script, bin/defaults/script/crypto.js, but is not invoked by default. For an example of how this functionality might be used, refer to the openidm/bin/defaults/script/onCreateUser.js script. The following section of that file (commented out by default) means that users created through the admin UI, or directly over the REST interface, will have a randomly generated password added to their entry:

if (!object.password) {

    // generate random password that aligns with policy requirements
    object.password = require("crypto").generateRandomString([
        { "rule": "UPPERCASE", "minimum": 1 },
        { "rule": "LOWERCASE", "minimum": 1 },
        { "rule": "INTEGERS", "minimum": 1 },
        { "rule": "SPECIAL", "minimum": 1 }
    ], 16);

}
The changes made to scripts take effect after the time set in the recompile.minimumInterval, described in Script configuration.

The generated password can be encrypted or hashed, in accordance with the managed user schema, defined in conf/managed.json . For more information, refer to Encoding Attribute Values. Synchronizing hashed passwords is not supported.

You can use this random string generation in a number of situations. Any script handler that is implemented in JavaScript can call the generateRandomString function.

password property

To use a property other than the default password property to store passwords, you must change the following files:

policy.json

If you want to enforce password validation rules on a different property, change the password property in this file.

managed.json

Modify the password object in this file, which also includes password complexity policies.

sync.json

If you change the password property, make sure that you limit the change to the appropriate system, designated as source or target.

selfservice-reset.json

If you are setting up self-service password reset, as described in Password reset, change the value of identityPasswordField from password to the desired new property.

Every UI file that includes password as a property name

Whenever there’s a way for a user to enter a password, the associated HTML page will include a password entry. For example, the LoginTemplate.html file includes the password property. A full list of default files with the password property include:

  • _passwordFields.html

  • _resetPassword.html

  • ConfirmPasswordDialogTemplate.html

  • EditPasswordPageView.html

  • LoginTemplate.html

  • MandatoryPasswordChangeDialogTemplate.html

  • resetStage-initial.html

  • UserPasswordTab.html

This list does not include any created custom UI files.

Email rate limiting

No rate limiting is applied to password reset emails, or any emails sent by the IDM server. This means that an attacker can potentially spam a known user account with an infinite number of emails, filling that user’s inbox. In the case of password reset, the spam attack can obscure an actual password reset attempt.

In a production environment, you must configure email rate limiting through the network infrastructure in which IDM runs. Configure the network infrastructure to detect and prevent frequent repeated requests to publicly accessible web pages, such as the password reset page. You can also handle rate limiting within your email server.

Network connections

This topic explains how to secure incoming connections and ports. As a general precaution in production environments, avoid communication over insecure HTTP.

Use TLS/SSL

Use TLS/SSL to access IDM, ideally with mutual authentication so that only trusted systems can invoke each other. TLS/SSL protects data on the network. Mutual authentication with strong certificates, imported into the truststore and keystore of each application, provides a level of confidence for trusting application access.

Restrict REST access to the HTTPS port

In a production environment, you should restrict REST access to a secure port:

  1. Edit your project’s conf/jetty.xml file:

    Comment out or delete the <Call name="addConnector"> code block that includes the openidm.port.http property.

    Do not delete the <Call name="addConnector"> code blocks that contain the openidm.port.https and openidm.port.mutualauth properties.
  2. Edit resolver/boot.properties :

    • Set the openidm.port.https port number.

    • Set the openidm.port.mutualauth port number.

    • Add the property openidm.https.enabled=true.

Use a certificate to secure REST access over HTTPS. You can use self-signed certificates in a test environment. In production, all certificates should be signed by a certificate authority. The examples in this guide assume a CA-signed certificate named ca-cert.pem.

Protect sensitive REST interface URLs

Anything attached to the router is accessible with the default policy, including the repository. If you do not need such access, deny it in the authorization policy to reduce the attack surface.

In addition, you can deny direct HTTP access to system objects in production, particularly access to action. As a rule of thumb, do not expose anything that is not used in production.

For an example that shows how to protect sensitive URLs, refer to Configure Access Control in access.json.

Enable HTTP Strict-Transport-Security

HTTP Strict-Transport-Security (HSTS) is a web security policy that forces browsers to make secure HTTPS connections to specified web applications. HSTS can protect websites against passive eavesdropper and active man-in-the-middle attacks.

IDM provides an HSTS configuration, but it is disabled by default. To enable HSTS, locate the following excerpt in your conf/jetty.xml file:

<New id="tlsHttpConfig" class="org.eclipse.jetty.server.HttpConfiguration">
    ...
    <Call name="addCustomizer">
        <Arg>
            <New class="org.eclipse.jetty.server.SecureRequestCustomizer">
                <!-- Enable SNI Host Check when true -->
                <Arg name="sniHostCheck" type="boolean">true</Arg>
                <!-- Enable Strict-Transport-Security header and define max-age when >= 0 seconds -->
                <Arg name="stsMaxAgeSeconds" type="long">-1</Arg>
                <!-- If enabled, add includeSubDomains to Strict-Transport-Security header when true -->
                <Arg name="stsIncludeSubdomains" type="boolean">false</Arg>
            </New>
        </Arg>
    </Call>
...

Set the following arguments:

stsMaxAgeSeconds

This parameter sets the length of time, in seconds, that the browser should remember that a site can only be accessed using HTTPS.

For example, the following setting applies the HSTS policy and remains in effect for an hour:

<Arg name="stsMaxAgeSeconds" type="long">3600</Arg>
stsIncludeSubdomains

If this parameter is true, the HSTS policy is applied to the domain of the issuing host as well as its subdomains:

<Arg name="stsIncludeSubdomains" type="boolean">true</Arg>

For more information about HSTS, click here.

Restrict the HTTP payload size

Restricting the size of HTTP payloads can protect the server against large payload HTTP DDoS attacks. IDM includes a servlet filter that limits the size of an incoming HTTP request payload, and returns a 413 Request Entity Too Large response when the maximum payload size is exceeded.

By default, the maximum payload size is 5MB. You can configure the maximum size in your project’s conf/servletfilter-payload.json file. That file has the following structure by default:

{
    "classPathURLs" : [ ],
    "systemProperties" : { },
    "requestAttributes" : { },
    "scriptExtensions" : { },
    "initParams" : {
        "maxRequestSizeInMegabytes" : 5
    },
    "urlPatterns" : [
        "/*"
    ],
    "filterClass" : "org.forgerock.openidm.jetty.LargePayloadServletFilter"
}

Change the value of the maxRequestSizeInMegabytes property to set a different maximum HTTP payload size.

The remaining properties in this file are described in Register Additional Servlet Filters.

Deploy securely behind a load balancer

IDM prevents URL-hijacking, with the following code block in the conf/jetty.xml file:

<Call name="addCustomizer">
  <Arg>
    <New class="org.eclipse.jetty.server.SecureRequestCustomizer">
      <!-- Enable SNI Host Check when true -->
      <Arg name="sniHostCheck" type="boolean">true</Arg>
      <!-- Enable Strict-Transport-Security header and define max-age when >= 0 seconds -->
      <Arg name="stsMaxAgeSeconds" type="long">-1</Arg>
      <!-- If enabled, add includeSubDomains to Strict-Transport-Security header when true -->
      <Arg name="stsIncludeSubdomains" type="boolean">false</Arg>
    </New>
  </Arg>
</Call>

If you are deploying IDM behind a system such as a load balancer, firewall, or a reverse proxy, you must uncomment the next section in jetty.xml , so that Jetty honors X-Forwarded-Host headers:

 <Call name="addCustomizer">
    <Arg>
        <New class="org.eclipse.jetty.server.ForwardedRequestCustomizer">
            <Set name="forcedHost">
                <Call class="org.forgerock.openidm.jetty.Param" name="getProperty">
                    <Arg>openidm.host</Arg>
                </Call>:<Call class="org.forgerock.openidm.jetty.Param" name="getProperty">
                    <Arg>openidm.port.https</Arg>
                </Call>
            </Set>
        </New>
    </Arg>
</Call>

Connect to IDM through a proxy server

  1. Add the following JVM parameters to the value of OPENIDM_OPTS in your startup script (startup.sh or startup.bat ):

    -Dhttps.proxyHost

    Hostname or IP address of the proxy server; for example, proxy.example.com or 192.168.0.1.

    -Dhttps.proxyPort

    Port number used by IDM; for example, 8443 or 9443.

    Example
    # Only set OPENIDM_OPTS if not already set
    [ -z "$OPENIDM_OPTS" ] && OPENIDM_OPTS="-Xmx1024m -Xms1024m -Dhttps.proxyHost=localhost -Dhttps.proxyPort=8443"
  2. Enable the ForwardedRequestCustomizer class so that Jetty honors X-Forwarded- headers.

    To enable the class, uncomment the following excerpt in your conf/jetty.xml file:

    <Call name="addCustomizer">
        <Arg>
            <New class="org.eclipse.jetty.server.ForwardedRequestCustomizer">
                <Set name="forcedHost">
                    <Call class="org.forgerock.openidm.jetty.Param" name="getProperty">
                        <Arg>openidm.host</Arg>
                    </Call>:
                    <Call class="org.forgerock.openidm.jetty.Param" name="getProperty">
                        <Arg>openidm.port.https</Arg>
                    </Call>
                </Set>
            </New>
        </Arg>
    </Call>

    For more information on this class, refer to the Jetty documentation.

Protect against Cross-Site Request Forgery

IDM provides a filter to protect against Cross-Site Request Forgery (CSRF). The filter is disabled by default. To enable it, set the following property in resolver/boot.properties:

openidm.csrfFilter.enabled=true

The filter requires the client to send a CSRF cookie (X-CSRF-Token) on every request. By default, this cookie is the JWT session cookie (session-jwt), obtained on authentication. If your client uses a different cookie for authentication, set the name of that cookie in the following property in resolver/boot.properties:

openidm.csrfFilter.authCookieName=session-jwt

If there are HTTP request paths that the CSRF filter should always allow, set these paths as comma-separated values of the openidm.csrfFilter.pathWhitelistCSV property in resolver/boot.properties. For example:

openidm.csrfFilter.pathWhitelistCSV=/openidm/example,/openidm/my-project

IDM data

Beyond relying on end-to-end availability of TLS/SSL to protect data, IDM also supports explicit encryption of data that goes on the network. This can be important if the TLS/SSL termination happens prior to the final endpoint.

IDM also supports encryption of data stored in the repository, using the symmetric keys specified in conf/secrets.json. This protects against some attacks on the data store. Explicit table mapping is supported for encrypted string values.

IDM automatically encrypts sensitive data (such as passwords) in configuration files, and replaces cleartext values when the system first reads the configuration file. Take care with configuration files that contain clear text values that IDM has not yet read and encrypted.

Encode attribute values

There are two ways to encode attribute values for managed objects—reversible encryption and salted hashing algorithms. Attribute values that might be encoded include passwords, authentication questions, credit card numbers, and social security numbers. If passwords are already encoded on the external resource, they are generally excluded from the synchronization process. For more information, refer to Passwords.

You configure attribute value encoding, per schema property, in the managed object configuration. The following sections show how to use reversible encryption and salted hash algorithms to encode attribute values.

Reversible encryption

The following managed object configuration encrypts and decrypts the password attribute using the default symmetric key:

{
    "objects" : [
        {
            "name" : "user",
            ...
            "schema" : {
                ...
                "properties" : {
                    ...
                    "password" : {
                        "title" : "Password",
                        ...
                        "encryption" : {
                            "purpose" : "idm.password.encryption"
                        },
                        "scope" : "private",
                    }
            ...
        }
    ]
}

The settings for reversible encryption depend on the encryption capabilities of the underlying JVM. refer to the explanations in javax.crypto.Cipher. You can accept the default settings, or specify the cipher and the keySize, for example:

...
    "encryption" : {
        "purpose": "idm.password.encryption",
        "cipher": "AES/GCM/NoPadding",
        "keySize": 128
},

The syntax for the cipher is algorithm/mode/padding, for example, "cipher" : "AES/CBC/PKCS5Padding":

  • The cipher algorithm defines how the plaintext is encrypted and decrypted.

    The default algorithm is the Advanced Encryption Standard (AES).

  • The cipher mode defines how a block cipher algorithm transforms data larger than a single block.

    The default cipher mode is cipher block chaining (CBC).

  • The cipher padding defines how to pad the plaintext to reach the appropriate size for the algorithm.

    The default cipher padding is PKCS#5 padding.

  • The cipher key size determines the encryption strength, where longer key lengths strengthen encryption at the cost of lower performance.

    The default keySize is 16.

If you change the default cipher, you must specify the algorithm, mode, and padding. If the algorithm does not require a mode, use NONE. If the algorithm does not require padding, use NoPadding.

To encrypt attribute values from the command-line, refer to encrypt.

Configure encryption using the admin UI

  1. Select Configure > Managed Objects, and select the object type whose property values you want to encrypt (for example, User).

  2. On the Properties tab, select the property whose value should be encrypted and select the Encrypt checkbox.

Salted hash algorithms

To encode attribute values with salted hash algorithms, add the secureHash property to the attribute definition and define the hashing configuration. The configuration depends on the algorithm that you choose.

If you do not specify an algorithm, SHA-256 is used by default. MD5 and SHA-1 are supported for legacy reasons, but should not be used in production environments.

Supported Hashing Algorithms and Configuration Properties
Algorithm Config Property and Description

BCRYPT

cost

Value between 4 and 31. Default is 13.

PBKDF2

hashLength

Byte-length of the generated hash. Default is 16.

saltLength

Byte-length of the salt value. Default is 16.

iterations

Number of cryptographic iterations. Default is 20000.

hmac

HMAC algorithm. Default is SHA3-256.

Supported values:

  • SHA-224

  • SHA-256

  • SHA-384

  • SHA-512

  • SHA3-224

  • SHA3-256

  • SHA3-384

  • SHA3-512

SCRYPT

hashLength

Byte-length of the generated hash, must be greater than or equal to 8. Default is 16.

saltLength

Byte-length of the salt value. Default is 16.

n

CPU/Memory cost. Must be greater than 1, a power of 2, and less than 2^(128 * r / 8). Default is 32768.

p

Parallelization. Must be a positive integer less than or equal to Integer.MAX_VALUE / (128 * r * 8). Default is 1.

r

Block size. Must be greater than or equal to 1. Default is 8.

SHA-256

saltLength

Byte-length of the salt value. Default is 16.

This is the default hashing.

SHA-384

saltLength

Byte-length of the salt value. Default is 16.

SHA-512

saltLength

Byte-length of the salt value. Default is 16.

The following list displays supported hash algorithms and example configurations:

SHA-256
"secureHash" : {
    "algorithm" : "SHA-256",
    "saltLength" : 16
}
SHA-384
"secureHash" : {
    "algorithm" : "SHA-384",
    "saltLength" : 16
}
SHA-512
"secureHash" : {
    "algorithm" : "SHA-512",
    "saltLength" : 16
}
Bcrypt
"secureHash" : {
    "algorithm" : "BCRYPT",
    "cost" : 16
}
Scrypt
"secureHash" : {
    "algorithm" : "SCRYPT",
    "hashLength" : 16,
    "saltLength" : 16,
    "n" : 32768,
    "r" : 8,
    "p" : 1
}
Password-Based Key Derivation Function 2 (PBKDF2)
"secureHash" : {
    "algorithm" : "PBKDF2",
    "hashLength" : 16,
    "saltLength" : 16,
    "iterations" : 10,
    "hmac" : "SHA-256"
}

Some one-way hash functions are designed to be computationally expensive. Functions such as PBKDF2, Bcrypt, and Scrypt are designed to be relatively slow even on modern hardware. This makes them generally less susceptible to brute force attacks. However, computationally expensive functions can dramatically increase response times. If you use these functions, be aware of the performance impact and perform extensive testing before deploying your service in production. Do not use functions like PBKDF2 and Bcrypt for any accounts that are used for frequent, short-lived connections.

Hashing is a one-way operation, such that the original value cannot be recovered. Therefore, if you hash the value of any property, you cannot synchronize that property value to an external resource. For managed object properties with hashed values, you must either exclude those properties from the mapping or set a random default value if the external resource requires the property.

The following excerpt of a managed object configuration shows that values of the password attribute are hashed using the SHA-256 algorithm:

{
    "objects" : [
        {
            "name" : "user",
            ...
            "schema" : {
                ...
                "properties" : {
                    ...
                    "password" : {
                        "title" : "Password",
                        ...
                        "secureHash" : {
                            "algorithm" : "SHA-256"
                        },
                        "scope" : "private",
                    }
            ...
        }
    ]
}

To hash attribute values from the command-line, refer to secureHash.

Configure hashing using the admin UI

You can set a property hash algorithm using the admin UI. However, only some algorithms and none of the enhanced configuration options are supported.

Show Me
Admin UI hash property
  1. Select Configure > Managed Objects, and select an object type (for example, User).

  2. On the Properties tab, select a property to hash.

  3. On the Property Name page, click the Privacy & Encryption tab, and select Hashed.

  4. From the adjacent drop-down menu, select an algorithm.

  5. Click Save.

Encrypted objects

Encrypted objects and properties, such as passwords, include a $crypto object, that has the following structure:

"password": {
  "$crypto": {
    "type": "x-simple-encryption",
    "value": {
      "cipher": "AES/CBC/PKCS5Padding",
      "stableId": "openidm-sym-default",
      "salt": "Gwi+AGrn+VBOTmyq+TTuuw==",
      "data": "+9i7XAXpWZBXYTVEOBkM+w==",
      "keySize": 16,
      "purpose": "idm.password.encryption",
      "iv": "4xtI88eFu5tgfm8ooq+yqQ==",
      "mac": "N1zsYo71M/b/G6iLOhNohA=="
    }
  }
}

Most of the properties in the encrypted object value are self-explanatory and indicate how the property was encrypted. Specific IDM properties include the following:

  • The stableId indicates the key alias that was used to encrypt the property value.

  • The purpose refers to the secret ID used to encrypt the property value. For more information about secret IDs, refer to Secret stores.

Encrypt and decrypt properties over REST

The openidm.encrypt and openidm.decrypt functions of the Resource API enable you to encrypt and decrypt property values. To use these functions over the REST interface, run the ?_action=eval action on the script endpoint.

The following example uses the openidm.encrypt function to encrypt a password value:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Content-Type: application/json" \
--cacert ca-cert.pem \
--request POST \
--data '{
  "type": "text/javascript",
  "globals": {
    "val": {
      "myKey": "myPassword"
    }
  },
  "source":"openidm.encrypt(val,null,\"idm.password.encryption\");"
}' \
"https://localhost:8443/openidm/script?_action=eval"
{
  "$crypto": {
    "type": "x-simple-encryption",
    "value": {
      "cipher": "AES/CBC/PKCS5Padding",
      "stableId": "openidm-sym-default",
      "salt": "qAS/eG7zdnFyK5H8lXvqTA==",
      "data": "zewf6hR1yjp34EFJqUGpdnzzFCPJs2IaX4V97jdQlSI=",
      "keySize": 16,
      "purpose": "idm.password.encryption",
      "iv": "A4pIiY6kG6t0uLyLmJAoWQ==",
      "mac": "sFDJqg0Mmp0Ftl+1q1Bjzw=="
    }
  }
}

The following example uses the openidm.decrypt function to decrypt the password value:

curl \
--header "X-OpenIDM-Username: openidm-admin" \
--header "X-OpenIDM-Password: openidm-admin" \
--header "Content-Type: application/json" \
--cacert ca-cert.pem \
--request POST \
--data '{
  "type": "text/javascript",
  "globals": {
    "val": {
      "$crypto": {
        "type": "x-simple-encryption",
        "value": {
          "cipher": "AES/CBC/PKCS5Padding",
          "stableId": "openidm-sym-default",
          "salt": "qAS/eG7zdnFyK5H8lXvqTA==",
          "data": "zewf6hR1yjp34EFJqUGpdnzzFCPJs2IaX4V97jdQlSI=",
          "keySize": 16,
          "purpose": "idm.password.encryption",
          "iv": "A4pIiY6kG6t0uLyLmJAoWQ==",
          "mac": "sFDJqg0Mmp0Ftl+1q1Bjzw=="
        }
      }
    }
  },
  "source":"openidm.decrypt(val);"
}' \
"https://localhost:8443/openidm/script?_action=eval"
{
  "myKey": "myPassword"
}

For more information, refer to openidm.encrypt and openidm.decrypt.

Secure the repository

Configuration data and, in most deployments, user data, are stored in the IDM repository. In production deployments, you must secure access to the repository, and encrypt sensitive stored data.

For JDBC repositories, use a strong password for the connection to the repository and change at least the password of the database user (openidm by default). When you change the database username and/or password, update your database connection configuration file (conf/datasource.jdbc-default.json).

For a DS repository, change the bindDN and bindPassword for the directory server user in the ldapConnectionFactories property in the repo.ds.json file.

In both cases, the password is encrypted on server startup, using the key specified in the idm.password.encryption secret ID in conf/secrets.json.

Sensitive files and directories

Protect IDM files from access by unauthorized users. In particular, prevent other users from reading files in at least the openidm/resolver/ and openidm/security/ directories.

The objective is to limit access to the user that is running the service. Depending on the operating system and configuration, that user might be root, Administrator, openidm, or something similar.

Protect sensitive files in Unix

  1. Make sure that user and group ownership of the installation and project directories is limited to the user running the IDM service.

  2. Disable access of any sort for other users. One simple command for that purpose, from the /path/to/openidm directory, is:

    chmod -R o-rwx .

Protect sensitive files in Windows

The IDM process in Windows is typically run by the Local System service account.

If you are concerned about the security of this account, you can set up a service account that only has permissions for IDM-related directories, then remove User access to the directories noted above. You should also configure the service account to deny local and remote login. For more information, refer to the User Rights Assignment article in Microsoft’s documentation.

Remove or protect development and debug tools

Before you deploy IDM in production, remove or protect development and debug tools, including the Felix web console that is exposed under /system/console. Authentication for this console is not integrated with authentication for IDM.

Remove the Felix web console

  1. Remove the web console bundle and all related plugin bundles:

    rm /path/to/openidm/bundle/org.apache.felix.webconsole*.jar
    rm /path/to/openidm/bundle/openidm-felix-webconsole-7.3.1.jar
  2. Remove the felix.webconsole.json configuration file from your project’s conf/ directory:

    rm /path/to/project-dir/conf/felix.webconsole.json

Protect the Felix web console

To protect access to the Felix web console, change the credentials in your project’s conf/felix.webconsole.json file. These properties can be set using property substitution. This file contains the username and password to access the console, by default:

{
  "username" : "&{openidm.felix.webconsole.username|admin}",
  "password" : "&{openidm.felix.webconsole.password|admin}"
}

Adjust log levels

In production, set log levels to INFO to ensure that you capture enough information to help diagnose issues, but do not expose unnecessary information. For more information, refer to Server logs.

At startup and shutdown, INFO can produce many messages. During stable operation, INFO generally results in log messages only when coarse-grain operations such as scheduled reconciliation start or stop.

The default IDM log formatter encodes all control characters (such as newline characters) using URL-encoding, to protect against log forgery. For more information, refer to Server logs.

Secure the API Explorer

The REST API Explorer serves up interactive REST API documentation. The API Explorer can help you identify endpoints, and run REST calls against those endpoints. To protect production servers from unauthorized API descriptor requests, IDM requires authentication, by default. The property authEnabled protects static web resources from public view.

Default ui.context-api.json file
{
    "enabled" : true,
    "authEnabled" : true,
    "urlContextRoot" : "/api",
    "defaultDir" : "&{idm.install.dir}/ui/api/default",
    "extensionDir" : "&{idm.install.dir}/ui/api/extension"
}

To disable the API Explorer, set the following property in your resolver/boot.properties file:

openidm.apidescriptor.enabled=false

Hide unused REST endpoints

The two main use cases for IDM are data synchronization and user self-service.

If you are using IDM only to synchronize data sources, do not expose the server externally. In this case, IDM initiates all connections.

If you are using IDM only for user self-service, ensure that the server is behind a firewall or proxy, such as ForgeRock Identity Gateway. At a minimum, hide the /admin endpoint in the web interface via the proxy. Use the conf/access.json file as a guide for proxy or firewall rules.

If you are using IDM for data synchronization and user self-service, it is preferable to run two IDM servers or clusters, each with its own security model. Because the two use cases have very different load characteristics and security implications, running them on separate servers can help to prevent synchronization activity from impacting the performance on end-user systems.

Disable automatic configuration updates

By default, IDM monitors files in the conf directory periodically for any changes to the configuration. This functionality is configured in the following properties in your conf/system.properties file:

openidm.fileinstall.poll

Sets the interval at which files are polled for changes. 2000 milliseconds by default.

openidm.fileinstall.enabled

Specifies whether files should be monitored. true by default. In a production system, you should disable automatic polling for updates to prevent untested configuration changes from disrupting your identity service. Setting this property to false also disables the file-based configuration view, which means that IDM reads its configuration only from the repository.

If automatic polling is enabled, IDM immediately uses changes to scripts called from a JSON configuration file.

openidm.config.bootstrap.enabled

Allows IDM to start up and load configuration when there aren’t currently any configs stored in the repository. true by default.

openidm.fileinstall.filter

Specify which file types should be monitored (if openidm.fileinstall.enabled=true). File types are specified in a pipe-separated list, for example:

openidm.fileinstall.filter=.*\\.cfg|.*\\.json

Disable configuration file writes

To disable writes to configuration files, set the following property to false in your conf/config.properties file:

felix.fileinstall.enableConfigSave=false

This setting is suitable for read-only installations.

Secure IDM server files with a read-only installation

One method of locking down the server is to install IDM on a read-only file system. To accomplish this, complete all procedures in this topic.

This topic assumes that you have prepared the read-only volume appropriate for your Linux/UNIX installation environment and that you have set up a regular Linux user named idm and a dedicated volume for the /idm directory.

Prep

  1. Configure the dedicated volume device, /dev/volume in the /etc/fstab file, as follows:

    /dev/volume/idm   ext4   ro,defaults   1,2

    When you run the mount -a command, the /dev/volume volume device is mounted on the /idm directory.

  2. You can switch between read-write and read-only mode for the /idm volume with the following commands:

    sudo mount -o remount,rw /idm
    sudo mount -o remount,ro /idm
  3. Confirm the result with the mount command, which should show that the /idm volume is mounted in read-only mode:

    /dev/volumeon /idm type ext4 (ro)
  4. Set up the /idm volume in read-write mode:

    sudo mount -o remount,rw /idm
  5. With the following commands, you can unpack the IDM binary in the /idm directory, and give user idm ownership of all files in that directory:

    sudo unzip /idm/IDM-7.3.1.zip
    sudo chown -R idm.idm /idm

Redirect audit and logging data

After you have installed IDM on a read-only file system, redirect audit and logging data to writable volumes. This procedure assumes a user idm with Linux administrative (superuser) privileges.

  1. Create an external directory where IDM can send logging, auditing, and internal repository information:

    sudo mkdir -p /var/log/openidm/audit
    sudo mkdir /var/log/openidm/logs
    sudo mkdir -p /var/cache/openidm/felix-cache
    sudo mkdir /var/run/openidm

    Alternatively, route audit data to a remote data store. For an example of how to send audit data to a MySQL repository, refer to Direct audit information to MySQL.

  2. Give the idm user ownership of the newly created directories:

    sudo chown -R idm.idm /var/log/openidm
    sudo chown -R idm.idm /var/cache/openidm
    sudo chown -R idm.idm /var/run/openidm
  3. Modify the following configuration files:

    conf/audit.json

    Make sure the handlerForQueries is the JSON audit event handler and change the logDirectory property to the /var/log/openidm/audit subdirectory:

    "eventHandlers" : [
        {
            "class" : "org.forgerock.audit.handlers.json.JsonAuditEventHandler",
            "config" : {
                "name" : "json",
                "logDirectory" : "/var/log/openidm/audit",
                ...
            },
            ...
        }
    ]
    conf/logging.properties

    Change the java.util.logging.FileHandler.pattern property as follows:

    java.util.logging.FileHandler.pattern = /var/log/openidm/logs/openidm%u.log
    conf/config.properties

    Activate and redirect the org.osgi.framework.storage property as follows:

    # If this value is not absolute, then the felix.cache.rootdir controls
    # how the absolute location is calculated. (See buildNext property)
    org.osgi.framework.storage=&{felix.cache.rootdir|&{user.dir}}/felix-cache
    
    # The following property is used to convert a relative bundle cache
    # location into an absolute one by specifying the root to prepend to
    # the relative cache path. The default for this property is the
    # current working directory.
    felix.cache.rootdir=/var/cache/openidm

Your setup may require additional redirection for the following:

  • Connectors. Depending on the connector, and the read-only volume, consider configuring connectors to direct output to writable volumes.

  • Scripts. If you are using Groovy, examine the script configuration for your project. Make sure that output such as to the groovy.target.directory is directed to an appropriate location, such as idm.data.dir.

Finishing touches

  1. Adjust the value of the OPENIDM_PID_FILE in the startup.sh and shutdown.sh scripts. To do so for a default bash shell, set the value of OPENIDM_PID_FILE for user idm by adding the following line to /home/idm/.bashrc:

    export OPENIDM_PID_FILE=/var/run/openidm/openidm.pid
    For other shells, adjust your changes accordingly.

    When you log in again as user idm, your OPENIDM_PID_FILE variable should redirect the process identifier file, openidm.pid to the /var/run/openidm directory, ready for access by the shutdown.sh script.

  2. While the volume is still mounted in read-write mode, start IDM normally:

    path/to/openidm/startup.sh -p project-dir

    The first startup of IDM either processes the signed certificate that you added, or generates a self-signed certificate, and encrypts any passwords in the various configuration files.

  3. Stop IDM.

  4. You can now mount the /idm directory in read-only mode. The configuration in /etc/fstab ensures that Linux mounts the /idm directory in read-only mode on next system boot.

    sudo mount -o remount,ro /idm
  5. Reboot the system.

  6. You can now start IDM, configured on a secure read-only volume.

    path/to/openidm/startup.sh -p project-dir