Development/Data Pools

From OpenXPKI Wiki

Jump to: navigation, search

Contents

The Global Key-Value Data Pool

Scope

OpenXPKI uses a workflow engine to handle stateful operations. The workflow engine implements a workflow context that allows to persistently store key/value tuples of information that is relevant to execution of the workflow. Data stored in the workflow context should be considered private to the corresponding workflow instance.

The workflow context semantics only work properly if the processed data is local to the particular workflow instance. However, there are cases where workflows need to share data between multiple instances or even across workflow type boundaries.

One possible remedy is to access a foreign workflow context from a workflow instance, but this should be considered a hack instead of a sensible solution for the problem.

In contrast, the Data Pool feature allows for persistent storage of structured data that is used by the system globally. It should be used wherever data needs to be shared between multiple workflow instances or workflow types.

Data Pool properties

The Data Pool provides a canonical method for storing key/value tuples that are not specific to a particular workflow instance. This mechanism is available both from the server core and from the Workflow engine.

The Data Pool can store plain text and encrypted data. Encrypted data is handled transparently for the caller (see below).

Access to the data pool is allowed only via the defined Server API calls.

Data access

Data is organized by [PKI Realm, namespace, key] which forms the primary key for accessing the corresponding value. This means that the same key can exist in multiple namespaces and that the same namespace name can exist in multiple PKI Realms.

PKI Realm

When called directly from a workflow activity, the API requires that the requested PKI Realm is identical to the PKI Realm of the current session. Otherwise, the caller may specify an arbitrary PKI Realm.

Data stored with a PKI Realm which is set to '/' is considered instance-wide (not limited to a PKI Realm). This is called the global Data Pool which is used for storing instance-wide data.

NOTE: Encryption is currently not available for the global Data Pool (because no Password Safe can be defined for it).

Namespaces

Namespaces may consist of alphanumerical characters, including blanks and some punctuation characters (.:_-)

Namespaces in any PKI Realm beginning with 'sys.' are reserved for internal use. These values are treated as read-only by the API when directly called by a Workflow Activity (trying to set an entry in such a namespace by a Workflow activity yields an exception).

In the global data pool '/' the following namespaces are reserved:

  • The namespace 'sys.config' is used for configuration data that may be modified by an administrator and that can be include in the XML configuration.
  • The namespace 'sys.stats.*' is used for performance and server health statistics.
    • An OpenXPKI host writes performance data to the namespace 'sys.stats.serverid.SERVERID.*' SERVERID is taken from the <server_id/> attribute in the database.xml configuration. This allows collecting node-specific data in cluster settings with a shared database.

Keys

Keys may consist of alphanumerical characters, including blanks and some punctuation characters (.:_-)

Values

Stored values must be in text format (binary data is not supported). Only valid UTF8 characters are allowed. The maximum size of data that can be stored depends on the underlying database. It is safe to assume that a minimum of 32 KByte text data can be stored.

Currently only Scalar values may be stored in the data pool.

Meta data

Modification date

The API call to write a value to the data pool also sets a modification time stamp in the created record. The modification time stamp is returned by the corresponding read API calls.

The modification date is specified in seconds since Epoch.

Expiration date

Each single data entry may contain an expiration date which defaults to Null. An undefined expiration date means that the stored data does not expire. An entry whose expiration date has passed may be deleted by the server at any time.

The expiration date is specified in seconds since Epoch.


Data pool encryption

The data pool supports encryption of individual entries in order to protect sensitive data. The encryption shall prevent administrators and users of the system from directly accessing stored data. Within the OpenXPKI server data may be transparently decrypted at any time.

When creating a key/value tuple via the corresponding API call the creator may request encryption for the stored data. If encryption is requested and the necessary keys are available the server encrypts the value and stores the encrypted value in the database.

When reading a value from the data pool the OpenXPKI server checks if the value was encrypted. If this was the case, it tries to decrypt the encrypted value. If this is successful, the decrypted value is transparently returned to the caller. The value returned from the read API all indicates that the value was encrypted.

If the server cannot access the necessary keys to decrypt the value it throws an exception.

Important: Encryption of data pool values only works properly if a usable password safe is available for the requested PKI Realm.

Encrypted data storage

Key Management Logic

At any given time, the datapool encryption feature uses one single symmetric key for encryption of new keys.

The same symmetric key will be used for encrypting all data pool entries during the usage time of the password safe, i. e. there is a 1:1 connection between password safe and symmetric key used for encryption of data pool entries.

After the first use the OpenXPKI server will cache the encryption key corresponding to each password safe in the SECRET table (protected by the server-wide VolatileVault). This dramatically speeds up access because no asymmetric operations need to be done for encryption and decryption of values once the cache has been prepared.

Scenario 1: A new password safe is used the first time, no keys previously exist (except of the asymmetric password safe key itself).

  • generate a new symmetric key (VolatileVault, exportable)
  • store the mapping password safe id -> symmetric key id in the data pool
  • encrypt the symmetric key with the password safe (asymmetrically, PKCS#7)
  • store the encrypted symmetric key in the data pool, access key is the symmetric key id
  • cache the symmetric key in the server-wide volatile vault
  • use newly created symmetric key for encryption of the value

Scenario 2: The password safe has been used before to encrypt a data pool entry, reuse it

  • get (decrypt) symmetric key from data pool (Note: this recursively resolves referenced keys until the key is decrypted. This possibly reuses cached keys in the server-wide volatile vault.)
  • use existing symmetric key for encryption of the value


The Data Pool encryption feature uses the Data Pool itself to store metadata necessary for value encryption. The following namespaces are used in the target PKI Realm:

Namespace: sys.datapool.pwsafe - mapping password safes to symmetric keys

This namespace contains simple (plain text) mappings that associate symmetric keys with password safe names. When encrypting a new value, the server checks if there is already a key associated to the password safe. If a match is found, it uses the referenced key, otherwise it creates a new key and stores the mapping.

Namespace: sys.datapool.pwsafe (within the target PKI realm)

Key: p7:SAFENAME (where SAFENAME is the name of the corresponding passwordsafe from config.xml)

Value: Symmetric Key Id (describes which symmetric key is used to encrypt the data)

Namespace: sys.datapool.keys - symmetric key storage

This namespace maps a symmetric key id to its corresponding encryption key value. The value is always encrypted, normally using the asymmetric password safe.

Commented storage example

Table: datapool

pki_realm namespace datapool_key datapool_value encryption_key notafter last_update
MYREALM sys.datapool.pwsafe p7:passwordsafe1 75SKlOB23rVCs0pqGV4iRv2cx2g NULL 1270368620
MYREALM sys.datapool.keys 75SKlOB23rVCs0pqGV4iRv2cx2g -----BEGIN PKCS7-----MIIBu... p7:passwordsafe1 NULL 1270368620
MYREALM smartcard.puk gem2_123456 75SKlOB2;base64-oneline;92wb/FsR+d1/YMs1 75SKlOB23rVCs0pqGV4iRv2cx2g 1270368680 1270368620

Decryption

In this example the value for key "gem2_123456" in namespace "smartcard.puk" is encrypted.

  • The data pool is asked to get the corresponding value it checks the encryption_key column.
  • If it is null, the value is unencrypted and directly returned.
  • In this case, it finds the value "75SKlOB23rVCs0pqGV4iRv2cx2g", which is the symmetric key identifier used for encryption of the value.
  • Now it tries to obtain the symmetric key by querying namespace "sys.datapool.keys" for "75SKlOB23rVCs0pqGV4iRv2cx2g".
  • For this key it finds that a value is encrypted with "p7:passwordsafe1".
  • Now it tries to decrypt the stored value "-----BEGIN PKCS7..." with the asymmetric passwordsafe1 key.
  • The resulting value is the used to decrypt the original encrypted value.

Encryption

A new key/value pair "gem2_98765" -> "1234" shall be encrypted.

  • The data pool determines the currently active password safe, which is literally "passwordsafe1"
  • It checks the "sys.datapool.pwsafe" table to obtain the corresponding symmetric key id.
  • It finds "75SKlOB23rVCs0pqGV4iRv2cx2g".
  • Now it tries to obtain the symmetric key by querying namespace "sys.datapool.keys" for "75SKlOB23rVCs0pqGV4iRv2cx2g".
  • For this key it finds that a value is encrypted with "p7:passwordsafe1".
  • Now it tries to decrypt the stored value "-----BEGIN PKCS7..." with the asymmetric passwordsafe1 key.
  • The resulting value is the used to encrypt the value to store.

Optimizations

Several optimizations help to avoid unnecessary asymmetric operations which considerably speeds up subsequent encryption/decryption operations. Mainly the used symmetric keys are cached in the instance-wide volatile vault, completely eliminating the need to decrypt the symmetric key every time it is used.


API reference

set_data_pool_entry

 CTX('api')->set_data_pool_entry({
   PKI_REALM => scalar: PKI Realm (may be '/' for global realm)
   NAMESPACE => scalar: Namespace to use
   EXPIRATION_DATE => scalar, optional: expiration date after which the record may be discarded. Format: YYYYMMDD
   ENCRYPT => scalar, optional: set to 1 to request encryption
   KEY => scalar: Key for accessing the stored value
   VALUE => scalar: Value to store
   FORCE => scalar, optional: set to 1 to allow overwriting existing values
 })

Returns: true on success, throws exception on error.

  • If directly called from a Workflow Activity the API requires PKI_REALM set identical to session PKI Realm.
  • If VALUE is undefined, the corresponding tuple will be deleted from the table.
  • Side effects: calling this function will expunge all expired values from the datapool.
  • No implicit commit() is performed on the backend database handle.
  • Encryption may work even though the private key for the password safe is not available (the symmetric encryption key is encrypted for the password

safe certificate). Retrieving encrypted information will only work if the password safe key is available during the first access to the symmetric key.

Exceptions:

I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_INVALID_PKI_REALM 
Invalid PKI Realm was specified.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_INVALID_NAMESPACE 
Invalid (e. g. reserved) namespace was specified.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_READ_ONLY_DATA 
Attempt to modify read-only data.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_ENTRY_EXISTS 
Specified key already exists.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_ENCRYPTION_NOT_CONFIGURED 
Encryption is not set up properly.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_ENCRYPTION_KEY_UNAVAILABLE 
Encryption key is currently not available (e. g. not logged in), cannot encrypt value.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_INVALID_VALUE_DATA_TYPE 
Value data type is not supported.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_INVALID_VALUE_DATA 
Value data content is not supported (e. g. binary data)
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_NESTED_VALUE_DATA 
Value data type is nested.
I18N_OPENXPKI_SERVER_API_OBJECT_SET_DATA_POOL_ENTRY_INVALID_EXPIRATION_DATE 
Invalid expiration date

get_data_pool_entry

 my $value = CTX('api')->get_data_pool_entry({
   PKI_REALM => scalar, see above
   NAMESPACE => scalar, see above
   KEY => scalar, see above
 });

Retrieves stored data entry from the data pool.

Returns: Hashref:

 {
   PKI_REALM => caller argument passed literally
   NAMESPACE => caller argument passed literally
   KEY => caller argument passed literally
   VALUE => scalar, retrieved data
   ENCRYPTED => 0 | 1, set to 1 if entry was originally encrypted
   ENCRYPTION_KEY => scalar, string describing the key id used to encrypt the value
   MTIME => modification time (Epoch)
   EXPIRATION_DATE => expiration date (Epoch)
 }

Exceptions:

I18N_OPENXPKI_SERVER_API_OBJECT_GET_DATA_POOL_ENTRY_INVALID_PKI_REALM 
Invalid PKI Realm was specified.
I18N_OPENXPKI_SERVER_API_OBJECT_GET_DATA_POOL_ENTRY_INVALID_NAMESPACE 
Invalid (e. g. reserved) namespace was specified.
I18N_OPENXPKI_SERVER_API_OBJECT_GET_DATA_POOL_ENTRY_ENCRYPTION_NOT_CONFIGURED 
Encryption is not set up properly.
I18N_OPENXPKI_SERVER_API_OBJECT_GET_DATA_POOL_ENTRY_ENCRYPTION_KEY_UNAVAILABLE 
Encryption key is currently not available (e. g. not logged in), cannot decrypt value.
I18N_OPENXPKI_SERVER_API_OBJECT_GET_DATA_POOL_ENTRY_NOT_FOUND 
Specified key was not found.


list_data_pool_entries

 CTX('api')->list_data_pool_entries({
   PKI_REALM => scalar, see above
   NAMESPACE => scalar, see above, optional
 });

List all keys available in the specified namespace. If no namespace is specified, lists all key, namespace tuples in the specified realm.

Returns: arrayref:

 [
   {
     KEY => string
     NAMESPACE => string
   },
   ....
 ]

Exceptions:

I18N_OPENXPKI_SERVER_API_OBJECT_LIST_DATA_POOL_ENTRIES_INVALID_PKI_REALM 
Invalid PKI Realm was specified.


Examples

Storing and retrieving smartcard unblocking keys

Smartcards use PUKs that allow to reset the Smartcard PIN. In order to allow for Smartcard unblocking by the user the system must access the Smartcard PUK and use it to allow PIN change. PUKs are sensitive information and must be stored in encrypted form.

In order to store a PUK securely from a Workflow the following code could be used:

     my $pki_realm  = CTX('api')->get_pki_realm();
     my $smartcard_id = 'gem2_123456';
     my $smartcard_puk = 'sUpers3cr3t';
 
     CTX('api')->set_data_pool_entry(
 	  {
 	      PKI_REALM => $pki_realm,
 	      NAMESPACE => 'smartcard.puk',
 	      KEY => $smartcard_id,
 	      VALUE => $smartcard_puk,
 	      ENCRYPT => 1,
 	      FORCE => 1,
 	  });

In order to retrieve the encrypted PUK the following code is used:

     my $pki_realm  = CTX('api')->get_pki_realm();
     my $smartcard_id = 'gem2_123456';
     my $data = 
 	CTX('api')->get_data_pool_entry(
 	    {
 		PKI_REALM => $pki_realm,
 		NAMESPACE => 'smartcard.puk',
 		KEY => $smartcard_id,
 	    });
     my $smartcard_puk = $data->{VALUE};

The returned data structure migh look like:

 {
         'ENCRYPTED' => 1,
         'ENCRYPTION_KEY' => '75SKlOB23rVCs0pqGV4iRv2cx2g',
         'KEY' => 'gem2_123456',
         'MTIME' => '1270371668',
         'NAMESPACE' => 'smartcard.puk',
         'PKI_REALM' => 'I18N_OPENXPKI_DEPLOYMENT_TEST_DUMMY_CA',
         'VALUE' => 'sUpers3cr3t'
 };

Referencing configuration data

Currently the only way to configure OpenXPKI is to include literal settings in the XML configuration file. To provide increased flexibility the configuration mechanism should support the possibility to reference entries from a key/value list stored in the database.

Example data:

 PKI_REALM = undef
 NAMESPACE = 'sys.config'
 KEY = 'ldap_host'
 VALUE = 'ldap.example.com'

Storing server statistics

The server allows access to internal runtime information by storing performance data and other statistics in the 'sys.stats' namespace. In order to allow collection of data for multiple server nodes accessing the same database, the server shall append its server ID to the namespace.

Example: config.xml:

 <openxpki>
   <common>
     <database>
       <server_id>0</server_id>
       <server_shift>8</server_shift>
     </database>
   </common>
 </openxpki>

In this example the server ID is 0 which shall be used by the server to set its data.

The following entry could be written if a new process has been spawned:

 my $config = CTX('xml_config');
 my $server_id = $config->get_xpath (
                  XPATH    => [ "common/database/server_id" ],
                  COUNTER  => [ 0 ],
                  CONFIG_ID => 'default',
 );
 
 my $processes = .... # get server statistics to store
 
 CTX('api')->set_data_pool_entry(
 	  {
 	      PKI_REALM => '/',
 	      NAMESPACE => "sys.stats.serverid.$server_id",
 	      KEY => 'processes',
 	      VALUE => $processes,
 	      FORCE => 1,
 	  });