next up previous contents
Next: Example 2: Generating a Up: PKCS #11 Previous: Initialization and cleanup   Contents

Example 1: Reading keys

Okay, it's time to do something interesting. In this example, we want to find all the private keys on the token, and print their label and id.

Keys are just one type of object that a token can contain, so if we would have magically acquired a pointer already (which we'll get to in the next function), this is the way to get information about it:

void
show_key_info(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE key)
{
     CK_RV rv;
     CK_UTF8CHAR *label = (CK_UTF8CHAR *) malloc(80);
     CK_BYTE *id = (CK_BYTE *) malloc(10);
     size_t label_len;
     char *label_str;

     memset(id, 0, 10);

     CK_ATTRIBUTE template[] = {
          {CKA_LABEL, label, 80},
          {CKA_ID, id, 1}
     };

     rv = C_GetAttributeValue(session, key, template, 2);
     check_return_value(rv, "get attribute value");

     fprintf(stdout, "Found a key:\n");
     label_len = template[0].ulValueLen;
     if (label_len > 0) {
          label_str = malloc(label_len + 1);
          memcpy(label_str, label, label_len);
          label_str[label_len] = '\0';
          fprintf(stdout, "\tKey label: %s\n", label_str);
          free(label_str);
     } else {
          fprintf(stdout, "\tKey label too large, or not found\n");
     }
     if (template[1].ulValueLen > 0) {
          fprintf(stdout, "\tKey ID: %02x\n", id[0]);
     } else {
          fprintf(stdout, "\tKey id too large, or not found\n");
     }

     free(label);
     free(id);
}

First of all, we define a template containing the types of information we want. A template is an array of (type, pointer, size) values. The type specifies what information we want, the pointer points to allocated data that will contain the result, and the size points to the amount of memory we have allocated there. If the 'pointer' is NULL, the size needed will be placed in 'size', so the application can then allocate exactly enough memory and call this function again. This is just like the way C_GetSlotList worked.

If a value can not be extracted, or the amount of memory we have allocated is not enough, the 'size' value will be -1 after the call has completed.

What data you can ask for depends on the type of object and the function you call.

With this template, we call C_GetAttributeValue. Of course we need to provide the session, the object we want data about, and the template. The final argument is the number of entries in our template array. In this case, we have specified 2 data types we want to get.

So after the call completed, and the library has supposedly filled out our template, we check the size values, and if there is data, print it.

Now we'll make a function that finds the keys on the token, and call the previous function for each key.

void
read_private_keys(session)
{
     CK_RV rv;
     CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
     CK_ATTRIBUTE template[] = {
          { CKA_CLASS, &keyClass, sizeof(keyClass) }
     };
     CK_ULONG objectCount;
     CK_OBJECT_HANDLE object;

     rv = C_FindObjectsInit(session, template, 1);
     check_return_value(rv, "Find objects init");

     rv = C_FindObjects(session, &object, 1, &objectCount);
     check_return_value(rv, "Find first object");

     while (objectCount > 0) {
          show_key_info(session, object);

          rv = C_FindObjects(session, &object, 1, &objectCount);
          check_return_value(rv, "Find other objects");
     }

     rv = C_FindObjectsFinal(session);
     check_return_value(rv, "Find objects final");
}

As in the previous function, we create a template. This time it is used as a filter in the C_FindObjectsInit function. Every object that matches the template will be returned with subsequent calls to C_FindObjects. You can ask for more than one object at a time, but for now, lets just do them one at a time.

When we're done, we call C_FindObjectsFinal, this cleans up any memory allocated by C_FindObjectsInit.

Let's tie this all together in an actual application:

int
main(int argc, char **argv)
{
     CK_SLOT_ID slot;
     CK_SESSION_HANDLE session;
     CK_BYTE *userPin = NULL;
     CK_RV rv;

     if (argc > 1) {
          if (strcmp(argv[1], "null") == 0) {
               userPin = NULL;
          } else {
               userPin = (CK_BYTE *) argv[1];
          }
     }

     rv = initialize();
     check_return_value(rv, "initialize");
     slot = get_slot();
     session = start_session(slot);
     login(session, userPin);
     read_private_keys(session);
     logout(session);
     end_session(session);
     finalize();
     return EXIT_SUCCESS;
}

In case we have a token without a user PIN, we'll let the user specify it on the command line. Normally, it would be a better idea to provide a callback function, or at least do both.

That's it! Compile it, link it, stick it in a stew, and you have your first PKCS #11 application. Wasn't that easy?

On the other hand, this example is only useful when there are actually keys present on the token. What if there aren't, and we want to add them ourselves? Let's do another example.


next up previous contents
Next: Example 2: Generating a Up: PKCS #11 Previous: Initialization and cleanup   Contents
Written by Jelte Jansen
© NLnet Labs, May 13, 2008
jelte@nlnetlabs.nl