The Linux cryptographic API was introduced from version 2.5.45 of the kernel. Since then, the Crypto API has been overgrown with all popular (and not only) international standards:
This cryptography is available and is mainly used by various kernel subsystems (in the kernel space ): IPsec , dm-crypt , etc. It is also possible to use the Crypto API functions from user space ( user space ) through a Netlink interface, where, starting with 2.6.38 kernel, the _AF ALG family is introduced, which provides access to the kernel cryptography from the user space of the code. However, the existing functionality is sometimes not enough, thus there is a need to extend the Crypto API with new algorithms.
So, about two years ago, I was faced with the task of embedding national (in the Republic of Belarus) cryptography into the IPsec implementation of the Linux kernel (version 3.13). At that time, I never did programming in the kernel space, and this was my first experience in writing kernel modules, with which Robert Love 's book Linux Kernel: Description of the Development Process helped me a lot. It was much more difficult to deal with the Crypto API itself - in the mentioned book this question is not covered in principle, and in the official documentation , although a rather detailed description of the architecture is given, there is almost no information about the implementation of new algorithms. In my search, in the Hacker magazine, I came across a wonderful article , popularly telling how to use the Crypto API , but, again, there is not a word about its extension. In the end, I turned to the original source (kernel code) for the knowledge I needed, and decided to solve the problem.
Some time has passed since then, and only now, having actualized my knowledge, I took up writing this text. It is about the crypto api . With specific examples, we will look at how arbitrary algorithms can be built into the Crypto API and how to access them from both the kernel space and the user space . I am sure that this material will be extremely useful for developers who are faced with similar problems, but I hope that the casual reader can find here something interesting for himself. Welcome under the cut!
PS The compiled source codes of my programs, fragments of which will appear below, are available on github . There is also the source of this article. All code was written and tested under desktop Ubuntu 16.04 LTS with kernel version 4.13.0-32-generic. Some of the interfaces that I used in the modules were introduced in 4 versions of the kernel, therefore on kernels 3. * they are NOT compiled for sure.
Before we start writing the code, let's talk a little about how things work in the Crypto API . However, I was told about this in detail in the documentation , but I, in turn, will cite only a squeeze, which should be enough to understand the material. Immediately make a reservation, then we will discuss mainly symmetric encryption, other types of crypto-transformations, in general, work in a similar way, but the numerous nuances would greatly complicate everything.
So, from the introduction, we already know that the cryptographic subsystem of the kernel includes many implementations of cryptographic algorithms and provides the interfaces accessible to the user (hereinafter, the user and developer mean, respectively, the API user and the API developer ). In terms of the Crypto API , the algorithms are called "transformations" ( transformations ), for example, different types of transformations have their own sub- API , and the descriptors (handles) of transformations are usually named " tfm ", for example:
struct crypto_cipher *tfm = crypto_alloc_cipher("aes", 0, 0);
Here tfm is the handle of some transformation, in this case, the encryption of the data block using the AES algorithm. The life cycle of the Crypto API handle is reduced to three stages:
Regardless of the algorithm used, implementations of the basic functions of encrypting a single data block are available through the crypto_cipher
type handle, and the Single Block Cipher API ( API ) provides methods for key installation and block encryption / decryption. As a standalone API, it is not of great interest to the user, however, from the point of view of the developer, it is important, since it uses the key concept of the Crypto API - the so-called "templates". Templates are used in conjunction with the basic functions and implement:
When creating a handle (see example above), the required algorithm is specified by a string β the name of the algorithm; this string has the following semantics:
template(single block cipher/message digest)
In addition, where applicable, the templates can be wrapped in other templates:
template_a(template_b(single block cipher/message digest))
In this case, the use of templates without the basic algorithm is impossible. I will give a few examples:
The use of symmetric block ciphers in any mode is carried out through a separate API . It is worth noting that now there are three such APIs in the core, however two of them are deprecated , and the Symmetric Key Cipher API is relevant. This API provides the user with methods for setting the key / syncing and encrypting / decrypting data of arbitrary length, it is also non-blocking (asynchronous) - encryption methods quickly return control to the caller, while the request to perform crypto operation and the callback function used for notification user about the completion of the operation, passed to the system scheduler. The Symmetric Key Cipher API will be discussed in detail when we proceed to create a kernel module testing our embedded algorithm.
In addition to the two mentioned, in the Crypto API there are sub- APIs for:
For a developer, the presence of templates in the Crypto API means that by embedding a new symmetric encryption algorithm, it suffices to implement only the basic block encryption function, which can then be encapsulated into the corresponding templates to obtain the required modes. But it is not so. Indeed, if you look at the source code of the algorithms included in the core ( crypto / aes_generic.c , crypto / blowfish_generic.c , ...), you can see that only basic functions are implemented there. However, if we in the same way implement the encryption function of the classic GOST 28147-89 block, βwrapβ it in the gamming mode ( CTR template), and then check the resulting algorithm on test sequences, then we will get the wrong result! The thing is that the gammating mode described in GOST differs from the gamming algorithm implemented in the CTR template. The same applies to other national algorithms with which I dealt. In such cases, it is necessary to embed high-grade block ciphers in the desired modes, as is done, for example, in an optimized implementation of the AES algorithm ( AES-NI - arch / x86 / crypto / aesni-intel_glue.c ). Later we will look at both embedding options.
Perhaps this is all I wanted to say about architecture. Those interested in a more detailed description should refer to the documentation , and my presentation should be enough so that we can move on.
So, we are ready to start embedding some new algorithm in the Crpyto API , with the only reservation that we do not yet have an algorithm that could be embedded. For this article, I did not bother to implement the "real" cryptoalgorithm, since I could hardly do it qualitatively (leave it to the cryptographers). On the other hand, it would be not so interesting to make a βdummyβ cipher ( null cipher ), especially since there is already one in the kernel. Thus, we compromise and write the implementation of an elementary encryption algorithm:
Where:
The block and key sizes for this algorithm are assumed to be equal to 128 bits (16 bytes).
We can start implementation. We define the crypto context of our algorithm and the function to create / destroy context:
#define XOR_CIPHER_KEY_SIZE 16 typedef struct xor_cipher_ctx xor_cipher_ctx; struct xor_cipher_ctx { uint8_t key[XOR_CIPHER_KEY_SIZE]; }; xor_cipher_ctx* xor_cipher_allocate() { xor_cipher_ctx *cipher = calloc(1, sizeof(xor_cipher_ctx)); return cipher; } void xor_cipher_free(xor_cipher_ctx *ctx) { memset(ctx->key, 0xFF, XOR_CIPHER_KEY_SIZE); free(ctx); }
Add key installation methods and block encryption / decryption:
#define XOR_CIPHER_BLOCK_SIZE 16 void xor_cipher_set_key(xor_cipher_ctx *ctx, uint8_t *key) { memmove(ctx->m_key, key, XOR_CIPHER_KEY_SIZE); } void xor_cipher_crypt_block(xor_cipher_ctx *ctx, uint8_t *dst, uint8_t *src) { for (int i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { dst[i] = src[i] ^ ctx->key[i]; } }
Given the reversibility of the " XOR " operation, the xor_cipher_crypt_block
method xor_cipher_crypt_block
used to encrypt and decrypt simultaneously.
Block encryption is good, but it will be even better if we implement any of the block encryption modes, for example, the cipher block chaining ( CBC ) mode :
Where:
Let's continue the work, we will implement the methods of encryption and decryption in the mode of coupling blocks of cipher.
void xor_cipher_encrypt_cbc(xor_cipher_ctx *ctx, uint8_t *_dst, uint32_t len, uint8_t *_src, uint8_t *_iv) { uint32_t blocks = len / XOR_CIPHER_BLOCK_SIZE; uint32_t leftover = len - (blocks * XOR_CIPHER_BLOCK_SIZE); uint8_t *dst = _dst, *src = _src, *iv = _iv; for (uint32_t i = 0; i < blocks; i++) { memmove(dst, src, XOR_CIPHER_BLOCK_SIZE); for (int j = 0; j < XOR_CIPHER_BLOCK_SIZE; j++) { dst[j] ^= iv[j]; } xor_cipher_crypt_block(ctx, dst, dst); iv = dst; dst += XOR_CIPHER_BLOCK_SIZE; src += XOR_CIPHER_BLOCK_SIZE; } if (leftover) { memmove(dst, src, leftover); for (uint32_t i = 0; i < leftover; i++) { dst[i] ^= iv[i]; dst[i] ^= ctx->key[i]; } } }
void xor_cipher_decrypt_cbc(xor_cipher_ctx *ctx, uint8_t *_dst, uint32_t len, uint8_t *_src, uint8_t *_iv) { uint32_t blocks = len / XOR_CIPHER_BLOCK_SIZE; uint32_t leftover = len - (blocks * XOR_CIPHER_BLOCK_SIZE); uint8_t u[XOR_CIPHER_BLOCK_SIZE], iv[XOR_CIPHER_IV_SIZE]; uint8_t *dst = _dst, *src = _src; memmove(iv, _iv, XOR_CIPHER_IV_SIZE); for (uint32_t i = 0; i < blocks; i++) { memmove(u, src, XOR_CIPHER_BLOCK_SIZE); xor_cipher_crypt_block(ctx, dst, src); for (int j = 0; j < XOR_CIPHER_BLOCK_SIZE; j++) { dst[j] ^= iv[j]; } memmove(iv, u, XOR_CIPHER_IV_SIZE); dst += XOR_CIPHER_BLOCK_SIZE; src += XOR_CIPHER_BLOCK_SIZE; } if (leftover) { for (uint32_t i = 0; i < leftover; i++) { dst[i] = src[i] ^ ctx->key[i]; dst[i] ^= iv[i]; } } }
Now this is more interesting! Now, using this implementation, we can prepare test sequences, on which, later on, we will check the implementation of the same algorithm in the kernel. Under the spoiler - the values ββthat I used.
Block encryption | |
---|---|
Key | 2f 1b 1a c6 d1 be cb a2 f8 45 66 0d d2 97 5c a3 |
Test number 1 | |
Input data | cc 6b 79 0c db 55 4f e5 a0 69 05 96 11 be 8c 15 |
Output | e3 70 63 ca 0a eb 84 47 58 2c 63 9b c3 29 d0 b6 |
Test number 2 | |
Input data | 53 f5 f1 ef 67 a5 ba 6c 68 09 b5 7a 24 de 82 5f |
Output | 7c ee eb 29 b6 1b 71 ce 90 4c d3 77 f6 49 de fc |
CBC Encryption | |
---|---|
Key | ec 8d 93 30 69 7e f8 63 0b f5 58 ec de 78 24 f2 |
Synchro parcel | db 02 1f a8 5a 22 15 cf 49 f7 80 8b 7c 24 a1 f3 |
Plain text | 6e 96 50 42 84 d2 7e e8 44 9b 75 1d e0 ac 0a 58 ee 40 24 cc 32 fc 6e c4 e2 fc d1 f5 76 6a 45 9a e4 88 ba d6 12 07 28 86 |
Ciphertext | 59 19 dc da b7 8e 93 44 06 99 ad 7a 42 f0 8f 59 5b d4 6b 26 ec 0c 05 e3 ef 90 24 63 ea e2 ee 31 53 d1 42 c0 97 75 d5 06 |
CBC decryption | |
---|---|
Key | ec 8d 93 30 69 7e f8 63 0b f5 58 ec de 78 24 f2 |
Synchro parcel | db 02 1f a8 5a 22 15 cf 49 f7 80 8b 7c 24 a1 f3 |
Ciphertext | db e9 1d c6 1f 13 1a 5a 34 2b 90 1e c3 b1 6f e9 52 1b 91 7f 8d 8f 6d b4 42 87 ad 85 5f 2d 89 7d |
Plain text | ec 66 91 5e 2c 4f f7 f6 76 29 48 79 61 ed ea e8 65 7f 1f 89 fb e2 8f 8d 7d 59 65 77 42 e4 c2 66 |
The sources for the implementation of the algorithm and the testing program are available here , and we are almost ready to go into the kernel space , but before that I want to draw your attention to one important point. The fact is that many well-known encryption algorithms do not involve processing the last incomplete block of input data. For example, implementations of the algorithm of GOST 28147-89 MUST return an error sign if the size of the input data is not a multiple of the block size, while the Belarusian Belt provides for such processing. My algorithm also provides for it (the key and the current value of synchromy are truncated to the size of an incomplete block). This fact will play its role a little later, while you just have to keep this in mind.
The action is transferred to the kernel space. Programming in the kernel is somewhat different from programming in user space and, due to the complexity of debugging, is a rather time-consuming process. However, while working on this material, I did not set myself the task of acquainting the reader with the basics of programming kernel modules, because even without this I have written enough, including here, on HabrΓ© ( more ; and more ). Therefore, if the reader is completely unfamiliar with writing modules, then I would recommend to first look at the materials that I cited above, or to search on my own (I assure you, this will not take much time). And with those who are ready to go further, we begin a deep study of the Crypto API .
So, we have an algorithm, and we want to build it into the kernel, but where do we start? Of course, with the documentation. Unfortunately, the section devoted to the development / integration of algorithms provides only very general knowledge of this process, but at least it helps to orient in the right direction. Specifically, we learn about the existence of functions responsible for registering and deregistering algorithms in the kernel. Let's understand:
/* include/linux/crypto.h */ int crypto_register_alg(struct crypto_alg *alg); int crypto_register_algs(struct crypto_alg *algs, int count); int crypto_unregister_alg(struct crypto_alg *alg); int crypto_unregister_algs(struct crypto_alg *algs, int count);
These functions return a negative value in the event of an error, and 0 - in the case of successful completion, and (once) the registered algorithm (s) are described by the crypto_alg
structure, let's look at its definition ( include / linux / crypto.h ):
/* include/linux/crypto.h */ struct crypto_alg { struct list_head cra_list; struct list_head cra_users; u32 cra_flags; unsigned int cra_blocksize; unsigned int cra_ctxsize; unsigned int cra_alignmask; int cra_priority; atomic_t cra_refcnt; char cra_name[CRYPTO_MAX_ALG_NAME]; char cra_driver_name[CRYPTO_MAX_ALG_NAME]; const struct crypto_type *cra_type; union { struct ablkcipher_alg ablkcipher; struct blkcipher_alg blkcipher; struct cipher_alg cipher; struct compress_alg compress; } cra_u; int (*cra_init)(struct crypto_tfm *tfm); void (*cra_exit)(struct crypto_tfm *tfm); void (*cra_destroy)(struct crypto_alg *alg); struct module *cra_module; } CRYPTO_MINALIGN_ATTR;
Fortunately, this structure is very well documented, and we donβt have to guess about the meaning of this or that field:
cra_flags
: a set of flags describing the algorithm. Flags are defined by constants starting with " CRYPTO_ALG_
" in include / linux / crypto.h , and are used to tune the algorithm description.cra_blocksize
: the byte size of the algorithm block. All types of transformations, except for hashing, return an error when trying to process data that is smaller than this value.cra_ctxsize
: the byte size of the cra_ctxsize
. The kernel uses this value when allocating memory for context.cra_alignmask
: alignment mask for input and output. The buffers for the input and output of the algorithm must be aligned with this mask. This is mandatory for algorithms running on hardware that is unable to access data at arbitrary addresses.cra_priority
: the priority of this implementation of the algorithm. If more than one transformation with the same cra_name
registered in the kernel, then when accessing by this name, the algorithm with the highest priority will be returnedcra_name
: the name of the algorithm. The kernel uses this field to search for implementations.cra_driver_name
: a unique name for the implementation of the algorithm. If more than one transformation with the same cra_name
registered in the kernel, and you need to request an algorithm with a lower priority, then you need to contact this namecra_type
: type of crypto-transform. Pointer to an instance of a crypto_type
structure that implements callback functions common to all conversion types. Available options: &crypto_blkcipher_type
, &crypto_ablkcipher_type
, &crypto_ahash_type
, &crypto_rng_type
. This field should be left blank for the following transformation types: block encryption ( cipher
), compression ( compress
), blocking hashing ( shash
)cra_u
: actually, the implementation of the algorithm. One of the union structures must be filled with specific functions defined by the type of transformation. This field should be left blank for blocking and non-blocking hashing ( ahash
)cra_init
: initialization function of the transform instance. This function is called once, during the creation of the instance (immediately after the memory is allocated for crypto context). In this function, you should perform various preparatory actions, if such are necessary (for example, allocating additional memory, checking hardware capabilities). Otherwise, the field can be left blank.cra_exit
: deinitialize the transform instance. In this function, you should perform actions that are inverse to those that were performed in cra_init
, if any. Otherwise, the field can be left blank.cra_module
: the owner of this conversion implementation. The value should be set to THIS_MODULE
Undocumented remaining fields are intended for internal use and should not be filled. It seems to be nothing complicated. To begin with, we want to embed the implementation of the block encryption algorithm, so let's take a look at the cipher_alg
structure from the cra_u
union:
/* include/linux/crypto.h */ struct cipher_alg { unsigned int cia_min_keysize; unsigned int cia_max_keysize; int (*cia_setkey)(struct crypto_tfm *tfm, const u8 *key, unsigned int keylen); void (*cia_encrypt)(struct crypto_tfm *tfm, u8 *dst, const u8 *src); void (*cia_decrypt)(struct crypto_tfm *tfm, u8 *dst, const u8 *src); };
It is still simpler and, it seems to me, does not need clarification. Now we are ready to see how all this works in practice. By the way, note that the function signatures in the cipher_alg
structure cipher_alg
similar to the function signatures from the API of our algorithm in 2 parts
We write the kernel module. We define the cipher_alg.cia_setkey
and the key installation function in accordance with the cipher_alg.cia_setkey
signature:
#define XOR_CIPHER_KEY_SIZE 16 struct xor_cipher_ctx { u8 key[XOR_CIPHER_KEY_SIZE]; }; static int xor_cipher_setkey(struct crypto_tfm *tfm, const u8 *key, unsigned int len) { struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); u32 *flags = &tfm->crt_flags; if (len != XOR_CIPHER_KEY_SIZE) { *flags |= CRYPTO_TFM_RES_BAD_KEY_LEN; return -EINVAL; } memmove(ctx->key, key, XOR_CIPHER_KEY_SIZE); return 0; }
In the API of our algorithm, the crypto_tfm_ctx
context was passed to the functions directly, but here the functions are taken by the handle of the tfm
algorithm, from which, then, the context is retrieved using the crypto_tfm_ctx
function. Also, here we check the length of the transferred key. If the length is incorrect, then we set the corresponding flag ( CRYPTO_TFM_RES_BAD_KEY_LEN
) and return the EINVAL
code (22: Invalid argument ).
Now, we define the block encryption function in accordance with cipher_alg.cia_encrypt
:
static void xor_cipher_crypt(struct crypto_tfm *tfm, u8 *out, const u8 *in) { struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); int i; for (i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { out[i] = in[i] ^ ctx->key[i]; } }
Here is nothing new. Just like in the original API , we will not define an additional function for decryption, and cipher_alg.cia_decrypt
simply initialize the cipher_alg.cia_decrypt
pointer with the cipher_alg.cia_decrypt
function.
Finally, we define an instance of the crypto_alg
structure, fill it in and call the registration and deregistration functions of the algorithm, respectively, after loading and before unloading the module:
static struct crypto_alg xor_cipher = { .cra_name = "xor-cipher", .cra_driver_name = "xor-cipher-generic", .cra_priority = 100, .cra_flags = CRYPTO_ALG_TYPE_CIPHER, .cra_blocksize = XOR_CIPHER_BLOCK_SIZE, .cra_ctxsize = sizeof(struct xor_cipher_ctx), .cra_module = THIS_MODULE, .cra_u = { .cipher = { .cia_min_keysize = XOR_CIPHER_KEY_SIZE, .cia_max_keysize = XOR_CIPHER_KEY_SIZE, .cia_setkey = xor_cipher_setkey, .cia_encrypt = xor_cipher_crypt, .cia_decrypt = xor_cipher_crypt } } }; static int __init xor_cipher_init(void) { return crypto_register_alg(&xor_cipher); } static void __exit xor_cipher_exit(void) { crypto_unregister_alg(&xor_cipher); }
Given all the above, no questions should arise here. If, having compiled and loaded such a module, execute the command " cat /proc/crypto
" in the terminal, then you can find your own in the list of registered algorithms:
name : xor-cipher driver : xor-cipher-generic module : xor_cipher priority : 100 refcnt : 1 selftest : passed internal : no type : cipher blocksize : 16 min keysize : 16 max keysize : 16
Not bad, huh? But this is only the beginning, and now, in theory, I had to move on to writing a module testing our algorithm, but still, I decided not to mix the issues of embedding and use. Therefore, we will be tested later, and in the next part we will see how we can embed the implementation of our algorithm in the mode of coupling cipher blocks.
I run a little forward. The attentive reader remembers the existence of patterns. So, having implemented block encryption in the last part, we can easily wrap this implementation in the CBC template and get the implementation of our algorithm in the cipher block chaining mode, but after testing it on my sequences, the CBC mode encryption call returns error code 22 ( EINVAL
). And now is the time to recall what I said about the processing of an incomplete block of input data and the crypto_alg.cra_blocksize
field. The fact is that the implementation of the CBC kernel mode has no idea how to handle an incomplete block. Moreover, by wrapping our algorithm in CBC mode, the kernel registers a new algorithm, the block size of which is equal to the block size of the basic algorithm. After calling the "cipher" encryption function by the cbc algorithm (xor-cipher) , the size of the input data is checked for the multiplicity of the block size and, if they are not multiples, the function returns EINVAL
. The size of the test vector for encryption in CBC mode (40 bytes) was deliberately chosen not to be a multiple of the block size. In my opinion, if an encryption standard provides for processing an incomplete block, and the implementation does not, then such an implementation will hardly be checked for compliance with the standard, even if the implementation gives the correct result when the multiplicity condition is met (in this case, it is). Therefore, now we will make the full implementation of the cipher block linkage mode for our algorithm. 2 , API , API , , , . Symmetric Key Cipher API .
, , , , Crypto API , . , ( arch/x86/crypto/aesni-intel_glue.c ), , , . , / :
/* include/crypto/internal/skcipher.h */ int crypto_register_skcipher(struct skcipher_alg *alg); int crypto_register_skciphers(struct skcipher_alg *algs, int count); void crypto_unregister_skcipher(struct skcipher_alg *alg); void crypto_unregister_skciphers(struct skcipher_alg *algs, int count);
skcipher_alg
:
/* include/crypto/skcipher.h */ struct skcipher_alg { int (*setkey)(struct crypto_skcipher *tfm, const u8 *key, unsigned int keylen); int (*encrypt)(struct skcipher_request *req); int (*decrypt)(struct skcipher_request *req); int (*init)(struct crypto_skcipher *tfm); void (*exit)(struct crypto_skcipher *tfm); unsigned int min_keysize; unsigned int max_keysize; unsigned int ivsize; unsigned int chunksize; unsigned int walksize; struct crypto_alg base; };
, include/crypto/skcipher.h . , struct crypto_alg
, , , init
exit
, crypto_alg
. , , chunksize
walksize
:
chunksize
: , , ( stream cipher ), , , , ( underlying ) . , , . ( skcipher_alg.base.cra_blocksize
) , chunksize
,walksize
: chunksize
, , , walksize
, chunksize
,encrypt
decrypt
. skcipher_request
, : / , . , - API , , , , .
, Crypto API . , - API / ( API ), scatterlist
. , , Synchronous Block Cipher API :
/* include/linux/crypto.h */ int crypto_blkcipher_encrypt(struct blkcipher_desc *desc, struct scatterlist *dst, struct scatterlist *src, unsigned int nbytes);
skcipher_request
struct scatterlist *src
struct scatterlist *dst
. scatterlist
:
/* include/linux/scatterlist.h */ struct scatterlist { /* ... */ unsigned long page_link; unsigned int offset; unsigned int length; /* ... */ };
. , sg_init_one
:
/* include/linux/scatterlist.h */ void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen);
, "" buf (page_link
), buf (offset
).
, . , :
struct crypto_blkcipher *tfm; struct blkcipher_dest desc; struct scatterlist sg[2]; u8 *first_segment, *second_segment; /* crypto and data allocation... */ sg_init_table(sg, 2); sg_set_buf(&sg[0], first_segment, len); sg_set_buf(&sg[1], second_segment, len); crypto_blkcipher_encrypt(&desc, &sg, &sg, 2*len);
, first_segment
second_segment
, . , Crypto API ( ) "" scatterlist
-, "" ( scattered ) . Crypto API IPsec :
One of the initial goals of this design was to readily support IPsec, so that processing can be applied to paged skb's without the need for linearization.
, scatterlist
API / ( Direct Memory Access , DMA I/O ). / , "" , , , .
. , :
static int xor_skcipher_setkey(struct crypto_skcipher *tfm, const u8 *key, unsigned int len) { return xor_cipher_setkey(crypto_skcipher_tfm(tfm), key, len); }
Everything is simple here. CBC ( struct xor_cipher_ctx
), , , .
.
static int cbc_encrypt(struct skcipher_request *req) { struct crypto_tfm *tfm = crypto_skcipher_tfm(crypto_skcipher_reqtfm(req)); struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); struct skcipher_walk walk; u32 nbytes; int i, blocks; u8 *src, *dst, *iv; skcipher_walk_virt(&walk, req, true); iv = walk.iv; while ((nbytes = walk.nbytes) >= XOR_CIPHER_BLOCK_SIZE) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; blocks = nbytes / XOR_CIPHER_BLOCK_SIZE; while (blocks) { for (i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { dst[i] = src[i] ^ iv[i]; } xor_cipher_crypt(tfm, dst, dst); iv = dst; src += XOR_CIPHER_BLOCK_SIZE; dst += XOR_CIPHER_BLOCK_SIZE; blocks--; } nbytes &= XOR_CIPHER_BLOCK_SIZE - 1; skcipher_walk_done(&walk, nbytes); } if ((nbytes = walk.nbytes)) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; for (i = 0; i < nbytes; i++) { dst[i] = src[i] ^ iv[i]; dst[i] ^= ctx->key[i]; } skcipher_walk_done(&walk, 0); } return 0; }
#define XOR_CIPHER_IV_SIZE 16 static int cbc_decrypt(struct skcipher_request *req) { struct crypto_tfm *tfm = crypto_skcipher_tfm(crypto_skcipher_reqtfm(req)); struct xor_cipher_ctx *ctx = crypto_tfm_ctx(tfm); struct skcipher_walk walk; u8 u[XOR_CIPHER_BLOCK_SIZE], iv[XOR_CIPHER_BLOCK_SIZE]; u32 nbytes; int i, blocks; u8 *src, *dst; skcipher_walk_virt(&walk, req, true); memmove(iv, walk.iv, XOR_CIPHER_IV_SIZE); while ((nbytes = walk.nbytes) >= XOR_CIPHER_BLOCK_SIZE) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; blocks = nbytes / XOR_CIPHER_BLOCK_SIZE; while (blocks) { memmove(u, src, XOR_CIPHER_BLOCK_SIZE); xor_cipher_crypt(tfm, dst, src); for (i = 0; i < XOR_CIPHER_BLOCK_SIZE; i++) { dst[i] ^= iv[i]; } memmove(iv, u, XOR_CIPHER_IV_SIZE); dst += XOR_CIPHER_BLOCK_SIZE; src += XOR_CIPHER_BLOCK_SIZE; blocks--; } nbytes &= XOR_CIPHER_BLOCK_SIZE - 1; skcipher_walk_done(&walk, nbytes); } if ((nbytes = walk.nbytes)) { src = (u8*)walk.src.virt.addr; dst = (u8*)walk.dst.virt.addr; for (i = 0; i < nbytes; i++) { dst[i] = src[i] ^ ctx->key[i]; dst[i] ^= iv[i]; } skcipher_walk_done(&walk, 0); } return 0; }
, , , "" skcipher_walk
. , skcipher_walk
skcipher_walk_virt
, skcipher_walk_done
"" / ( walk.src.virt.addr
walk.dst.virt.addr
) walk.nbytes
, scatterlist
-.
skcipher_alg
, / :
static struct skcipher_alg cbc_xor_cipher = { .base = { .cra_name = "cbc(xor-cipher)", .cra_driver_name = "cbc-xor-cipher", .cra_priority = 400, .cra_flags = CRYPTO_ALG_ASYNC, .cra_blocksize = 1, .cra_ctxsize = sizeof(struct xor_cipher_ctx), .cra_module = THIS_MODULE, }, .min_keysize = XOR_CIPHER_KEY_SIZE, .max_keysize = XOR_CIPHER_KEY_SIZE, .ivsize = XOR_CIPHER_IV_SIZE, .setkey = xor_skcipher_setkey, .encrypt = cbc_encrypt, .decrypt = cbc_decrypt, .chunksize = XOR_CIPHER_BLOCK_SIZE, }; static int __init xor_cipher_init(void) { crypto_register_alg(&xor_cipher); return crypto_register_skcipher(&cbc_xor_cipher); } static void __exit xor_cipher_exit(void) { crypto_unregister_alg(&xor_cipher); crypto_unregister_skcipher(&cbc_xor_cipher); }
cra_blocksize
. , , skcipher_walk_done
"" , .
, /proc/crypto
:
name : cbc(xor-cipher) driver : cbc-xor-cipher module : xor_cipher priority : 400 refcnt : 1 selftest : passed internal : no type : skcipher async : yes blocksize : 1 min keysize : 16 max keysize : 16 ivsize : 16 chunksize : 16
( ) : " xor-cipher
" ( ) " cbc(xor-cipher)
" ( ). , , . , ( arch/x86/crypto ), .
, , "", , , , . , , β , : , , Crypto API . , , , .
. cipher_testvec_t
:
typedef enum test_t { TEST_BLK_ENCRYPT = 0, TEST_BLK_DECRYPT, TEST_CBC_ENCRYPT, TEST_CBC_DECRYPT, TEST_END, } test_t; struct cipher_testvec_t { test_t test; u32 len; char *key; char *iv; char *in; char *result; };
: in
, len
, ( .test = TEST_BLK_*
) ( .test = TEST_CBC_*
), key
, , iv
. result
. TEST_END
cipher_testvec_t
.
β :
static int test_blk(cipher_testvec_t *testvec) { struct crypto_cipher *tfm = NULL; int encrypt = (testvec->test == TEST_BLK_ENCRYPT) ? 1 : 0; u8 dst[16]; tfm = crypto_alloc_cipher("xor-cipher", 0, 0); if (IS_ERR(tfm)) { pr_err("error allocating xor-cipher: %ld\n", PTR_ERR(tfm)); return 0; } crypto_cipher_setkey(tfm, (u8*)testvec->key, 16); if (encrypt) { crypto_cipher_encrypt_one(tfm, dst, (u8*)testvec->in); } else { crypto_cipher_decrypt_one(tfm, dst, (u8*)testvec->in); } crypto_free_cipher(tfm); if (memcmp(dst, testvec->result, 16)) { pr_err("block %sciphering test failed!\n", encrypt ? "" : "de"); dumpb((u8*)testvec->key, 16, "key"); dumpb((u8*)testvec->in, 16, "in"); dumpb(dst, 16, "result"); dumpb((u8*)testvec->result, 16, "should be"); return 0; } return 1; }
, Single Block Cipher API . , crypto_alloc_cipher
, " xor-cpher ". , ( crypto_cipher_setkey
), , , ( crypto_cipher_encrypt_one
) ( crypto_cipher_decrypt_one
) . , ( crypto_free_cipher
) .
CBC .
static int test_cbc(cipher_testvec_t *testvec) { struct scatterlist sg; struct cb_data_t cb_data; struct crypto_skcipher *tfm = NULL; struct skcipher_request *req = NULL; int encrypt = (testvec->test == TEST_CBC_ENCRYPT) ? 1 : 0; u32 err; u8 *buf = NULL; tfm = crypto_alloc_skcipher("cbc-xor-cipher", 0, 0); if (IS_ERR(tfm)) { pr_err("error allocating cbc-xor-cipher: %ld\n", PTR_ERR(tfm)); goto exit; } req = skcipher_request_alloc(tfm, GFP_KERNEL); if (!req) { pr_err("error allocating skcipher request\n"); goto exit; } buf = kmalloc(testvec->len, GFP_KERNEL); if (!buf) { pr_err("memory allocation error\n"); goto exit; } memmove(buf, (u8*)testvec->in, testvec->len); sg_init_one(&sg, buf, testvec->len); crypto_skcipher_setkey(tfm, (u8*)testvec->key, 16); skcipher_request_set_crypt(req, &sg, &sg, testvec->len, (u8*)testvec->iv); skcipher_request_set_callback(req, 0, skcipher_cb, &cb_data); init_completion(&cb_data.completion); err = (encrypt) ? crypto_skcipher_encrypt(req) : crypto_skcipher_decrypt(req); switch (err) { case 0: break; case -EINPROGRESS: case -EBUSY: wait_for_completion(&cb_data.completion); err = cb_data.err; if (!err) { break; } default: pr_err("failed with error: %d\n", err); goto exit; } if (memcmp(buf, testvec->result, testvec->len)) { pr_err("cbc %sciphering test failed!\n", encrypt ? "" : "de"); dumpb((u8*)testvec->key, 16, "key"); dumpb((u8*)testvec->iv, 16, "iv"); dumpb((u8*)testvec->in, testvec->len, "in"); dumpb(buf, testvec->len, "result"); dumpb((u8*)testvec->result, testvec->len, "should be"); goto exit; } skcipher_request_free(req); crypto_free_skcipher(tfm); kfree(buf); return 1; exit: if (buf) { kfree(buf); } if (req) { skcipher_request_free(req); } if (tfm) { crypto_free_skcipher(tfm); } return 0; }
: API ( Symmetric Key Cipher API ), .
test_cbc
:
cb_data_t
,crypto_alloc_skcipher
) skcipher_requset
( skcipher_request_alloc
)kmalloc
) , , ; scatterlist
( sg_init_one
)crypto_skcipher_setkey
) skcipher_requset
( skcipher_request_set_crypt
). skcipher_request_set_crypt
, , . scatterlist
, , " ", buf
skcipher_request_set_callback
). API , , , . , , , . skcipher_request_set_callback
. cb_data_t
, ( skcipher_cb
) :
struct cb_data_t { struct completion completion; int err; }; static void skcipher_cb(struct crypto_async_request *req, int error) { struct cb_data_t *data = req->data; if (error == -EINPROGRESS) { return; } data->err = error; complete(&data->completion); }
completion
test_cbc
, . , , test_cbc
. , test_cbc
, :
completion
( init_completion
),crypto_skcipher_encrypt
) ( crypto_skcipher_decrypt
)-EINPROGRESS
-EBUSY
, . , completion
( complete
wait_for_completion
),crypto_skcipher_encrypt
switch
-a . That's all. , ( , ), - :
insmod: ERROR: could not insert module xor_cipher_testing.ko: Operation not permitted
( dmesg
) :
[---] done 4 tests, passed: 4, failed: 0
, SK Cipher API , . β "", Asynchronous Block Cipher API , . -, -, , .
, . , , , , , , Linux . libkcapi , Crypto API ( ). "" Netlink -, "" Netlink API . , , , , , ( 1.0.3).
, . , , , ( ), Crypto API . af_alg Strongswan , .
So let's go. , , . , , . , test_cbc
.
static int test_cbc(cipher_testvec_t *testvec) { uint8_t dst[testvec->len]; int encrypt = (testvec->test == TEST_CBC_ENCRYPT) ? 1 : 0; struct af_alg_skcipher *tfm = NULL; tfm = af_alg_allocate_skcipher("cbc-xor-cipher"); if (!tfm) { fprintf(stderr, "error allocating \"cbc-xor-cipher\"\n"); goto err; } if (!af_alg_skcipher_setkey(tfm, (uint8_t*)testvec->key, XOR_CIPHER_KEY_SIZE)) { fprintf(stderr, "can't set \"cbc-xor-cipher\" key\n"); goto err; } if (!af_alg_skcipher_crypt(tfm, encrypt, dst, testvec->len, (uint8_t*)testvec->in, (uint8_t*)testvec->iv, XOR_CIPHER_IV_SIZE)) { goto err; } af_alg_free_skcipher(tfm); if (memcmp(dst, (uint8_t*)testvec->result, testvec->len)) { fprintf(stderr, "cbc %sciphering test failed!\n", encrypt ? "" : "de"); dumpb((uint8_t*)testvec->key, XOR_CIPHER_KEY_SIZE, "key"); dumpb((uint8_t*)testvec->iv, XOR_CIPHER_IV_SIZE, "iv"); dumpb((uint8_t*)testvec->in, testvec->len, "in"); dumpb(dst, testvec->len, "result"); dumpb((uint8_t*)testvec->result, testvec->len, "should be"); return 0; } return 1; err: if (tfm) { af_alg_free_skcipher(tfm); } return 0; }
. , , , , , . af_alg_skcipher
af_alg_*
. We look further.
struct af_alg_skcipher { int sockfd; }; static struct af_alg_skcipher* af_alg_allocate_skcipher(char *name) { struct af_alg_skcipher *tfm = NULL; struct sockaddr_alg sa = { .salg_family = AF_ALG, .salg_type = "skcipher", }; strncpy((char*)sa.salg_name, name, sizeof(sa.salg_name)); tfm = calloc(1, sizeof(struct af_alg_skcipher)); if (!tfm) { errno = ENOMEM; goto err; } tfm->sockfd = socket(AF_ALG, SOCK_SEQPACKET, 0); if (tfm->sockfd == -1) { goto err; } if (bind(tfm->sockfd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { goto err; } return tfm; err: if (tfm->sockfd > 0) { close(tfm->sockfd); } if (tfm) { free(tfm); } return NULL; }
:
sockaddr_alg
( )calloc
)socket
)socket
, "" . AF_ALG Crypto API . AF_ALG , , sys/socket.h , , :
#ifndef AF_ALG #define AF_ALG 38 #endif
bind
)sockaddr_alg
, bind
:
/* sys/socket.h */ int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockadr
:
struct sockaddr { sa_family_t sa_family; char sa_data[14]; }
, , ( sa_family
), AF_ALG :
/* linux/if_alg.h */ struct sockaddr_alg { __u16 salg_family; __u8 salg_type[14]; __u32 salg_feat; __u32 salg_mask; __u8 salg_name[64]; };
:
salg_family
: .salg_type
: : " skcipher
" β , " hash
" β , " aead
" β , AEAD , "rng" βsalg_name
: ( cra_name
cra_driver_name
), bind
-1, errno
ENOENT
.
, af_alg_allocate_skcipher
, . .
:
static int af_alg_skcipher_setkey(struct af_alg_skcipher *tfm, uint8_t *key, uint32_t keylen) { return (setsockopt(tfm->sockfd, SOL_ALG, ALG_SET_KEY, key, keylen) == -1) ? 0 : 1; }
setsockopt
, man - . , SOL_ALG
ALG_SET_KEY
, , sys/socket.h linux/af_alg.h , , , , :
#ifndef SOL_ALG #define SOL_ALG 279 #endif #ifndef ALG_SET_KEY #define ALG_SET_KEY 1 #endif
, .
static int af_alg_skcipher_crypt(struct af_alg_skcipher *tfm, int encrypt, uint8_t *_dst, uint32_t _len, uint8_t *_src, uint8_t *iv, uint32_t ivlen) { int type = encrypt ? ALG_OP_ENCRYPT : ALG_OP_DECRYPT; struct msghdr msg = {}; struct cmsghdr *cmsg; struct af_alg_iv *ivm; struct iovec iov; char buf[CMSG_SPACE(sizeof(type)) + CMSG_SPACE(offsetof(struct af_alg_iv, iv) + ivlen)]; int op = 0; ssize_t len, remainig = _len; uint8_t *src = _src, *dst = _dst; op = accept(tfm->sockfd, NULL, 0); if (op == -1) { goto end; } memset(buf, 0, sizeof(buf)); /* fill in af_alg cipher controll data */ msg.msg_control = buf; msg.msg_controllen = sizeof(buf); /* operation type: encrypt or decrypt */ cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_OP; cmsg->cmsg_len = CMSG_LEN(sizeof(type)); memmove(CMSG_DATA(cmsg), &type, sizeof(type)); /* initialization vector */ cmsg = CMSG_NXTHDR(&msg, cmsg); cmsg->cmsg_level = SOL_ALG; cmsg->cmsg_type = ALG_SET_IV; cmsg->cmsg_len = CMSG_LEN(offsetof(struct af_alg_iv, iv) + ivlen); ivm = (void*)CMSG_DATA(cmsg); ivm->ivlen = ivlen; memmove(ivm->iv, iv, ivlen); /* set data stream (scatter/gather list) */ msg.msg_iov = &iov; msg.msg_iovlen = 1; while (remainig) { iov.iov_base = src; iov.iov_len = remainig; len = sendmsg(op, &msg, 0); if (len == -1) { if (errno == EINTR) { continue; } goto end; } while (read(op, dst, len) != len) { if (errno != EINTR) { goto end; } } src += len; remainig -= len; /* no iv for subsequent data chunks */ msg.msg_controllen = 0; } /* done */ close(op); return 1; end: if (op > 0) { close(op); } return 0; }
, . , , , , , . , 3 :
accept
, , , ,msghdr
), ( cmsghdr
) , , / ( iovec
)( ALG_SET_OP
): ( ALG_OP_ENCRYPT
) ( ALG_OP_DECRYPT
), ( ALG_SET_IV
, af_alg_iv
). , , linux/if_alg.h , , , :
#ifndef ALG_SET_IV #define ALG_SET_IV 2 #endif #ifndef ALG_SET_OP #define ALG_SET_OP 3 #endif #ifndef ALG_OP_DECRYPT #define ALG_OP_DECRYPT 0 #endif #ifndef ALG_OP_ENCRYPT #define ALG_OP_ENCRYPT 1 #endif
sendmsg
read
, , accept
, , , / done 2 tests, passed: 2, failed: 0
β , .
, , , , , , , , . , , . .
β . , , , . , Crypto API . , - API , . , , , , .
, : , , , . , github . Thanks for attention!
, "" , β . ! . , xor-cipher , , CBC , ( SK Cipher API , β ):
struct crypto_skcipher *tfm = crypto_alloc_skcipher("cbc(xor-cipher)", 0, 0);
xor-cipher , ( crypto/cbc.c ).
" ! β . β , cbc(xor-cipher) ?". " ", β . , .
, :
. "" . , " cbc ", :
.cra_name = "xor-cipher"; .cra_driver_name = "xor-cipher-generic";
:
.cra_name = "cbc(xor-cipher)"; .cra_driver_name = "cbc(xor-cipher-generic)";
, crypto_alg
, , cra_driver_name
, . , , cra_driver_name
" cbc(xor-cipher-generic) ", :
struct crypto_skcipher *tfm = crypto_alloc_skcipher("cbc(xor-cipher-generic)", 0, 0);
, , , . , Crypto API , , .
tcrypt ( crypto/tcrypt.c ) . , crypto/tcrypt.c , , crypto/testmgr.c . , testmgr , ( ).
, tcrypt . "" tcrypt testmgr , Makefile , β tcryptext (tcrypt External) . .
tcryptext ( tcrypt , ), - , . ( README.md , ).
testmgr.c Crypto API .
, Linux-based . ( ) .
. " crypto " crypto/Kconfig Makefile . , crypto xor_cipher.c , crypto/Kconfig ( comment "Ciphers" ) :
... config CRYPTO_XOR_CIPHER tristate "XOR cipher algorithm" help Custom XOR cipher algorithm. ...
crypto/Makefile :
... obj-$(CONFIG_CRYPTO_XOR_CIPHER) += xor_cipher.o ...
( " Cryptographic API ") . , .
tcrypt testmgr .
Literature:
Git:
Source: https://habr.com/ru/post/348552/
All Articles