πŸ“œ ⬆️ ⬇️

Extending and using the Linux Crypto API

[0] Intro


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.




[1] Crypto API Architecture


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.




[2] Soil preparation


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.


Encryption
 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]; } } } 

Decryption
 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.


Test sequences
Block encryption
Key2f 1b 1a c6 d1 be cb a2 f8 45 66 0d d2 97 5c a3
Test number 1
Input datacc 6b 79 0c db 55 4f e5 a0 69 05 96 11 be 8c 15
Outpute3 70 63 ca 0a eb 84 47 58 2c 63 9b c3 29 d0 b6
Test number 2
Input data53 f5 f1 ef 67 a5 ba 6c 68 09 b5 7a 24 de 82 5f
Output7c ee eb 29 b6 1b 71 ce 90 4c d3 77 f6 49 de fc

CBC Encryption
Keyec 8d 93 30 69 7e f8 63 0b f5 58 ec de 78 24 f2
Synchro parceldb 02 1f a8 5a 22 15 cf 49 f7 80 8b 7c 24 a1 f3
Plain text6e 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
Ciphertext59 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
Keyec 8d 93 30 69 7e f8 63 0b f5 58 ec de 78 24 f2
Synchro parceldb 02 1f a8 5a 22 15 cf 49 f7 80 8b 7c 24 a1 f3
Ciphertextdb 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 textec 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.




[3] Immersion in the core


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:



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.




[3.1] "A little deeper" immersion into the core


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 :



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 ), .




[4]


, , "", , , , . , , β€” , : , , 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 .


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 :



. 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 , :



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 , . -, -, , .




[4.1] user-space


, . , , , , , , Linux . libkcapi , Crypto API ( ). "" Netlink -, "" Netlink API . , , , , , ( 1.0.3).


, . , , , ( ), Crypto API . af_alg Strongswan , .


So let's go. , , . , , . , test_cbc .


CBC user-space
 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; } 

:



socket , "" . AF_ALG Crypto API . AF_ALG , , sys/socket.h , , :


 #ifndef AF_ALG #define AF_ALG 38 #endif 


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]; }; 

:



, 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 :



( 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 


That's all. . , :


 done 2 tests, passed: 2, failed: 0 

β€” , .




[5] ?


, , , , , , , , . , , . .


β€” . , , , . , 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 .




useful links


Literature:



Git:



')

Source: https://habr.com/ru/post/348552/


All Articles