C tutorial

Overview

This overview section provides a description of how to achieve two fundamental nCore API programming tasks: connecting to the hardserver and transacting a command. These two tasks are common to almost all cryptographic applications. The rest of this chapter works through a simple example of a basic cryptographic application.

All applications that require nCore functionality first need to create a connection to a hardserver running on an nShield module. The following diagram illustrates the steps required to create a connection to a hardserver running on Entrust hardware:

ndt 1 5

When connected to the hardserver, an application can send an M_Command to a module. The module processes the command and then returns the results along with any relevant error and status codes. The following diagram illustrates the process of transacting a cryptographic operation with the module:

ndt 1 6

The M_Reply structure contains the results of the operation and an M_Status message that indicates the outcome of the operation. If a problem was encountered, the M_Status value gives an indication of what went wrong. The M_Reply contains the results of the command, for example, a key handle or the bytes of an encrypted file.

nCore API functionality used in this tutorial

This tutorial uses the following libraries from the nCore API. You may find it useful to familiarize yourself with these libraries by reading the API documentation, which is located at <nfast_dir>/document/ncore/html/index.html.

  • nfkm.h

    This library provides Security World functionality, for example, card-loading libraries, key-generation, and key-loading.

  • nfinttypes.h

    This library is a utility library that provides standard integer types.

  • nffile.h

    This library is a utility library that provides file manipulation functionality.

  • simplebignum.h

    This library is a utility implementation of bignum functionality.

  • ncthread-upcalls.h

    This library is a thread-handling library.

  • rqcard-applic.h

    This library is a card-loading library.

  • rqcard-fips.h

    This library is a card-loading library for use in a FIPS 140‑2 Level 3 (Federal Information Processing Standards) environment.

Variables used in this tutorial

The following table lists and describes the variables used in this tutorial. Throughout this tutorial you may wish to refer to this table. You may also find it useful to consult the API documentation of the listed types.

Variable Name Variable Type Description

rc

M_Status

Status code returned by operations

worldinfo

NFKM_WorldInfo

Information about a Security World

app

NFast_AppHandle

The application handle

app_init_args

NFastAppInitArgs

Used to initialize the application

conn

NFastApp_Connection

The connection to a hardserver

moduleinfo

NFKM_ModuleInfo

Contains information about the module being used

keyident

NFKM_KeyIdent

The name of the key

keyinfo

NFKM_Key

Information about the key

keyid

M_KeyID

The key loaded into the module

ltid

M_KeyID

The card set loaded into the module

keytype

M_KeyType

The cryptographic key type, for example, KeyType_DSA

mech

M_Mech

The encryption mechanism used, for example, Mech_DSA

sigbytes

M_ByteBlock

The marshaled signature

iv

M_IV

The initialization vector

command

M_Command

The command sent to module

reply

M_Reply

The reply returned by the module

idch

M_KeyID

The ID of the channel used for streaming

rqcard

RQCard

The card-loader handle

rqcard_fips

RQCard_FIPS

The card-loader handle used in a FIPS 140‑2 Level 3 environment

Before connecting to the hardserver

The nCore API provides mechanisms that allow you to control how threading, memory allocation, and numbers larger than the available C data types are handled, through an upcall mechanism. Specifying these upcalls is optional. Also optional is the call context structure, which can contain any contextual information that your application might require to keep track of. If you define your own upcalls and call context they must be supplied as arguments when initializing a handle to the hardserver.

Declaring a call context

Many nCore functions take a call context argument, cctx or ctx, which is passed on to upcalls. The call context structure can be used for any purpose required by an application. For example, the call context could identify an application thread.

The following code shows an example declaration of a call context structure:

struct NFast_Call_Context {
  int notused;
};

Declaring memory allocation upcalls

By default the nCore API manages memory by using the standard C library functions malloc, realloc, and free. To customize memory management, define a collection of memory allocation upcalls and pass this collection when initializing the application handle. For example, a heavily threaded application may allocate memory per thread, and have separate application handles per thread, to avoid contention. In this code example the memory allocation upcalls re-direct back to the default memory application functions. The call context cctx and the transaction context tctx can contain any context information required by your application.

const NFast_MallocUpcalls mallocupcalls = {
  local_malloc,
  local_realloc,
  local_free
};
static void *local_malloc(size_t nbytes,
    struct NFast_Call_Context *cctx,
    struct NFast_Transaction_Context *tctx) {
  return malloc(nbytes);
}
static void *local_realloc(void *ptr,
    size_t nbytes,
    struct NFast_Call_Context *cctx,
    struct NFast_Transaction_Context *tctx) {
  return realloc(ptr, nbytes);
}
static void local_free(void *ptr,
    struct NFast_Call_Context *cctx,
    struct NFast_Transaction_Context *tctx) {
  free(ptr);
}

Declaring threading upcalls

ncthread_upcalls provides a mechanism to specify how threads are implemented on the target platform. If an application needs to use a non-native thread model then the application can either:

  • fill in an nf_thread_upcalls structure with suitable upcalls and optionally write a translation function xlate_cctx_to_ncthread()

  • or fill in an NFast_ThreadUpcalls structure, and use NFAPP_IF_THREAD in the code example below instead of NFAPP_IF_NEWTHREAD.

const NFast_NewThreadUpcalls newthreadupcalls = {
  &ncthread_upcalls,
  xlate_cctx_to_ncthread
};
static void xlate_cctx_to_ncthread(NFast_AppHandle app,
   struct NFast_Call_Context *cc,
   struct nf_lock_cctx **lcc_r) {
  *lcc_r = 0;
}

Initializing the nFast application handle

The hardserver application handle is the main access point to nCore functionality. The following code specifies the application initialization arguments and initializes the application handle. The flags sent to the application initialization function in the following code example are:

  • NFAPP_IF_MALLOC indicates that an application is setting its own memory allocation upcalls

  • NFAPP_IF_BIGNUM is necessary for any bignum operations to work. The following code example uses simplebignum upcalls

  • One of NFAPP_IF_NEWTHREAD or NFAST_IF_THREAD is required in threaded applications. This code example does not perform any multi-threaded operations but the setting are included anyway for the purposes of the example.

memset(&app_init_args, 0, sizeof app_init_args);
app_init_args.flags = NFAPP_IF_MALLOC|NFAPP_IF_BIGNUM|NFAPP_IF_NEWTHREAD;
app_init_args.mallocupcalls = &mallocupcalls;
app_init_args.bignumupcalls = &sbn_upcalls;
app_init_args.newthreadupcalls = &newthreadupcalls;

rc = NFastApp_InitEx(&app, &app_init_args, cctx);

Connecting to the hardserver

Now that application handle is initialized, create a connection to the hardserver, as shown in the following code example. The NFastApp_Connect() automatically determines whether to use pipes, local sockets, or TCP sockets, as appropriate.

rc = NFastApp_Connect(app, &conn, 0, cctx);
if(rc) {
  NFast_Perror("error calling NFastApp_Connect", rc);
  goto cleanup;
}

Getting Security World information

The following code reads in the Security World information that is associated with the application handle. An application handle will only ever be associated with a single Security World, which consists of one or more modules.

rc = NFKM_getinfo(app, &worldinfo, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_getinfo", rc);
  goto cleanup;
}

Setting up the authorization mechanism

The nCore API supports three types of key protection:

  • module protection

  • pass phrase protection

  • card set protection.

The following three code examples demonstrate how to set up an application to use card set protection.

Initializing the card-loading libraries

The following code initializes the card-loading libraries, which are used later in the example. Card-loading libraries are bound to a single connection and to a single Security World.

rc = RQCard_init(&rqcard, app, conn, worldinfo, cctx);
if(rc) {
  NFast_Perror("error calling RQCard_init", rc);
  goto cleanup;
}
rqcard_initialized = 1;

Obtaining additional FIPS authorization

FIPS 140-2 Level 3 mode requires authorization for key-generation, which can be obtained from either an Operator Card or an Administrator Card. The following code initializes the FIPS 140-2 Level 3 code library, which seeks FIPS 140-2 Level 3 authorization when this is required:

rc = RQCard_fips_init(&rqcard, &rqcard_fips);
if(rc) {
  NFast_Perror("error calling RQCard_fips_init", rc);
  goto cleanup;
}
rqcard_fips_initialized = 1;

Selecting a user interface

The following code selects the default user interface for the platform on which the example is running. The user interface will be displayed to the user when authorization is required to perform an operation.

rc = RQCard_ui_default(&rqcard);
if(rc) {
  NFast_Perror("error calling RQCard_ui_default", rc);
  goto cleanup;
}

Generating a symmetric key

This section describes the key-generation process in detail. The process of generating a symmetric key differs slightly from the process of generating an asymmetric key, so each is described in a separate section. There is some repetition in the two sections.

This section does not explain how to use softcards to protect keys. Softcards can be listed with NFKM_listsoftcards() and loaded with NFKM_loadsoftcard(). For more information about using softcards, see the information about nfkm.h in the nCore API documentation.

The following diagram illustrates the key-generation process:

ndt 1 7

The code in this section makes use of the following variables:

Variable Name Variable Type Description

acl_params

NFKM_MakeACL_Params

Used to construct ACLs

blob_params

NFKM_MakeBlobs_Params

Used when making blobs

keyinfo

NFKM_Key

Information about a key

moduleinfo

NFKM_ModuleInfo

The module to use

mc

M_ModuleCert

A certificate from a module

fips140authhandle

NFKM_FIPS140AuthHandle

FIPS authorization

ltid

M_KeyID

A loaded card set

cardset

NFKM_CardSet

Information about a card set

moduleid

M_ModuleID

The ID of a module

cardhash

NFKM_CardSetIdent

A hash of a card set

rc

M_Status

A command return code

command

M_Command

A command structure

reply

M_Reply

A command reply

Obtaining authorization and selecting a module

Keys are generated on a specific module and protected by some form of authorization. When a key is generated the type of authorization that is required to use the key is defined, as well as the purposes for which the key is allowed to be used, for example, only for encryption and decryption, or only for signing and verification.

Using card set protection

The following code prompts the user to provide a card to protect the key that will be generated. The card set hash populates cardhash when the card-loader completes.

rc = RQCard_logic_ocs_anyone(rqcard, &cardhash,
                             "Insert a card set to protect the new key");
if(rc) {
  NFast_Perror("error calling RQCard_logic_ocs_anyone", rc);
  goto cleanup;
}

Selecting a Security World module

Now that authorization has been obtained, prompt the user to select a module in the Security World on which to generate the key. Alternatively you could use the RQCard_whichmodule_specific() function to dictate which module will be used, or the NFKM_getusablemodule() function to use the first available module.

The module ID and a key ID for the desired card set on that module are assigned to the moduleid and ltid variables when the card-loader completes.

rc = RQCard_whichmodule_anyone(rqcard, &moduleid, &ltid);
if(rc) {
  NFast_Perror("error calling RQCard_whichmodule_anyone", rc);
  goto cleanup;
}

rc = rqcard->uf->eventloop(rqcard);
if(rc) {
  NFast_Perror("error running card loader", rc);
  goto cleanup;
}

The moduleid, id, and ltid variables are now populated. Next, populate the moduleinfo variable for the chosen module, and create a card set handle.

for(n = 0; n < worldinfo->n_modules; ++n)
  if(worldinfo->modules[n]->module == moduleid)
    break;
assert(n < worldinfo->n_modules);
moduleinfo = worldinfo->modules[n];

rc = NFKM_findcardset(app, &cardhash, &cardset, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_findcardset", rc);
  goto cleanup;
}
Up to now in this example the application has performed actions common to generating either a symmetric key or an asymmetric key. The process from here on differs depending on which key type is generated.

Preparing the key-generation command and ACL

Start by setting up some command parameters based on the information we have already gathered.

command.cmd = Cmd_GenerateKeyPair;
command.args.generatekey.params.type = keytype;
command.args.generatekey.flags = Cmd_GenerateKey_Args_flags_Certify;
command.args.generatekey.module = moduleinfo->module;

Keys are stored with an ACL, which defines which entities can perform operations with the key. The next step is to populate the acl_params variable with the information needed to create the ACL that will be stored in the key blob along with the key we generate. In this example the application sets the acl_params.f flags parameter to enable key recovery and specify the type of key protection to use. There are three options:

  • card set protection

  • module protection

  • pass phrase protection.

This following code demonstrates how to indicate that a key should be protected by a card set. In this case, the card set is the one selected earlier by the user in Selecting a Security World module.

acl_params.f = NFKM_NKF_RecoveryEnabled|protection;
acl_params.cs = cardset;

The make ACL blob flags (acl_params.f) parameter must be same as the make blob flags parameter (blob_params.f), so is set accordingly.

blob_params.f = acl_params.f;

The next step is to define in the ACL for which operations the key is allowed to be used. In this example, the application specifies that the key can be used to sign, verify, encrypt, or decrypt.

acl_params.op_base = (NFKM_DEFOPPERMS_SIGN
              |NFKM_DEFOPPERMS_VERIFY
              |NFKM_DEFOPPERMS_ENCRYPT
              |NFKM_DEFOPPERMS_DECRYPT);

The application is now ready to generate the ACL:

rc = NFKM_newkey_makeaclx(app, conn, worldinfo, &acl_params,
                              &command.args.generatekey.acl, cctx);

The following code sets up further generate key command parameters. The parameters that are required differ according to key type. For example, if an application is generating a Rijndael key, you need to specify the length of the key required, in bytes:

command.args.generatekey.params.params.random.lenbytes = 128/8;

Generating a key in a FIPS140-2 Level 3 environment requires that an application obtains authorization (in this case, card set authorization) before attempting to generate a key. It is possible that the card loader has already obtained the necessary authorization from a prior card-loading operation. In this case, the following call will retrieve this authorization:

rc = RQCard_fips_get(rqcard_fips, moduleinfo->module, &fips140authhandle,
                     0);

If this call returns Status_RQCardMustContinue, an application must explicitly attempt to obtain the correct authorization as follows:

rc = RQCard_fips_logic(rqcard);
if(rc) {
  NFast_Perror("error calling RQCard_fips_logic", rc);
  goto cleanup;
}
rc = RQCard_whichmodule_specific(rqcard, moduleinfo->module, 0);
if(rc) {
  NFast_Perror("error calling RQCard_whichmodule_anyone", rc);
  goto cleanup;
}
rc = rqcard->uf->eventloop(rqcard);
if(rc) {



  NFast_Perror("error running card loader", rc);
  goto cleanup;
}
rc = RQCard_fips_get(rqcard_fips, moduleinfo->module, &fips140authhandle,
                      0);

Now that the application has obtained the necessary FIPS 140-2 Level 3 authorization (or cancelled the operation if the correct authorization could not be obtained), it can use the authorization to authorize the creation of the key.

rc = NFKM_newkey_makeauth(app, worldinfo, &command.flags, &command.certs,
                          fips140authhandle, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_newkey_makeauth", rc);
  goto cleanup;
}

With or without FIPS authorization, the application has now obtained all the information necessary to transact a key-generation operation, so is now ready to send the key-generation command to the selected module. The reply is checked using the reply checking utility function mentioned at the beginning of the chapter.

rc = NFastApp_Transact(conn, cctx, &command, &reply, 0);
rc = check_reply(rc, &reply, "error generating new key");
if(rc)
  goto cleanup;

The application has now generated a new key, but as yet the key exists only in the module’s memory. Next, construct an NFKM_Key key information structure (keyinfo) and then save it to disk.

keyinfo->v = 8;
keyinfo->appname = keyident.appname;
keyinfo->ident = keyident.ident;
time(&keyinfo->gentime);

The next step is to populate the parameters of the blob_params structure, which contains the information that is to be written to the key blob. The following code also checks that a key-generation certificate was included in the reply. The NFKM_MakeBlobsParams flags blob_params.f must be the same as the flags passed to NFKM_newkey_makeaclx() when the application created the private ACL.

mc = 0;
blob_params.kpriv = reply.reply.generatekey.key;
if(reply.reply.generatekey.flags & Cmd_GenerateKey_Reply_flags_cert_present)
  mc = reply.reply.generatekey.cert;
if(cardset) {

  blob_params.lt = ltid;
  blob_params.cs = cardset;
}
blob_params.fips = fips140authhandle;

The parameters required for the NFKM_newkey_makeblobsx() are now populated, and the application is ready to create the key blob. As this is a symmetric key type the application need only save a private key blob.

rc = NFKM_newkey_makeblobsx(app, conn, worldinfo, &blob_params, keyinfo, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_newkey_makeblobsx", rc);
  goto cleanup;
}
if(mc) {
  rc = NFKM_newkey_writecert(app, conn, moduleinfo, blob_params.kpriv, mc,
                            keyinfo, cctx);
  if(rc) {
    NFast_Perror("error calling NFKM_newkey_writecert", rc);
    goto cleanup;
 }
}

The keyinfo structure is now ready to be saved to disk.

rc = NFKM_recordkey(app, keyinfo, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_recordkey", rc);
  goto cleanup;
}
rc = Status_OK;

Freeing memory

The final part of the key-generation process is the important step of unloading the key information in the module.

NFastApp_FreeACL(app, cctx, 0, &command.args.generatekey.acl);
NFKM_cmd_destroy(app, conn, 0, reply.reply.generatekey.key,
                 "generatekey.key", cctx);
if(ltid) NFKM_cmd_destroy(app, conn, 0, ltid, "ltid", cctx);

If you are running your application in FIPS 140-2 Level 3 mode, NFKM_newkey_makeauth() creates a certificate list, which also needs to be freed:

if(command.flags & Command_flags_certs_present)
   NFastApp_Free_CertificateList(app, cctx, 0, command.certs);

NFastApp_Free_Reply(app, cctx, 0, &reply);
keyinfo->appname = 0;
keyinfo->ident = 0;
NFKM_freekey(app, keyinfo, cctx);
NFKM_freecardset(app, cardset, cctx);

This concludes the explanation of symmetric key-generation. The next section describes the process of generating asymmetric keys.

Generating an asymmetric key

This section describes the asymmetric key-generation process in detail. The process of generating a symmetric key differs slightly from the process of generating an asymmetric key, so each is described in a separate section. There is some repetition in the two sections.

This section does not explain how to use softcards to protect keys. Softcards can be listed with NFKM_listsoftcards() and loaded with NFKM_loadsoftcard(). See the nCore API documentation of nfkm.h for more information about using softcards.

The following diagram illustrates the key-generation process:

ndt 1 8

The following diagram illustrates how the programming environment architecture stores generated asymmetric keys. See nCore architecture for more information about the programming environment architecture.

ndt 1 9

The code in this section makes use of the following variables:

Variable Name Variable Type Description

acl_params

NFKM_MakeACL_Params

Used to construct ACLs

blob_params

NFKM_MakeBlobs_Params

Used when making blobs

keyinfo

NFKM_Key

Information about a key

moduleinfo

NFKM_ModuleInfo

The module to use

mc

M_ModuleCert

A certificate from a module

fips140authhandle

NFKM_FIPS140AuthHandle

FIPS authorization

ltid

M_KeyID

A loaded card set

cardset

NFKM_CardSet

Information about a card set

moduleid

M_ModuleID

The ID of a module

cardhash

NFKM_CardSetIdent

A hash of a card set

rc

M_Status

A command return code

command

M_Command

A command structure

reply

M_Reply

A command reply

Obtaining authorization and selecting a module

Keys are generated on a specific module and protected by some form of authorization. When a key is generated the type of authorization that is required to use the key is defined, as well as the purposes for which the key is allowed to be used, for example, only for encryption and decryption, or only for signing and verification.

Using card set protection

Proper authorization is required to generate a key. This example handles card set authorization. The following code prompts the user to provide a card to protect the key that is to be generated. The card set hash populates cardhash when the card-loader completes.

rc = RQCard_logic_ocs_anyone(rqcard, &cardhash,
                             "Insert a cardset to protect the new key");
if(rc) {
  NFast_Perror("error calling RQCard_logic_ocs_anyone", rc);
  goto cleanup;
}

Selecting a Security World module

Now that authorization has been obtained, prompt the user to select a module in the Security World on which to generate the key. Alternatively you could use the RQCard_whichmodule_specific() function to dictate which module to use or the NFKM_getusablemodule() function to use the first available module.

The module ID and a key ID for the desired card set on that module are assigned to the moduleid and ltid variables when the card-loader completes.

rc = RQCard_whichmodule_anyone(rqcard, &moduleid, &ltid);
if(rc) {
  NFast_Perror("error calling RQCard_whichmodule_anyone", rc);
  goto cleanup;
}

rc = rqcard->uf->eventloop(rqcard);
if(rc) {
  NFast_Perror("error running card loader", rc);
  goto cleanup;
}

The moduleid, id and ltid are now populated. The next step is to populate the moduleinfo variable for the chosen module, and create a card set handle.

for(n = 0; n < worldinfo->n_modules; ++n)
  if(worldinfo->modules[n]->module == moduleid)
    break;
assert(n < worldinfo->n_modules);
moduleinfo = worldinfo->modules[n];

rc = NFKM_findcardset(app, &cardhash, &cardset, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_findcardset", rc);
  goto cleanup;
}
Up to now in this example the application has performed actions common to generating either a symmetric key or an asymmetric key. The process from here on differs depending on which key type is generated.

Preparing the key-generation command and ACL

Start by setting up some command parameters based on the information we have already gathered.

command.cmd = Cmd_GenerateKeyPair;
command.args.generatekeypair.params.type = keytype;
command.args.generatekeypair.flags = Cmd_GenerateKeyPair_Args_flags_Certify;
command.args.generatekeypair.module = moduleinfo->module;

Keys are stored with an ACL which defines which entities can perform operations with the key. The next step is to populate the acl_params variable with the information needed to create the ACL that is stored in the key blob along with the key we generate. The application sets the acl_params.f flags parameter to enable key recovery, and specify the type of key protection to use. There are three options:

  • card set protection

  • module protection

  • pass phrase protection.

This following code demonstrates how to indicate that a key should be protected by a card set. In this case the card set is the one selected earlier by the user in Selecting a Security World module.

acl_params.f = NFKM_NKF_RecoveryEnabled|protection;
acl_params.cs = cardset;

The make ACL blob flags (acl_params.f) must be same as the make blob flags (blob_params.f), so it is set accordingly.

blob_params.f = acl_params.f;

The next step is to define in the ACL which operations the key is allowed to be used for. Firstly the application defines the allowed uses for the private key ACL. The is_signing_only_keytype() function is not an nCore function:

if(is_signing_only_keytype(keytype))
  acl_params.op_base = NFKM_DEFOPPERMS_SIGN;
else if(is_encryption_only_keytype(keytype))
  acl_params.op_base = NFKM_DEFOPPERMS_DECRYPT;
else
  acl_params.op_base = (NFKM_DEFOPPERMS_SIGN
                 |NFKM_DEFOPPERMS_DECRYPT);

The application is now ready to generate the private key ACL:

rc = NFKM_newkey_makeaclx(app, conn, worldinfo, &acl_params,
                          &command.args.generatekeypair.aclpriv, cctx);

For asymmetric keys the application also defines a public key ACL.

acl_params.f = NFKM_NKF_PublicKey;
if(is_signing_only_keytype(keytype))
  acl_params.op_base = NFKM_DEFOPPERMS_VERIFY;
else if(is_encryption_only_keytype(keytype))
  acl_params.op_base = NFKM_DEFOPPERMS_ENCRYPT;
else
  acl_params.op_base = (NFKM_DEFOPPERMS_VERIFY
                 |NFKM_DEFOPPERMS_ENCRYPT);

The public key ACL is created in the same manner as the private key ACL:

rc = NFKM_newkey_makeaclx(app, conn, worldinfo, &acl_params,
                          &command.args.generatekeypair.aclpub, cctx);

The following code sets up further key generation command parameters. The parameters that are required differ according to key type. For example, an application might use the following code when generating a 1024 bit DSA key using strict key verification. For details of the parameters required for the types of key you want to generate, see the relevant nCore API documentation.

command.args.generatekeypair.params.params.dsaprivate.flags =
  KeyType_DSAPrivate_GenParams_flags_Strict;
command.args.generatekeypair.params.params.dsaprivate.lenbits = 1024;

Generating a key in a FIPS 140-2 Level 3 environment requires that an application obtains authorization (in this case, card set authorization) before attempting to generate a key. It is possible that the card loader has already obtained the necessary authorization from a prior card-loading operation. In this case, the following call retrieves this authorization:

rc = RQCard_fips_get(rqcard_fips, moduleinfo->module, &fips140authhandle,
                     0);

If this call returns Status_RQCardMustContinue, an application must explicitly attempt to obtain the correct authorization as follows:

rc = RQCard_fips_logic(rqcard);
if(rc) {
  NFast_Perror("error calling RQCard_fips_logic", rc);
  goto cleanup;
}
rc = RQCard_whichmodule_specific(rqcard, moduleinfo->module, 0);
if(rc) {
  NFast_Perror("error calling RQCard_whichmodule_anyone", rc);
  goto cleanup;
}
rc = rqcard->uf->eventloop(rqcard);
if(rc) {
  NFast_Perror("error running card loader", rc);
  goto cleanup;
}
rc = RQCard_fips_get(rqcard_fips, moduleinfo->module, &fips140authhandle,
                      0);

Now that the application has obtained the necessary FIPS 140-2 Level 3 authorization (or cancelled the operation if the correct authorization could not be obtained), it can use the authorization to authorize the creation of the key.

rc = NFKM_newkey_makeauth(app, worldinfo, &command.flags, &command.certs,
                          fips140authhandle, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_newkey_makeauth", rc);
  goto cleanup;
}

With or without FIPS authorization, the application has now obtained all the information necessary to transact a key-generation operation, so is now ready to send the key-generation command to the selected module. The reply is checked using the reply checking utility function mentioned at the beginning of the chapter.

rc = NFastApp_Transact(conn, cctx, &command, &reply, 0);
rc = check_reply(rc, &reply, "error generating new key");
if(rc)
  goto cleanup;

The application has now generated a new key, but as yet the key exists only in the module’s memory. Next, construct an NFKM_Key key information structure (keyinfo) and then save it to disk.

keyinfo->v = 8;
keyinfo->appname = keyident.appname;
keyinfo->ident = keyident.ident;
time(&keyinfo->gentime);

The next step is to populate the parameters of the blob_params structure, which contains the information that will be written to the key blob. The following code also checks that a key-generation certificate was included in the reply. The NFKM_MakeBlobsParams flags blob_params.f must be the same as the flags passed to NFKM_newkey_makeaclx() when the application created the private ACL.

mc = 0;
blob_params.kpriv = reply.reply.generatekeypair.keypriv;
blob_params.kpub = reply.reply.generatekeypair.keypub;
if(reply.reply.generatekeypair.flags & Cmd_GenerateKeyPair_Reply_flags_certpriv_present)
  mc = reply.reply.generatekeypair.certpriv;
if(cardset) {
  blob_params.lt = ltid;
  blob_params.cs = cardset;
}
blob_params.fips = fips140authhandle;

The parameters required for the NFKM_newkey_makeblobsx() are now populated and the application can now create the key blob.

rc = NFKM_newkey_makeblobsx(app, conn, worldinfo, &blob_params, keyinfo, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_newkey_makeblobsx", rc);
  goto cleanup;
}
if(mc) {
  rc = NFKM_newkey_writecert(app, conn, moduleinfo, blob_params.kpriv, mc,
                            keyinfo, cctx);
  if(rc) {
    NFast_Perror("error calling NFKM_newkey_writecert", rc);
    goto cleanup;
 }
}

The keyinfo structure is now ready to be saved to disk.

rc = NFKM_recordkey(app, keyinfo, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_recordkey", rc);
  goto cleanup;
}
rc = Status_OK;

Freeing memory

The final part of the key-generation process is the important step of freeing the memory used by the application, so that no key information remains in memory, which would make the key vulnerable to attackers.

NFastApp_FreeACL(app, cctx, 0, &command.args.generatekeypair.aclpriv);
NFastApp_FreeACL(app, cctx, 0, &command.args.generatekeypair.aclpub);
NFKM_cmd_destroy(app, conn, 0, reply.reply.generatekeypair.keypriv,
                 "generatekeypair.keypriv", cctx);
NFKM_cmd_destroy(app, conn, 0, reply.reply.generatekeypair.keypub,
                 "generatekeypair.keypub", cctx);
if(ltid) NFKM_cmd_destroy(app, conn, 0, ltid, "ltid", cctx);

If you are running your application in FIPS 140-2 Level 3 mode, NFKM_newkey_makeauth() will have created a certificate list, which also needs to be freed:

if(command.flags & Command_flags_certs_present)
   NFastApp_Free_CertificateList(app, cctx, 0, command.certs);

NFastApp_Free_Reply(app, cctx, 0, &reply);
keyinfo->appname = 0;
keyinfo->ident = 0;
NFKM_freekey(app, keyinfo, cctx);
NFKM_freecardset(app, cardset, cctx);

This concludes the explanation of asymmetric key-generation.

Using a key

Once a key has been generated on a module the encrypted key information, or key blob, is stored on the hard disk of the application that requested it. For your application to use a key, you first need to pass the information contained in the key blob to the hardserver, which will use a module to decrypt the key and return a key handle to your application.

The following diagram illustrates the process of loading a key:

ndt 1 10

Finding a key

To load a key, first locate the key blob. A key is identified by the name of the application that created it and the key identifier. The following code tries to find an existing key blob of the requested type. If a key of this type cannot be found, the code generates a new key.

The following code uses a function called generate_key() to generate a key if a key cannot be found.

rc = NFKM_findkey(app, keyident, &keyinfo, cctx);
if(rc) {
  NFast_Perror("error calling NFKM_findkey", rc);
  goto cleanup;
}
if(!keyinfo) {
  rc = generate_key(app, conn, worldinfo, &rqcard, &rqcard_fips, opt_protect,
                    keyident, keytype, cctx);
  if(rc)
    goto cleanup;
  rc = NFKM_findkey(app, keyident, &keyinfo, cctx);
  if(rc) {
    NFast_Perror("error calling NFKM_findkey", rc);
    goto cleanup;
  }
  if(keyinfo == 0) {
    fprintf(stderr,
            "NFKM_findkey could not find key even after generating it.\n");
    rc = -1;
    goto cleanup;
  }
}

Loading a key

Before a key can be loaded into a module, an application must obtain the appropriate authorization. In this example the authorization required comes from a card in a card set, so the application must first initialize the card-loading libraries:

if(keyinfo->flags & Key_flags_ProtectionCardSet) {
  M_ModuleID moduleid;
  int n;
  rc = RQCard_logic_ocs_specific(&rqcard, &keyinfo->cardset,
                                 "Load cardset");
  if(rc) {
    NFast_Perror("error calling RQCard_logic_ocs_specific", rc);
    goto cleanup;
  }
}

A Security World often contains multiple modules, many of which may have the key that is needed to decrypt the key blob an application wants to load. For this example the user is prompted to choose a module that contains the necessary key, and then prompted to provide the card that authorizes the use of the key:

rc = RQCard_whichmodule_anyone(&rqcard, &moduleid, &ltid);
if(rc) {
  NFast_Perror("error calling RQCard_whichmodule_anyone", rc);
  goto cleanup;
}
rc = rqcard.uf->eventloop(&rqcard);
if(rc) {
  NFast_Perror("error running card loader", rc);
  goto cleanup;
}

It is also possible for an application to ask the Security World to nominate a usable module by using the NFKM_getusablemodule() function:

rc = NFKM_getusablemodule(worldinfo, 0, &moduleinfo);
if(rc) {
  NFast_Perror("error calling NFKM_getusablemodule", rc);
  goto cleanup;
}

Now that the user has selected a module, an application can populate the moduleinfo variable, which is later used as a parameter to the NFKM_cmd_loadblob() function.

for(n = 0; n < worldinfo->n_modules; ++n)
  if(worldinfo->modules[n]->module == moduleid)
    break;
 assert(n < worldinfo->n_modules);
 moduleinfo = worldinfo->modules[n];

The application has now gathered all the information it needs to load the key onto a module using the NFKM_cmd_loadblob() function. The next step is to prepare a pointer to the key that will be loaded into the module. The following code loads the public key blob. An application can load the private key blob in similar fashion using &keyinfo->privblob.

const M_ByteBlock *blobptr;
blobptr = &keyinfo->pubblob;

The following code attempts to load the key blob. NFKM_cmd_loadblob() fills in the command structure and handles the reply. Assuming that the command executes successfully, you will now have a handle on the key loaded onto the selected module.

It is possible to construct an M_Command structure by using Cmd_LoadBlob() directly instead.
rc = NFKM_cmd_loadblob(app,
                       conn,
                       moduleinfo->module,
                       blobptr,
                       ltid,
                       &keyid,
                       "loading key blob",
                       cctx);
if(rc) {
  NFast_Perror("error calling NFKM_cmd_loadblob", rc);
  goto cleanup;
}

Encrypting a file

This section demonstrates how to encrypt the contents of a text file by using a secure channel. For the sake of simplicity, this example has no error handling.

First, generate an appropriate initialization vector:

iv.mech = Mech_RijndaelmCBCi128pPKCS5;
for (i=0; i<sizeof iv->generic128.iv.bytes; i++)
  iv.iv->generic128.iv.bytes[i]=(unsigned char)((i*19) ^ iv.mech);

Next, open a channel to use to encrypt the file. The mechanism that the channel uses to encrypt the file is specified when the channel is opened:

M_Command channel_open_command;
M_Reply channel_open_reply;
M_Status channel_open_rc;
channel_open_command.cmd = Cmd_ChannelOpen;
channel_open_command.args.channelopen.type = ChannelType_Any;
channel_open_command.args.channelopen.mode = ChannelMode_Encrypt;
channel_open_command.args.channelopen.mech = mech;

Some M_Command arguments are optional. In this example, the application specifies both the key to be used to encrypt the file and the initialization vector and indicates which optional arguments have been specified by setting the appropriate flags:

channel_open_command.args.channelopen.flags |= Cmd_ChannelOpen_Args_flags_key_present;
channel_open_command.args.channelopen.key = &keyid;
channel_open_command.args.channelopen.flags |= Cmd_ChannelOpen_Args_flags_given_iv_present;
channel_open_command.args.channelopen.given_iv = iv;

To open the channel, transact the M_Command in the usual way and then set the channel ID pointer idch:

channel_open_rc = NFastApp_Transact(conn, cctx, &channel_open_command, &channel_open_reply, 0);
idch = channel_open_reply.reply.channelopen.idch;

The next step is to load the input file (the file to be encrypted) into a file stream (inputstream) and prepare the output file stream (outputstream) to which the encrypted file is going to be written.

inputstream = fopen("file_in.txt", "rb");
outputstream = fopen("file_out.txt", "wb");

Now that the application has opened the channel and prepared the input and output streams, start to prepare an M_Command to process the inputstream through the channel.

M_Command channel_process_stream_command;
M_Reply channel_process_stream_reply;
M_Status channel_process_stream_rc;
int eof = 0;
unsigned char buffer[6144];
size_t bytes_read;

Next, read the bytes of the inputstream into a char buffer, updating the channel on each read.

do {
  bytes_read = fread(buffer, 1, sizeof buffer, inputstream);
  if(ferror(inputstream)) {
    fprintf(stderr, "error reading from %s: %s\n",
            input_path, strerror(errno));
    rc = -1;
    goto cleanup;
  }

  if(feof(inputstream))
    eof = 1;

  command.cmd = Cmd_ChannelUpdate;
  if(eof)
    command.args.channelupdate.flags |= Cmd_ChannelUpdate_Args_flags_final;
  command.args.channelupdate.idch = idch;
  command.args.channelupdate.input.ptr = buffer;
  command.args.channelupdate.input.len = (M_Word)bytes_read;

  rc = NFastApp_Transact(conn, cctx, &command, &reply, 0);
  rc = check_reply(rc, 0, "Cmd_ChannelUpdate");
  if(rc)
    goto cleanup;

  if(reply.reply.channelupdate.output.len) {
    if(outputstream) {
      fwrite(reply.reply.channelupdate.output.ptr,
             1, reply.reply.channelupdate.output.len,
             outputstream);
      /* Check for a write error */
      if(ferror(outputstream)) {
        fprintf(stderr, "error writing to %s: %s\n",
                output_path, strerror(errno));
        rc = -1;
        NFastApp_Free_Reply(app, cctx, 0, &reply);
        goto cleanup;
      }
    }
    if(outputdstr) {
      if(nf_dstr_putm(outputdstr, reply.reply.channelupdate.output.ptr,
                      reply.reply.channelupdate.output.len)) {
        fprintf(stderr, "error writing to dstr: %s\n", strerror(errno));
        rc = -1;
        goto cleanup;
      }
    }
  }

 NFastApp_Free_Reply(app, cctx, 0, &reply);
 memset(&reply, 0, sizeof reply);
} while(!eof);

If the file was successfully encrypted, save the file to disk:

if(channel_process_stream_reply.reply.channelupdate.output.len) {
  if(outputstream) {
    fwrite(channel_process_stream_reply.reply.channelupdate.output.ptr,
           1, channel_process_stream_reply.reply.channelupdate.output.len,
           outputstream);
    writefile(ciphertext_path,
              reply->reply.encrypt.cipher.data.generic128.cipher.ptr,
              reply->reply.encrypt.cipher.data.generic128.cipher.len);
  }
}

The final step is to free memory and close the outputstream.

NFastApp_Free_Reply(app, cctx, 0, &reply);
memset(&reply, 0, sizeof reply);
fclose(outputstream);

Cleaning up resources

Memory leaks and objects left in memory constitute a security risk. The following code removes all sensitive information from memory and cleanly shuts down the connection to the hardserver.

free(sigbytes.ptr);
if(keyid) NFKM_cmd_destroy(app, conn, 0, keyid, "keyid", cctx);
if(idch) NFKM_cmd_destroy(app, conn, 0, idch, "idch", cctx);
NFastApp_Free_Reply(app, cctx, 0, &reply);
if(rqcard_fips_initialized) RQCard_fips_free(&rqcard, &rqcard_fips);
if(rqcard_initialized) RQCard_destroy(&rqcard);
NFKM_freekey(app, keyinfo, cctx);
NFKM_freeinfo(app, &worldinfo, cctx);
if(conn) NFastApp_Disconnect(conn, cctx);
NFastApp_Finish(app, cctx);
if(inputstream) fclose(inputstream);
if(outputstream) fclose(outputstream);