Directory content for CCA, ICA, EP11, and Soft tokens

Each token uses a unique token directory. This token directory stores the token-specific objects (like for example, key objects, user PIN, SO PIN, or hashes). Thus, the information for a certain token is separated from all other tokens. Read the information about the format of data and objects stored in these directories. openCryptoki users may need this information for example, when working with containerized applications.

As of openCryptoki version 3.16, CCA, ICA, EP11, and Soft tokens have the same structure and content within their directories.

The path name of the token directories is derived from either the token default name or by the token name as defined in the opencryptoki.conf file. The location of the token directories may look similar to the shown examples:

[root@t3545033 opencryptoki]# pwd
/var/lib/opencryptoki
[root@t3545033 opencryptoki]# ls -l
total 24
drwxrwx---. 3 root pkcs11 4096 Mar 19 11:47 ccatok
drwxrwx---. 3 root pkcs11 4096 Mar 19 12:35 ep11tok
drwxrwx---. 3 root pkcs11 4096 Mar 19 11:47 lite     /* legacy name of ICA Tok */
drwxrwx---. 3 root pkcs11 4096 Mar 19 11:47 swtok

The directory containing the token directories, in our example /var/lib/opencryptoki, must not be an NFS nor a CIFS file system, but must be a file system that supports the flock() function which manages file locks.

Note: With openCryptoki, you can select from two data store formats:
  • Before openCryptoki version 3.12, there is only the one NVTOK.DAT format (the old format).
  • Starting with openCryptoki version 3.12, you can choose to use a FIPS compliant data format. Being FIPS compliant, the token data is stored in a format that is better protected against attacks than the previously used data format.
If you want to use the token data format that was generated with FIPS compliant operations, you must explicitly specify the tokversion option for the token's slot entry in the openCryptoki configuration file. You must do this before token initialization with the pkcsconf command, for example:
Figure 1. Slot entry for an EP11 token with FIPS compliant data format in the opencryptoki.conf file

slot 4
{
stdll = libpkcs11_ep11.so
confname = ep11tok01.conf
tokname = ep11token01 
tokversion = 3.12
description = "Ep11 Token"
manufacturer = "IBM"
hwversion = "4.11"
firmwareversion = "2.0"
}

You can use the pkcstok_migrate utility to transform an EP11 token, a CCA token, an ICA token, or a Soft token created with any version of openCryptoki into a data format that was generated by FIPS compliant operations. For more information, read Migrating to FIPS compliance - pkcstok_migrate utility.

There are two objects derived from both a token’s user PIN and a token’s SO PIN:

  • a non-secret password hash which is checked at login
  • and a secret key encryption key (KEK) which is used to wrap the token’s master key (SO KEK or a user KEK, respectively).

Therefore, the token’s master key is stored on disk twice:

  • The MK_SO file holds the master key wrapped with the SO KEK.
  • The MK_USER file holds the master key wrapped with the user KEK.

So both user and SO can access the master key. The password-based key derivation function PBKDF2 is used to derive those four objects. An iteration count of 100000 is used with a salt consisting of different purpose strings for each of the four derivations and a random part. Iteration count, purpose strings, and the random parts are non-secret and can be stored on disk with the other non-volatile token data in NVTOK.DAT, together with the corresponding data. The two password hashes are non-secret and can also be stored unencrypted in NVTOK.DAT.

The two key-encryption keys (KEKs) are secret and cannot be stored with a token’s non-volatile token data: They must be (re-)derived when needed and are cached on the stack for an application’s lifetime.

All following structures are C pseudo code, used to describe data structures at byte level.

The old NVTOK.DAT format (still valid)


struct TOKEN_DATA {
    CK_TOKEN_INFO info;
    u8 user_pin_sha1 [24];
    u8 so_pin_sha1 [24];
    u8 next_token_obj_name[8];
    u32 allow_weak_des;
    u32 check_des_parity;
    u32 allow_key_mods;
    u32 netscape_mods;
};

The new NVTOK.DAT format

Note: The new NVTOK.DAT is available starting with openCryptoki version 3.12 and is valid in parallel with the old format.

struct TOKEN_DATA {
    /* --- old format for compat --- */
    CK_TOKEN_INFO info;
    u8 user_pin_sha1 [24];
    u8 so_pin_sha1 [24];
    u8 next_token_obj_name[8];
    u32 allow_weak_des;  
    u32 check_des_parity;
    u32 allow_key_mods;
    u32 netscape_mods;

    /* --- 3.12 additions start here --- */

    u32 version; /* tokversion major<<16|minor */

    /* --- PBKDF2 --- 
     * 64b salts are 32b purpose string concat 32b random */
    /* SO PW hash (login) */
    u64 so_login_it;
    u8 so_login_salt[64];
    u8 so_login_key[32];
    /* User PW hash (login) */
    u64 user_login_it;
    u8 user_login_salt[64];
    u8 user_login_key[32];
    /* SO MK KEK (wrap) */
    u64 so_wrap_it;
    u8 so_wrap_salt[64];
    /* User MK KEK (wrap) */
    u64 user_wrap_it;
    u8 user_wrap_salt[64];
};

Changes from old to new data store format

In the old format, the multiple-byte-width data types were not serialized before stored to disk, so NVTOK_DAT could not be used on little endian (LE) and big endian (BE) platforms. The new format serializes those data types from host to BE byte order. The AES Key Wrap algorithm is used to wrap a token’s master key (MK) with the KEKs. The token’s master key is randomly generated at token initialization. In the old format, the master key was a 3DES CBC key. The KEK was a 2TDEA CBC key. CBC was used with a per token (fixed) initialization vector (IV). Integrity was intended to be provided by a SHA1 hash of the unencrypted key. The MK master key object had the following format:


struct MK {
    u8 MK [24];
    u8 sha1 [20];
    u8 padding[4];
};

It was wrapped by the SO KEK and stored to MK_SO and was also wrapped by the user KEK and stored to MK_USER. The new format defines the MK to be an AES-256 key. Its unencrypted format is just the 32 key bytes. Its encrypted format is a 40 byte key blob output by the AES Key Wrap algorithm (wrapped with the SO or user KEK). The file names MK_SO and MK_USER are unchanged.

The old format stored non-private token objects unencrypted in the following format:


struct OBJECT_PUB {
    u32 total_len;
    u8 private_flag;
    u8 object[object_len];
};

The new format is:


struct OBJECT_PUB {
    //--------------        <--+
    u32 tokversion;            | 16-byte header
    u8 private_flag;           |
    u8 reserved[7];            |
    u32 object_len;            |
    //--------------        <--+
    u8 object[object_len];     | body
    //--------------        <--+
};

The old format encrypted all private token objects under the MK. The unencrypted format was:


struct OBJECT_PRIV {
    u32 total_len;
    u8 private_flag;
    //--- enc ---
    u32 object_len;
    u8 object[object_len];
    u8 sha1[20];
    u8 padding[padding_len];
};

The new format features authenticated encryption via AES-256-GCM. To avoid initialization vector (IV) uniqueness , instead of using the MK to encrypt all token objects, the MK is used to wrap a per-object key using the AES Key Wrap algorithm. The wrapped per-object key is stored together with the IV and other meta-data as additional authenticated data (AAD) in the authenticated object header. The 16 byte GMAC tag is appended to the authenticated and encrypted object body which holds the private token object’s data:


struct OBJECT_PRIV {
    u32 total_len;
    //- auth -------        <--+
    u32 tokversion;            | 64-byte header
    u8 private_flag;           |
    u8 reserved[3];            |
    u8 key_wrapped[40];        |
    u8 iv[12];                 |
    u32 object_len;            |
    //- auth+enc ---        <--+
    u8 object[object_len];     | body
    //--------------        <--+
    u8 tag[16];                | 16-byte footer
    //--------------        <--+
};

In the old format, the multiple-byte-width data types were not serialized before stored to disk, so the token objects could not be moved between LE and BE platforms. The new format serializes all header and footer data from host to BE byte order, so all object data can always be encrypted and decrypted. However, the decrypted object data itself (body) must still be interpreted in host byte order.

So the new format should only be used with fresh setups. The new format can be used by specifying the new tokversion keyword in the token’s slot configuration in opencryptoki.conf. For a value of equal or grater 3.12 the new format is used.

Here is a table of all key material that is used per version 3.12 token:

Table 1.
NAME TYPE GENERATION USAGE STORAGE
MK 256-bit AES random wrap tok.obj keys (AES-KW) wrapped on disk (MK_SO,MK_USER)
SO KEK 256-bit AES derived from SO PIN (PBKDF2) wrap MK (AES-KW) cached on stack/heap
User KEK 256-bit AES derived from User PIN (PBKDF2) wrap MK (AES-KW) cached on stack/heap
SO PW hash 256-bit derived from SO PIN (PBKDF2) SO login on disk (NVTOK.DAT)
User PW hash 256-bit derived from User PIN (PBKDF2) User login on disk (NVTOK.DAT)
Tok.obj keys 256-bit AES random auth.enc of tok.obj data wrapped on disk (in tok.objs)