next up previous contents
Next: Example 1: Reading keys Up: PKCS #11 Previous: Platform specific header   Contents


Initialization and cleanup

Let's walk through a typical session. We'll set up the environment, read what keys are present on the device, and print some summary information about them.

First of all, we'll include the header we created in section 3.3

#include "pkcs11_linux.h"

Most PKCS #11 functions return a status code of the type CK_RV. Because we do not want to make an example that doesn't use error checking at all, but we do not want to clutter up the code with graceful error handling, we'll just provide a function that checks the return value and exits with an error message if there was a problem. The standard specifies exactly what error codes all functions can return, so graceful error handling should not be too much of a problem. A good idea is to at least map the error code back to its name.

void
check_return_value(CK_RV rv, const char *message)
{
        if (rv != CKR_OK) {
                fprintf(stderr, "Error at %s: %u\n",
                        message, (unsigned int)rv);
                exit(EXIT_FAILURE);
        }
}

So, now that we have that out of the way, let's initialize the environment.

CK_RV
initialize()
{
        return C_Initialize(NULL);
}

The initialization function has one argument. In it, you can specify some global options. These all have to do with threading. See section 6.6 and section 11.4 of the specification for more information. Since we are not making a threaded application here, we can safely pass NULL.

With the library initialized, it's time to select a slot to use. PKCS providers usually provide multiple slots that can take tokens. In this case, we just want the first one.

CK_SLOT_ID
get_slot()
{
     CK_RV rv;
     CK_SLOT_ID slotId;
     CK_ULONG slotCount = 10;
     CK_SLOT_ID *slotIds = malloc(sizeof(CK_SLOT_ID) * slotCount);

     rv = C_GetSlotList(CK_TRUE, slotIds, &slotCount);
     check_return_value(rv, "get slot list");

     if (slotCount < 1) {
          fprintf(stderr, "Error; could not find any slots\n");
          exit(1);
     }

     slotId = slotIds[0];
     free(slotIds);
     printf("slot count: %d\n", (int)slotCount);
     return slotId;
}

The C_GetSlotList function takes three arguments, the first one is a boolean value that specifies whether to only return slots that actually have a token present at the moment, or just return any slots. The second is an array to place the slot ID values in, and the third arguments is a pointer to the number of slots we have allocated memory for.

As explained in 3.2, if the second argument would have been NULL, the number of slots found will be set in the third argument. This value could be used to allocate exactly the right amount of memory, after which the function would be called again. This mechanism is used often in PKCS #11.

In this case we'll do it in one step, and assume our reader does not have more than 10 slots. If it does, this function should return the error code CKR_BUFFER_TOO_SMALL. Some implementations might just return the first 10 slots, although you shouldn't count on that.

So, if everything went right, we have a slot with a token now. For that slot, we'll start a session. This session will be the context for all actions we do later.

CK_SESSION_HANDLE
start_session(CK_SLOT_ID slotId)
{
        CK_RV rv;
        CK_SESSION_HANDLE session;
        rv = C_OpenSession(slotId,
                           CKF_SERIAL_SESSION,
                           NULL,
                           NULL,
                           &session);
        check_return_value(rv, "open session");
        return session;
}

The second argument is a flags parameter, in which certain settings can be set. CKF_SERIAL_SESSION must always be set. If we would want to have write access to the token (which we don't for just reading keys), we would XOR this with the value CKF_RW_SESSION.

If we would want the library to notify it of certain events in the session, we would provide a callback function as the third argument. See section 11.17 of the specification for more information on this.

The final argument is a pointer to the CK_SESSION_HANDLE variable that we'll use to refer to the session we have opened.

If the token needs a user to provide a PIN, we can use the function C_login. The pin is an array of bytes. Some implementations accept a NULL pin if it wasn't set, although this does not follow the specification.

void
login(CK_SESSION_HANDLE session, CK_BYTE *pin)
{
     CK_RV rv;
     if (pin) {
          rv = C_Login(session, CKU_USER, pin, strlen((char *)pin));
          check_return_value(rv, "log in");
     }
}

That's the initialization. Now we could go and actually do something. But, for completeness' sake, let's first get the cleanup routines out of the way, these are not much more than the reversed function made so far.

void
logout(CK_SESSION_HANDLE session)
{
     CK_RV rv;
     rv = C_Logout(session);
     if (rv != CKR_USER_NOT_LOGGED_IN) {
          check_return_value(rv, "log out");
     }
}

We do one additional check on the return value; if we did not pass a PIN to the login function, we would not be logged in at all, resulting in the error CKR_USER_NOT_LOGGED_IN. Rather than keeping track of whether we should have been logged in, we'll just watch out for this error.

Two things remain: we are still in a session, and we need to clean up the library.

void
end_session(CK_SESSION_HANDLE session)
{
        CK_RV rv;
        rv = C_CloseSession(session);
        check_return_value(rv, "close session");
}

void
finalize()
{
        C_Finalize(NULL);
}


next up previous contents
Next: Example 1: Reading keys Up: PKCS #11 Previous: Platform specific header   Contents
Written by Jelte Jansen
© NLnet Labs, May 13, 2008
jelte@nlnetlabs.nl