Talk: Crypto Kernel subsystem

(1) Using cryptography from inside a kernel module

Using the knowledge learned from the device drivers to create a basic device driver we can extend that by also enabling the use of a cryptographic algorithm from within our module.

To do so, we first need to decide which algorithm we're going to use. In this example the salsa20 algorithm (a symmetric block cipher) was chosen. With that in mind, we then need to make sure we have all skcipher structures and operations available to ourselves, since skcipher wraps any symmetric algorithm API (regardless of it being stream or block cipher). Thus we're going to need two headers:

/* Skcipher kernel crypto API */
#include <crypto/skcipher.h>
/* Scatterlist manipulation */
#include <linux/scatterlist.h>

Scatterlist is a special type of buffer used by the crypto subsystem to keep the data, being encrypted/decrypted, in an efficient organization directly in the physical memory, improving the storage and performance for each crypto operation.

(2) Checking for the existence of the required algo

Now we need to make sure we have the algorithm we want to use available in the current kernel configuration:

if (!crypto_has_skcipher("salsa20", 0, 0)) {
    pr_err("skcipher not found\n");
    return -EINVAL;
}

It's important to check because some algorithms are not built-in in the kernel image, but rather are loadable modules that the user must first load prior to using the above code.

(3) Setting the parameters for the algo

Each algorithm requires a different set of parameters to be initialized by the API customer (some share the very same parameters though). In other words, the API doesn't really help the user informing what the parameters should be, so it must be known beforehand, since the input and output buffers may vary in size and even might require another specific buffer for other data.

In our case, the algorithm used, salsa20, requires a 16 bytes initialization vector to be used alongside the input buffer. Thus, for this specific case we need the following buffers:

char plaintext[16] = {0};
char ciphertext[16] = {0};
/* We're going to use a zerod 128 bits key */
char key[16] = {0};

/* Initialization Vector */
char *iv;
size_t ivsize;

ivsize = crypto_skcipher_ivsize(tfm);
iv = kmalloc(ivsize, GFP_KERNEL);

Warning

Never use a zero'ed buffer for neither the key and IV buffers when performing any crypto operation. These values should be filled with random data, also gathered from the crypto subsystem, but using the RNG API instead of skcipher. In this example we're using a zero'ed key just to simplify things, while the IV will use the content of a dynamically allocated memory.

(4) Initializing the main crypto structures

With all basic data already allocated we can move forward and effectively prepare the data internally used by the crypto subsystem to perform the operations:

  • set the cryptographic key
  • encrypt our plain text
  • decrypt the cipher text
struct crypto_skcipher *tfm;
struct skcipher_request *req;
struct scatterlist sg;

tfm = crypto_alloc_skcipher("salsa20", 0, 0);
crypto_skcipher_setkey(tfm, key, sizeof(key));
req = skcipher_request_alloc(tfm, GFP_KERNEL);

memcpy(plaintext, "aloha", 6);
sg_init_one(&sg, plaintext, 16);
skcipher_request_set_crypt(req, &sg, &sg, 16, iv);

The req structure represents a request inside the crypto stack, which could be seen as the context of what we're requesting. It holds the algorithm information, the buffers to be encrypted and decrypted, the callback functions (when allocating an asynchronous request, which isn't presented in this post), and so forth.

(5) Performing encryption and decryption

Now we have everything we need to finally perform some crypto operation.

To start with, encryption:

crypto_skcipher_encrypt(req);
sg_copy_to_buffer(&sg, 1, ciphertext, 16);

The plaintext to be encrypted was previously filled and added to the req structure. After the encryption is done the result is placed in the scatterlist sg, which has its content copied to ciphertext using the sg_copy_to_buffer() function and which can be later printed to check the encrypted data.

For decryption the steps are the same, we just need to remember to reset our sg if we have used it before (in the encryption step for instance):

memset(plaintext, 0, 16);
sg_init_one(&sg, ciphertext, 16);

crypto_skcipher_decrypt(req);
sg_copy_to_buffer(&sg, 1, plaintext, 16);

Dumping all data we have we're going to see something like this:

key: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
iv: fd 73 c3 b1 66 ca fd 15
orig text: 61 6c 6f 68 61 00 00 00 00 00 00 00 00 00 00 00  aloha...........
encr text: a3 99 55 36 f7 68 20 0a e9 f5 ce dd e2 a6 b7 de  ..U6.h .........
decr text: 61 6c 6f 68 61 00 00 00 00 00 00 00 00 00 00 00  aloha...........

Complete code

That's an example of a module code that you can try:

/* __init/exit, macros (MODULE_*) that initializes the module itself */
#include <linux/module.h>
/* Printing function definitions */
#include <linux/kernel.h>
/* Skcipher kernel crypto API */
#include <crypto/skcipher.h>
/* Scatterlist manipulation */
#include <linux/scatterlist.h>
/* Error macros */
#include <linux/err.h>

static int __init crypto_sync_init(void)
{
    int err;

    struct crypto_skcipher *tfm;
    struct skcipher_request *req;
    struct scatterlist sg;

    char plaintext[16] = {0};
    char ciphertext[16] = {0};
    /* We're going to use a zerod 128 bits key */
    char key[16] = {0};

    /* Initialization Vector */
    char *iv;
    size_t ivsize;

    pr_dbg("initializing module\n");

    /* Check the existence of the cipher in the kernel (it might be a
     * module and it isn't loaded. */
    if (!crypto_has_skcipher("salsa20", 0, 0)) {
        pr_err("skcipher not found\n");
        return -EINVAL;
    }

    /* Allocate synchronous cipher handler.
     *
     * For generic implementation you can provide either the generic name
     * "salsa20" or the driver (specific) name "salsa20-generic", since
     * the generic has higher priority compared to the x86_64 instruction
     * implementation "salsa20-asm".
     *
     * Also, cypher type will be left 0 since there isn't any other type
     * other than the default one for this cypher and the mask also will
     * be 0 since I don't want to use the asynchronous interface variant.
     */
    tfm = crypto_alloc_skcipher("salsa20", 0, 0);
    if (IS_ERR(tfm)) {
        pr_err("impossible to allocate skcipher\n");
        return PTR_ERR(tfm);
    }

    /* Default function to set the key for the symetric key cipher */
    err = crypto_skcipher_setkey(tfm, key, sizeof(key));
    if (err) {
        pr_err("fail setting key for transformation: %d\n", err);
        goto error0;
    }
    print_hex_dump(KERN_DEBUG, "key: ", DUMP_PREFIX_NONE, 16, 1, key, 16,
               false);

    /* Each crypto cipher has its own Initialization Vector (IV) size,
     * because of that I first request the correct size for salsa20 IV and
     * then set it. Considering this is just an example I'll use as IV the
     * content of a random memory space which I just allocated. */
    ivsize = crypto_skcipher_ivsize(tfm);
    iv = kmalloc(ivsize, GFP_KERNEL);
    if (!iv) {
        pr_err("could not allocate iv vector\n");
        err = -ENOMEM;
        goto error0;
    }
    print_hex_dump(KERN_DEBUG, "iv: ", DUMP_PREFIX_NONE, 16, 1, iv,
               ivsize, false);

    /* Requests are objects that hold all information about a crypto
     * operation, from the tfm itself to the buffers and IV that will be
     * used in the enc/decryption operations. But it also holds
     * information about asynchronous calls to the crypto engine. If we
     * have chosen async calls instead of sync ones, we should also set
     * the callback function and some other flags in the request object in
     * order to be able to receive the output date from each operation
     * finished. */
    req = skcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
        pr_err("impossible to allocate skcipher request\n");
        err = -ENOMEM;
        goto error0;
    }

    /* The word to be encrypted */
    /* TODO: explain scatter/gather lists, that has relation to DMA */
    memcpy(plaintext, "aloha", 6);
    sg_init_one(&sg, plaintext, 16);
    skcipher_request_set_crypt(req, &sg, &sg, 16, iv);

    print_hex_dump(KERN_DEBUG, "orig text: ", DUMP_PREFIX_NONE, 16, 1,
               plaintext, 16, true);

    /* Encrypt operation against "plaintext" content */
    err = crypto_skcipher_encrypt(req);
    if (err) {
        pr_err("could not encrypt data\n");
        goto error1;
    }

    sg_copy_to_buffer(&sg, 1, ciphertext, 16);
    print_hex_dump(KERN_DEBUG, "encr text: ", DUMP_PREFIX_NONE, 16, 1,
               ciphertext, 16, true);

    /* Time to decrypt */
    memset(plaintext, 0, 16);
    sg_init_one(&sg, ciphertext, 16);

    /* Decrypt operation against the new buffer (scatterlist that holds
     * the ciphered text). */
    err = crypto_skcipher_decrypt(req);
    if (err) {
        pr_err("could not decrypt data\n");
        goto error1;
    }

    sg_copy_to_buffer(&sg, 1, plaintext, 16);
    print_hex_dump(KERN_DEBUG, "decr text: ", DUMP_PREFIX_NONE, 16, 1,
               plaintext, 16, true);
error1:
    skcipher_request_free(req);
error0:
    crypto_free_skcipher(tfm);
    return err;
}

static void __exit crypto_sync_exit(void)
{
    PR_DEBUG("exiting module\n");
}

module_init(crypto_sync_init);
module_exit(crypto_sync_exit);

Further material

You can find more documentation directly in the kernel's documentation folder (Documentation/crypto/) and, surprisingly enough, the code for this particular subsystem is pretty well documented. Don't hesitate to check the headers under include/crypto/.

Another document is the slide deck presented in one of the LKCAMP meetings, which should be available in the presentations bin repository.