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

Example 3: Signing data

If the last example worked on your particular device, we should have some keys now. You should be able to verify it by calling the application for example 1 again.

But what good are keys if we don't use them? In this example, we'll create a very simple application to sign the contents of a file, and store the signature data in another. Normally, this data would be enclosed in some form of envelope, depending on the security protocol used. But again, for the sake of simplicity, we'll just write the raw data.

We can use the same initalization functions as in example 1 and 2. Since we will not be writing to the device, the flag CKF_RW_SESSION can be removed again from start_session.

We are going to use Sha1WithRSA, so let's first get the SHA1 digest out of the way. Some devices and PKCS #11 libraries can do this in one go, by using the mechanism CKM_SHA1_RSA_PKCS, but some can only do them separately.

CK_RV
digest_data(CK_SESSION_HANDLE session,
            FILE *data_file,
            CK_BYTE *digest,
            CK_ULONG *digestLen)
{
     CK_MECHANISM digest_mechanism;
     CK_RV rv;

     CK_ULONG dataLen;
     CK_BYTE *data = malloc(1024);

     if (!data_file) {
          fprintf(stderr, "Error, no file handle in digest_data\n");
          exit(3);
     }

     digest_mechanism.mechanism = CKM_SHA_1;
     digest_mechanism.pParameter = NULL;
     digest_mechanism.ulParameterLen = 0;

     rv = C_DigestInit(session, &digest_mechanism);
     check_return_value(rv, "digest init");

     printf("read data from %p\n", data_file);
     dataLen = fread(data, 1, 1024, data_file);
     printf("read %u bytes\n", (unsigned int)dataLen);
     while (dataLen > 0) {
          printf("add %u bytes to digest\n", (unsigned int)dataLen);
          rv = C_DigestUpdate(session, data, dataLen);
          check_return_value(rv, "digest update");
          dataLen = fread(data, 1, 1024, data_file);
     }

     rv = C_DigestFinal(session, digest, digestLen);
     check_return_value(rv, "digest final");

     free(data);
     return rv;
}

In this method, we initialize a digest operation with the mechanism CKM_SHA_1. This mechanism does not take any arguments, so we set pParameter to NULL. After we call C_DigestInit, we feed it the data we are reading from the input file.

The actual digest operation is performed when we call C_DigestFinal. If the memory we allocated for the digest is too little (as told to the implementation by the variable dataLen in this case), this would return the error CKR_BUFFER_TOO_SMALL. Otherwise, the hash is calculated, copied to the given buffer, and the operation is cleaned up and finished. After this, the session can start another operation. If the above error was returned, the operation is not finished, and initializing a new operation would result in an error.

So now we have the digest. But if we want to do any signing, we are going to need a specific key, so let's make a function to get one.

CK_OBJECT_HANDLE
get_private_key(CK_SESSION_HANDLE session, CK_BYTE *id, size_t id_len)
{
     CK_RV rv;
     CK_OBJECT_CLASS keyClass = CKO_PRIVATE_KEY;
     CK_ATTRIBUTE template[] = {
          { CKA_CLASS, &keyClass, sizeof(keyClass) },
          { CKA_ID, id, id_len }
     };
     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");

     if (objectCount > 0) {
          rv = C_FindObjectsFinal(session);
          check_return_value(rv, "Find objects final");
          return object;
     } else {
          fprintf(stderr, "Private key not found\n");
          exit(2);
     }
}

This function is a lot like the one we used in example 1. It initializes a search, and the gets the first object found. The main difference is that we specify more in the filter template. We add a specific key ID. Of course, you could also choose other parameters, like a CKA_LABEL. Don't forget to clean up by calling C_FindObjectsFinal.

While we're at it, we also make a function to get a public key, with which we can verify a signature.

CK_OBJECT_HANDLE
get_public_key(CK_SESSION_HANDLE session, CK_BYTE *id, size_t id_len)
{
     CK_RV rv;
     CK_OBJECT_CLASS keyClass = CKO_PUBLIC_KEY;
     CK_ATTRIBUTE template[] = {
          { CKA_CLASS, &keyClass, sizeof(keyClass) },
          { CKA_ID, id, id_len }
     };
     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");

     if (objectCount > 0) {
          rv = C_FindObjectsFinal(session);
          check_return_value(rv, "Find objects final");
          return object;
     } else {
          fprintf(stderr, "Public key not found\n");
          exit(3);
     }
}

This function is exactly the same as the previous one, but for one detail; the CKA_CLASS value. This is a good hint that you might be better of specifying this through a function argument, but this makes it more clear for now.

So when we can retrieve our keys, we can sign data.

void
sign_data(CK_SESSION_HANDLE session, FILE *data_file, FILE *signature_file)
{
     CK_RV rv;
     CK_BYTE id[] = { 0x45 };
     CK_OBJECT_HANDLE key = get_private_key(session, id, 1);
     CK_MECHANISM sign_mechanism;

     CK_ULONG digestLen = 20;
     CK_BYTE *digest = malloc(digestLen);

     CK_ULONG signatureLen = 512;
     CK_BYTE *signature = malloc(signatureLen);

     sign_mechanism.mechanism = CKM_RSA_PKCS;
     sign_mechanism.pParameter = NULL;
     sign_mechanism.ulParameterLen = 0;

     rv = digest_data(session, data_file, digest, &digestLen);
     check_return_value(rv, "digest data");

     rv = C_SignInit(session, &sign_mechanism, key);
     check_return_value(rv, "sign init new");

     rv = C_Sign(session, digest, digestLen, signature, &signatureLen);
     check_return_value(rv, "sign final");

     if (signatureLen > 0) {
          fwrite(signature, signatureLen, 1, signature_file);
     }

     free(digest);
     free(signature);
}

As you can see, first we get a digest and initialize a signing operation. At the end we get the signature by calling C_SignFinal. If you have do not have a fixed amount of data to sign, you can also replace C_Sign by various calls of the function C_SignUpdate, followed by C_SignFinal. Both C_Sign and C_SignFinal end the signing operation initialized by C_SignInit. In our case, however, we have already performed a digest operation, and so our data will always have a fixed length.

We allocate 512 bytes of data for the signature. This is enough to store a signature made with an 4096-bit RSA key. If the key that is used is smaller, the value of signatureLen will be changed in the call to C_Sign.

In order for the library and device to know exactly what they have to do, we need to specify a mechanism again. In this case we choose CKM_RSA_PKCS. The resulting signature size depends on the size of the modulus of the RSA key used.

A full list of possible mechanisms can be found in section 12 of the specification. Of course, your device needs to support the mechanism chosen, or you'll get an error code CKR_MECHANISM_INVALID.

If the signing operation succeeds, we write it to the output_file file pointer.

We have hardcoded the key ID here. Of course you'll want to change that to your specific key ID. If you used the key generation function in example 2, you'll need to set it to 0xa1. Better yet would be to let the user specify the key.

For the sake of symmetry, we'll just go ahead and create a function to verify a signature too:

void
verify_data(CK_SESSION_HANDLE session, FILE *data_file, FILE *signature_file)
{
     CK_RV rv;
     CK_BYTE id[] = { 0x45 };
     CK_OBJECT_HANDLE key = get_public_key(session, id, 1);
     CK_MECHANISM sign_mechanism;

     CK_ULONG digestLen = 20;
     CK_BYTE *digest = malloc(digestLen);

     CK_ULONG signatureLen = 512;
     CK_BYTE *signature = malloc(signatureLen);

     signatureLen = fread(signature, 1, signatureLen, signature_file);

     sign_mechanism.mechanism = CKM_RSA_PKCS;
     sign_mechanism.pParameter = NULL;
     sign_mechanism.ulParameterLen = 0;

     rv = digest_data(session, data_file, digest, &digestLen);
     check_return_value(rv, "digest data");

     rv = C_VerifyInit(session, &sign_mechanism, key);
     check_return_value(rv, "verify init");

     rv = C_Verify(session,
                   digest,
                   digestLen,
                   signature,
                   signatureLen
                   );
     check_return_value(rv, "verify");

     printf("The signature is valid\n");

     free(digest);
     free(signature);
}

Again, we hardcode the key ID. And since we know it's modulus size, we also hardcode the signature length. Again we reserve 512 bytes for the signature, which is enough for a signature made with a 4096-bit RSA key.

The method to verify a signature looks quite a bit like the method to create one; We initialize the verification procedure, create a digest from the input data, and check the result by calling C_Verify with the signature we read.

If the signature is valid, C_Verify will just return CKR_OK. If it is invalid, it will either return CKR_SIGNATURE_INVALID or CKR_SIGNATURE_LEN_RANGE. That last error code is returned when the signature is obviously invalid because it has the wrong length.

Again, a setup with C_VerifyUpdate and C_VerifyFinal is also possible instead of just C_Verify. Both C_VerifyFinal and C_Verify end the current operation.

Because we do need a bit more input here than in the first two examples, we expand on the main function a bit:

int
main(int argc, char **argv)
{
     CK_SLOT_ID slot;
     CK_SESSION_HANDLE session;
     FILE *input_file = NULL;
     FILE *output_file = NULL;
     CK_BYTE *user_pin = NULL;

     if (argc < 3) {
          printf("Usage: pkcs11_example3 <input file> <output file> <pin>\n");
          exit(0);
     }
     if (argc > 3) {
          user_pin = (CK_BYTE *) argv[3];
     }

     initialize();
     slot = get_slot();

     /* signing */
     input_file = fopen(argv[1], "r");
     output_file = fopen(argv[2], "w");
     session = start_session(slot);
     if (user_pin) {
          login(session, user_pin);
     }
     sign_data(session, input_file, output_file);
     if (user_pin) {
          logout(session);
     }
     end_session(session);
     fclose(input_file);
     fclose(output_file);

     /* verification */
     input_file = fopen(argv[1], "r");
     output_file = fopen(argv[2], "r");
     session = start_session(slot);
     if (user_pin) {
          login(session, user_pin);
     }
     verify_data(session, input_file, output_file);
     if (user_pin) {
          logout(session);
     }
     end_session(session);
     fclose(input_file);
     fclose(output_file);

     finalize();

     return EXIT_SUCCESS;
}

Here, we actually perform two operations, which should be completely separate; first we open the two files for signing the first, and then we open them again to verify the signature we've just created. Feel free to comment out either one, or split them up into separate functions. There's no error checking on the file operations, which should obviously be present in a robust application.

One additional thing; some tokens or drivers cannot handle multiple operations within the same session well. Even though the operation has been finalized, it will not start a new one. Some tokens might be silent and stop the first operation if you initialize a second within the same session, but others might return an error even if you did finalize it. Therefore, in this example we just start a completely new session.

That's it!

Please start skimming over the PKCS #11 specification again. Hopefully you can understand it better, now that you have seen some actual code in action.


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