BooAA
BooAA

Reputation: 183

How to use sync skcipher in linux kernel?

I want to use sync encryption in linux kernel (since the code is run in interrupt context, which cannot sleep). Under /proc/crypto, there are several candidates which is marked sync, like __gcm(aes), __ctr(aes), __xts(aes). I tried to use this code example in kernel crypto API documentation, but get error when trying to allocate tfm using crypto_alloc_req. The code is as below:

static int test_skcipher(void)
{
    struct crypto_skcipher *tfm = NULL;
    struct skcipher_request *req = NULL;
    u8 *data = NULL;
    const size_t datasize = 512; /* data size in bytes */
    struct scatterlist sg;
    DECLARE_CRYPTO_WAIT(wait);
    u8 iv[16];  /* AES-256-XTS takes a 16-byte IV */
    u8 key[64]; /* AES-256-XTS takes a 64-byte key */
    int err;

    /*
     * Allocate a tfm (a transformation object) and set the key.
     *
     * In real-world use, a tfm and key are typically used for many
     * encryption/decryption operations.  But in this example, we'll just do a
     * single encryption operation with it (which is not very efficient).
     */

    tfm = crypto_alloc_skcipher("__xts(aes)", 0, 0);
    if (IS_ERR(tfm)) {
            pr_err("Error allocating xts(aes) handle: %ld\n", PTR_ERR(tfm));
            return PTR_ERR(tfm);
    }

    get_random_bytes(key, sizeof(key));
    err = crypto_skcipher_setkey(tfm, key, sizeof(key));
    if (err) {
            pr_err("Error setting key: %d\n", err);
            goto out;
    }

    /* Allocate a request object */
    req = skcipher_request_alloc(tfm, GFP_KERNEL);
    if (!req) {
            err = -ENOMEM;
            goto out;
    }

    /* Prepare the input data */
    data = kmalloc(datasize, GFP_KERNEL);
    if (!data) {
            err = -ENOMEM;
            goto out;
    }
    get_random_bytes(data, datasize);

    /* Initialize the IV */
    get_random_bytes(iv, sizeof(iv));

    /*
     * Encrypt the data in-place.
     *
     * For simplicity, in this example we wait for the request to complete
     * before proceeding, even if the underlying implementation is asynchronous.
     *
     * To decrypt instead of encrypt, just change crypto_skcipher_encrypt() to
     * crypto_skcipher_decrypt().
     */
    sg_init_one(&sg, data, datasize);
    skcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
                                       CRYPTO_TFM_REQ_MAY_SLEEP,
                                  crypto_req_done, &wait);
    skcipher_request_set_crypt(req, &sg, &sg, datasize, iv);
    err = crypto_wait_req(crypto_skcipher_encrypt(req), &wait);
    if (err) {
            pr_err("Error encrypting data: %d\n", err);
            goto out;
    }

    pr_debug("Encryption was successful\n");
out:
    crypto_free_skcipher(tfm);
    skcipher_request_free(req);
    kfree(data);
    return err;
}

This code is basically the same as the document, only replacing xts(aes) with __xts(aes). I have already checked that __xts(aes) appears in /proc/crypto, but it seems kernel cannot find the corresponding algorithm. Is there any other documentation about how to use sync skcipher inside linux kernel? A working code example will be appreciated.

Upvotes: 0

Views: 1049

Answers (1)

r s
r s

Reputation: 11

Today I have spent a lot of time on the same issue and during my "No-Straightforward-Documentation-Blues". I found your almost-the-same story here and without any reply (omg....). Thus, if you are still facing this problem, I hope it will be helpful.

GCM is about an AEAD cipher operation mode and this operation mode also reflects into linux/crypto. The transformations done by using skcipher is not suitable for GCM. AEAD does much more than just encrypt/decrypt data. It is capable of authenticating. So skcipher cannot "see" ciphers working under GCM mode, because it does not know how to handle them. So if you try to get a transformation handle for GCM by using skcipher, it will say "gc what?! gc who?!!". It is clearer now, because I also was considering that skcipher was about a meta data transformation stuff, but it is not. There is another "tfm" (poorly documented, btw) that can attend us.

Linux crypto api offers struct crypto_aead. The crypto_aead has all its correspondent convenience functions that skcipher has. In general it is almost the "same". However, it is necessary taking into consideration the particularities of GCM and how it is organized into crypto_aead stuff. Anyway, it is not so arcane (if you already know some aspects of the concepts behind of AEAD/GCM and I am taking into consideration that you know). Take a look:

int do_aes_gcm_min_sample(void) {
    struct crypto_aead *tfm = NULL;
    struct aead_request *req = NULL;
    u8 *buffer = NULL;
    size_t buffer_size = TEST_DATA_SIZE;
    u8 *bp = NULL, *bp_end = NULL;
    struct scatterlist sg = { 0 };
    DECLARE_CRYPTO_WAIT(wait);
    // INFO(Rafael): The majority of AES/GCM implementation uses 12 bytes iv (crypto_aead_ivsize()
    //               returned this, so for this reason I am using this "magic" value here.)
    u8 iv[12] = { 0 };
    u8 key[32] = { 0 }; // INFO(Rafael): The version of AES is defined by the size (in bytes) of the
                        //               informed key. So, here we are using AES-256.
    int err = -1;

    tfm = crypto_alloc_aead("gcm(aes)", 0, 0);

    if (IS_ERR(tfm)) {
        err = PTR_ERR(tfm);
        pr_err("AES/GCM min sample: crypto_alloc_aead() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): Telling to api how many bytes will compound our MAC (a.k.a tag etc [etc no!...]).
    err = crypto_aead_setauthsize(tfm, AES_GCM_TAG_SIZE);
    if (err != 0) {
        pr_err("AES/GCM min sample: crypto_aead_setauthsize() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    // WARN(Rafael): In practice it could come from a `KDF(weak_usr_password)` stuff.
    //               Never ever use directly the "key" informed by the user.
    //               It demolishes the good will of any crypto algorithm on
    //               doing good encryption. Let's value hard work of cryptographers on
    //               seeking to create state of the art ciphers ;), please!
    //               So, I am only getting the key from a csprng that is a thing
    //               near to what an alleged good KDF is able to do with a weak password
    //               or at least must do.
    get_random_bytes(key, sizeof(key));
    err = crypto_aead_setkey(tfm, key, sizeof(key));
    if (err != 0) {
        pr_err("AES/GCM min sample: crypto_aead_setkey() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    req = aead_request_alloc(tfm, GFP_KERNEL);
    if (req == NULL) {
        err = -ENOMEM;
        pr_err("AES/GCM min sample: aead_request_alloc() has failed.\n");
        goto do_aes_gcm_min_sample_epilogue;
    }
    req->assoclen = 0; // INFO(Rafael): No associated data, just reinforcing it.
                       //               Anyway, when you want to also authenticated
                       //               plain data (a.k.a AAD, associated data) you
                       //               must indicate the size in bytes of the
                       //               aad here and prepend your plaintext with
                       //               aad.

    get_random_bytes(iv, sizeof(iv));

    // INFO(Rafael): The AES/GCM encryption primitive will also spit at the end of
    //               the encrypted buffer the n bytes asked tags generated by GHASH.
    //               Since we are using the same buffer for output stuff, this buffer
    //               must be able to fit the input and ***also*** the result.
    buffer_size = TEST_DATA_SIZE + AES_GCM_TAG_SIZE;
    buffer = kmalloc(buffer_size, GFP_KERNEL);
    if (buffer == NULL) {
        err = -ENOMEM;
        pr_err("AES/GCM min sample: kmalloc() has failed.\n");
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): Copying the one block input...
    memcpy(buffer, TEST_DATA, TEST_DATA_SIZE);
    bp = buffer;
    bp_end = bp + TEST_DATA_SIZE;

    // INFO(Rafael): Currently buffer contains only the one dummy test block. Right?...
    pr_info("Original data: ");
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }

    // INFO(Rafael): ...however our scattterlist must be initialised
    //               by indicating the whole allocated buffer segment (including room
    //               for the tag). Because it will also output data, got it?
    sg_init_one(&sg, buffer, buffer_size);
    aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
                                   CRYPTO_TFM_REQ_MAY_SLEEP, crypto_req_done, &wait);

    // INFO(Rafael): Thus, for ***encrypting*** our input buffer is
    //                      `TEST_DATA_SIZE == buffer_size - AES_GCM_TAG_SIZE`,
    //               since
    //                      `buffer_size == TEST_DATA_SIZE + AES_GCM_TAG_SIZE`.
    aead_request_set_crypt(req, &sg, &sg, buffer_size - AES_GCM_TAG_SIZE, iv);
    err = crypto_wait_req(crypto_aead_encrypt(req), &wait);
    if (err != 0) {
        pr_err("AES/GCM min sample: error when encrypting data: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): If aad would be also passed it would prepend the cryptogram.
    //               req-assoclen give you the clue of traverse or even skip it.

    pr_info("Cryptogram: ");
    // INFO(Rafael): Now buffer contains the authenticated cryptogram. I meant <cryptogram><MAC>.
    //               Here the intention is only print the cryptogram.
    bp = buffer;
    bp_end = bp + buffer_size - AES_GCM_TAG_SIZE;
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }

    pr_info("Authentication tag: ");
    // INFO(Rafael): Since bp is already pointing to the first byte of what should be the tag, let's only moving
    //               AES_GCM_TAG_SIZE bytes ahead the end marker of the output buffer.
    bp_end += AES_GCM_TAG_SIZE;
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }

    // INFO(Rafael): I hate incomplete samples, so let's decrypt, too.
    //               Decrypting with GCM involves checking if the tag informed at the end of cryptogram,
    //               is really the same of the on-the-fly calculated by GHASH. Thus, when decrypting the
    //               is necesary to indicate the cryptogram and ***also*** the tag, so here its size is
    //               expressed by buffer_size.
    aead_request_set_crypt(req, &sg, &sg, buffer_size, iv);


    // INFO(Rafael): What about testing if GCM is really detecting tampered data?
    //               Give it a try by uncomment all or even one of the following three lines.
    //key[sizeof(key) >> 1] += 1;
    //buffer[buffer_size >> 1] += 1;
    //buffer[buffer_size - AES_GCM_TAG_SIZE + 1] += 1; // INFO(Rafael): Bit flipping MAC.

    // INFO(Rafael): For the context of this sample, it would not be necessary. Anyway, we want to test
    //               corrupted key cases.
    err = crypto_aead_setkey(tfm, key, sizeof(key));
    if (err != 0) {
        pr_err("AES/GCM min sample: crypto_aead_setkey() has failed: %d.\n", err);
        goto do_aes_gcm_min_sample_epilogue;
    }

    err = crypto_wait_req(crypto_aead_decrypt(req), &wait);
    if (err != 0) {
        pr_err("AES/GCM min sample: Error when decrypting data, it seems tampered. "
               "Ask for a retransmission or verify your key.\n");
        goto do_aes_gcm_min_sample_epilogue;
    }

    // INFO(Rafael): If aad would be also passed it would prepend the plaintext.
    //               req->assoclen give you the clues of how to traverse or even
    //               skipping it. But even skipped it must be passed by the
    //               decryption routine. Because it also authenticates the whole
    //               buffer, got it?

    pr_info("Authenticated plaintext: ");
    bp = buffer;
    bp_end = bp + buffer_size - AES_GCM_TAG_SIZE; // INFO(Rafael): It will not reallocate the buffer so, let's exclude the MAC.
                                                  //               Due to it maybe should be good to ensure a buffer_size multiple of four.
                                                  //               It would keep this simpler. Anyway you can apply a more sophisticated
                                                  //               padding technique, but for this sample I think it express the main idea.
    while (bp != bp_end) {
        pr_info("%c\n", isprint(*bp) ? *bp : '.');
        bp++;
    }


do_aes_gcm_min_sample_epilogue:

    if (req != NULL) {
        aead_request_free(req);
    }

    if (tfm != NULL) {
        crypto_free_aead(tfm);
    }

    if (buffer != NULL) {
        kfree(buffer);
    }

    return err;
}

If you want to test quickly, you can clone this sample repo here.

I also have tried to run this code on kernel 4.4.14 (it has needed some minor workarounds, it seems fine, but I did not tested it a lot). For the sake of brevity I will not comment what I done but if you are interested feel free on cloning the repo and grasp into those "hacks". But the code is only a sample, much more must be done to deliver a good crypto there, avoid copying and pasting, please.

Upvotes: 1

Related Questions