Talk: Crypto Kernel subsystem

(1) Using cryptography from userspace

The same crypto subsystem we presented being used in kernel space, can also be used in user space. However, the API used doesn't follow the same pattern, since it makes use of the socket() systemcall API, with its other companions systemcalls, i.e. bind(), accept() and setsockopt().

The idea to enable access from userspace was first created to allow users to have some level of cryptography without any external library dependency and also to ease the use of separate hardwares such as crypto accelerators that are not commonly or easily available in userspace traditional libraries.

The Socket API is well known among a lot of applications and developers, since this is also the interface used to connect a program to the internet. As usual for the Socket API, the headers we need are:

#include <sys/socket.h>
#include <linux/if_alg.h>
#include <linux/socket.h>

And differently from the Crypto API inside the kernel, we don't need to be aware of how the data will be stored in the memory (like using scatterlists).

(2) Checking for the existence of the required algo

In this example we show how simple it is to perform a hash operation over a common user input data.

The first thing we need to do is to allocate a struct sockaddr_alg structure, which contains the basic information about the algorithm we want to use: the address family of the socket, the type of the algorithm and also the precise algorithm name:

struct sockaddr_alg sa_alg = {
    .salg_family = AF_ALG,
    .salg_type = "hash",
    .salg_name = "sha256"
};

Note

When handling a socket connection we use the term address family to specify to which family of protocols our socket belongs to. In TCP/IP case, for instance, the family is known as AF_INET, while for crypto we use the AF_ALG family.

Now, we can make sure we have this algorithm available to us during a direct call to socket():

int sock_fd;
int err;

sock_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
if (sock_fd < 0) {
    perror("failed to allocate socket\n");
    return -1;
}

err = bind(sock_fd, (struct sockaddr *)&sa_alg, sizeof(sa_alg));
if (err) {
    perror("failed to bind socket, alg may not be supported\n");
    return -EAFNOSUPPORT;
}

Note

Another interesting note is that for crypto we only use SOCK_SEQPACKET, since we don't allow data fragmentation and we always limit the size of the data buffer being transmitted to the maximum value.

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

In the case of a hash algorithm we don't really have much to add about additional parameters, but if we were presenting a cipher algorithm we would need to set, for instance, the cryptographic key via the setsockopt() systemcall:

char key[16] = {0};

setsockopt(sock_fd, SOL_ALG, ALG_SET_KEY, key, AES_KEY_LEN);

But considering that's not our case here, let's move forward.

(4) Performing the hash

First we make the socket ready for accepting data:

int fd;

fd = accept(sock_fd, NULL, 0);

In the case of a hash algorithm we only need to perform two simple calls now: one to write() our plaintext to the file descriptor we just got from accept() and then read() from the exact same file descriptor to retrieve the final sha256 digest:

write(fd, plaintext, text_len);
read(fd, digest, SHA256_DIG_LEN);

Complete code

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

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <linux/if_alg.h>
#include <linux/socket.h>

/* Some old versions of glibc doesn't have it set yet */
#ifndef AF_ALG
#define AF_ALG 38
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif

#define SHA256_DIG_LEN 32

int main(int argc, char *argv[])
{
    char *plaintext;
    int sock_fd, fd, text_len;
    unsigned char digest[SHA256_DIG_LEN];
    int err, i;

    /* Different from what we use in normal TCP/IP socket programming,
     * that fills a sockaddr_in structure, here we work over a
     * sockaddr_alg one */
    struct sockaddr_alg sa_alg = {
        .salg_family = AF_ALG,
        .salg_type = "hash",
        .salg_name = "sha256"
    };

    /* Get input from user */
    if (argc > 1) {
        plaintext = argv[1];
    } else {
        plaintext = strndup("Hello World", 11);
        if (!plaintext) {
            fprintf(stderr, "not enough memory\n");
            return -ENOMEM;
        }
    }

    /* AF_ALG is the address family we use to interact with Kernel
     * Crypto API. SOCK_SEQPACKET is used because we always know the
     * maximum size of our data (no fragmentation) and we care about
     * getting things in order in case there are consecutive calls */
    sock_fd = socket(AF_ALG, SOCK_SEQPACKET, 0);
    if (sock_fd < 0) {
        perror("failed to allocate socket\n");
        return -1;
    }

    err = bind(sock_fd, (struct sockaddr *)&sa_alg, sizeof(sa_alg));
    if (err) {
        perror("failed to bind socket, alg may not be supported\n");
        return -EAFNOSUPPORT;
    }

    /* Once it's "configured", we tell the kernel to get ready for
     * receiving some requests */
    fd = accept(sock_fd, NULL, 0);
    if (fd < 0) {
        perror("failed to open connection for the socket\n");
        return -EBADF;
    }

    /* In hash cases, we don't really need to inform anything else, we
     * can start sending data to the fd and read back from it to get our
     * digest. OTOH, when working with ciphers, we need to perform some
     * operations via setsockopt() interface, using the specifics
     * options, like ALG_SET_KEY */
    text_len = strlen(plaintext);
    err = write(fd, plaintext, text_len);
    if (err != text_len) {
        perror("something went wrong while writing data to fd\n");
        return -1;
    }
    read(fd, digest, SHA256_DIG_LEN);

    close(fd);
    close(sock_fd);

    /* Print digest to output */
    for (i = 0; i < SHA256_DIG_LEN; i++)
        printf("%02x", digest[i]);
    printf("\n");

    return 0;
}

Finally, to check that it works (consider the above program was named hash):

$ echo -n "aloha" | sha256sum
0206a97843b1ba4fbb147d472550ec3b5ee8aacadf3707522157240940d1bebd -
$ ./hash aloha
0206a97843b1ba4fbb147d472550ec3b5ee8aacadf3707522157240940d1bebd

Further material

You can find more documentation directly in the kernel's documentation folder, in the file Documentation/crypto/userspace-if.rst.

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