介紹由應用層所發出的 crypto(cryptography) request,透過 system call 將 request 傳送到 Linux kernel 端,並經由 crypto subsystem 將 request 轉發給硬體算法引擎 (hardware crypto engine) 的流程。
Overview
Crypto subsystem 是 Linux 系統中負責處理 crypto request 的子系統,除了包含流程控制機制之外,另一個重要特色就是提供演算法實作的抽象層,讓各家廠商能夠依據需求去客製化實作方式。其中一個常見例子就是廠商在硬體架構中加入用以加速特定演算法運算效率的硬體算法引擎,並且透過 crypto subsystem 將驅動硬體算法引擎的流程整合進 Linux 系統中,供其他 kernel module 或是應用層使用。
以下以 openSSL library 如何將 crypto request 傳送到 kernel crypto subsystem 為例子:
image from: Linux Kernel 密碼學演算法實作流程
cryptodev Engine
在 Linux 系統中,想要實現應用層與硬體裝置的溝通,第一個想到的就是透過 character/block device driver,讓應用程式開啟表示此硬體裝置的抽象檔案,並且藉由讀寫行為與硬體裝置進行互動。而 Cryptodev-linux 就是負責此角色,它提供中間層的服務,接收由應用層傳送過來的 crypto request,再呼叫 Linux kernel crypto Subsystem 的 crypto API 將 request 轉發給特定的硬體算法引擎。
Cryptodev-linux 為 miscellaneous device 類型的 kernel module,預設路徑是 /dev/crypto,使用 ioctl file operation cryptodev_ioctl 來接受應用端所傳遞過來的資料。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // https://github.com/cryptodev-linux/cryptodev-linux/blob/master/ioctl.c
static const struct file_operations cryptodev_fops = {
.owner = THIS_MODULE,
.open = cryptodev_open,
.release = cryptodev_release,
.unlocked_ioctl = cryptodev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = cryptodev_compat_ioctl,
#endif /* CONFIG_COMPAT */
.poll = cryptodev_poll,
};
static struct miscdevice cryptodev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "crypto",
.fops = &cryptodev_fops,
.mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH,
};
static int __init
cryptodev_register(void)
{
int rc;
rc = misc_register(&cryptodev);
if (unlikely(rc)) {
pr_err(PFX "registration of /dev/crypto failed\n");
return rc;
}
return 0;
}
|
應用端則是使用 cryptodev.h 定義好的 struct crypt_op 或是 struct crypt_auth_op 來組成指定 crypto request,並呼叫 ioctl system call 將 request 送給 Cryptodev-linux。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // https://github.com/cryptodev-linux/cryptodev-linux/blob/master/crypto/cryptodev.h
struct crypt_auth_op {
__u32 ses; /* session identifier */
__u16 op; /* COP_ENCRYPT or COP_DECRYPT */
__u16 flags; /* see COP_FLAG_AEAD_* */
__u32 len; /* length of source data */
__u32 auth_len; /* length of auth data */
__u8 __user *auth_src; /* authenticated-only data */
/* The current implementation is more efficient if data are
* encrypted in-place (src==dst). */
__u8 __user *src; /* data to be encrypted and authenticated */
__u8 __user *dst; /* pointer to output data. Must have
* space for tag. For TLS this should be at least
* len + tag_size + block_size for padding */
__u8 __user *tag; /* where the tag will be copied to. TLS mode
* doesn't use that as tag is copied to dst.
* SRTP mode copies tag there. */
__u32 tag_len; /* the length of the tag. Use zero for digest size or max tag. */
/* initialization vector for encryption operations */
__u8 __user *iv;
__u32 iv_len;
};
|
Sample code for Cryptodev-linux ioctl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // setup data for your crypto request
cryp.ses = ctx->sess.ses;
cryp.iv = (void*)iv;
cryp.op = COP_DECRYPT;
cryp.auth_len = auth_size;
cryp.auth_src = (void*)auth;
cryp.len = size;
cryp.src = (void*)ciphertext;
cryp.dst = ciphertext;
cryp.flags = COP_FLAG_AEAD_TLS_TYPE;
// call ioctl to pass a crypto request to `/dev/crypto`
if (ioctl(ctx->cfd, CIOCAUTHCRYPT, &cryp)) {
perror("ioctl(CIOCAUTHCRYPT)");
return -1;
}
|
另外,Cryptodev-linux 也提供 session 機制,每個 crypto request 對應到一個 session,而 session 管理當前 crypto request 的狀態。例如,目前 session 在 initialized 的狀態,則表示此 crypto request 可執行 encrypt,透過此方式來確保 crypto request 會在正確的流程下運作。
Linux Kernel Crypto Subsystem
Crypto request 會透過 kernel crypto API 傳到 kernel crypto subsystem 中。以下為簡略的 crypto API 呼叫流程:

首先 Crypto subsystem 有兩個重要元素:transformation object 和 transformation implementation。
transformation object 在 API 中會簡寫為 tfm,又被稱作 cipher handler;而 transformation implementation 則是 transformation object 底層的實作內容,又被稱作 crypto algo,以之前例子來說就是 crypto engine 的演算法實作。之所以要區分成 object 和 implementation,最主要的原因是有可能多個 object 會使用同一個 implementation。舉例來說,A 和 B 使用者都要使用 hmac-sha256 算法,因此會新建立 A 和 B 兩個 transformation object 並包含 A 和 B 各自擁有的 key 值,但這兩個 object 有可能會使用同一個 transformation implementation 來呼叫同一個 crypto engine 進行算法運算。
TFM: The transformation object (TFM) is an instance of a transformation implementation. There can be multiple transformation objects associated with a single transformation implementation. Each of those transformation objects is held by a crypto API consumer or another transformation. https://www.kernel.org/doc/html/latest/crypto/intro.html
1
2
3
4
5
6
7
| struct crypto_tfm {
u32 crt_flags;
int node;
void (*exit)(struct crypto_tfm *tfm);
struct crypto_alg *__crt_alg; // crypto algorithm or transformation implementation
void *__crt_ctx[] CRYPTO_MINALIGN_ATTR;
};
|
當有 crypto request 進來,會先根據 request 中指定的算法名稱,從已註冊的 crypto algorithm list 中取出適合的 crypto algorithm,並新建立 transformation object。之後, transformation object 會再被組成 crypto subsystem 所用的 cipher request。cipher request 有可能共用同一個 transformation object,舉例來說,hmac-sha256 的 transformation object 包含了 transformation implementation 和一個 key 值,而這個 transformation object 可以使用在多個 cipher request 的 messsage 上進行 hash 算法 (不同 plaintext 使用同一把 key 進行運算)。
當 cipher request 完成相關設值之後,接著實際呼叫 transformation object 的 transformation implementation 執行演算法運算。
此時會出現一個問題,就是當短時間有多個 request 進來時,我們該如何依序地處理 request?這點 crypto subsystem 也設計了方便的 struct crypto_engine,crypto engine 提供了 queue 管理機制,讓多個 request 能夠順序地轉發給對應的 crypto engine。當然如果我們有額外的需求,也可以自己實作其他機制來管理,不一定要使用 crypto engine。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| struct crypto_engine {
char name[ENGINE_NAME_LEN];
bool idling;
bool busy;
bool running;
bool retry_support;
struct list_head list;
spinlock_t queue_lock;
struct crypto_queue queue;
struct device *dev;
bool rt;
// implement these three functions to trigger your hardware crypto engine
int (*prepare_crypt_hardware)(struct crypto_engine *engine);
int (*unprepare_crypt_hardware)(struct crypto_engine *engine);
int (*do_batch_requests)(struct crypto_engine *engine);
struct kthread_worker *kworker;
struct kthread_work pump_requests;
void *priv_data;
struct crypto_async_request *cur_req;
};
|
介紹完 crypt API 流程後,可以知道要新增 transformation implementation 到 crypto subsystem,最重要的就是註冊 transformation implementation 到 crypto algorithm list 中。而 Crypto API 提供了相關註冊 API,以 stm32-cryp 為例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| 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;
};
static struct skcipher_alg crypto_algs[] = {
{
.base.cra_name = "ecb(aes)",
.base.cra_driver_name = "stm32-ecb-aes",
.base.cra_priority = 200,
.base.cra_flags = CRYPTO_ALG_ASYNC,
.base.cra_blocksize = AES_BLOCK_SIZE,
.base.cra_ctxsize = sizeof(struct stm32_cryp_ctx),
.base.cra_alignmask = 0xf,
.base.cra_module = THIS_MODULE,
.init = stm32_cryp_init_tfm,
.min_keysize = AES_MIN_KEY_SIZE,
.max_keysize = AES_MAX_KEY_SIZE,
.setkey = stm32_cryp_aes_setkey,
.encrypt = stm32_cryp_aes_ecb_encrypt,
.decrypt = stm32_cryp_aes_ecb_decrypt,
},
}
|
上述建立含有演算法實作的 transformation implementation 後,接著呼叫註冊 API:
1
2
3
4
5
| ret = crypto_register_skciphers(crypto_algs, ARRAY_SIZE(crypto_algs));
if (ret) {
dev_err(dev, "Could not register algs\n");
goto err_algs;
}
|
即可完成註冊。
另外,上面提及的 structure member 中, cra_priority 代表各 transformation implementation 的優先程度,舉例來說,AES-ECB 有註冊軟體和硬體兩種不同的 transformation implementation,優先程度較高的會先被採用。
cra_priority
- Priority of this transformation implementation. In case multiple transformations with same cra_name are available to the Crypto API, the kernel will use the one with highest cra_priority.
References
- Linux Kernel Crypto API
- An overview of the crypto subsystem - The Linux Foundation