Crypto API - 7. 用户空间接口

Crypto API - 7. 用户空间接口

简介

对内核空间可见的内核加密 API 的概念也完全适用于用户空间接口。因此,内核内用例的内核加密 API 高级讨论也适用于此处。

然而,主要区别在于,用户空间只能充当消费者,而不能充当转换或密码算法的提供者。

下面介绍内核加密 API 导出的用户空间接口。此描述的一个工作示例是 libkcapi,可以从 [1] 获得。该库可由需要内核加密服务的用户空间应用程序使用。

但是,内核中内核加密 API 方面的一些细节不适用于用户空间。这包括同步调用和异步调用之间的区别。用户空间 API 调用是完全同步的。

[1] https://www.chronox.de/libkcapi.html

用户空间 API 一般备注

内核加密 API 可从用户空间访问。目前,可以使用以下密码:

  • 消息摘要,包括带密钥的消息摘要(HMAC,CMAC)
  • 对称密码
  • AEAD密码(认证加密)
  • 随机数生成器

该接口通过使用AF_ALG 类型的套接字提供。此外,setsockopt 选项类型为 SOL_ALG。如果用户空间头文件尚未导出这些标志,请使用以下宏:

#ifndef AF_ALG
#define AF_ALG 38
#endif
#ifndef SOL_ALG
#define SOL_ALG 279
#endif

使用与内核API调用相同的名称访问密码。这包括密码的通用命名模式和唯一命名模式,以及对通用名称的优先级强制执行。

为与内核crypto API交互,用户空间应用程序必须创建一个套接字。用户空间使用send()/write()系统调用族调用加密操作。通过read()/recv()系统调用族可以得到加密操作的结果。

下面的API调用假定套接字描述符已经由用户空间应用程序打开,只讨论特定于内核crypto API的调用。

要初始化套接字接口,消费者必须执行以下序列:

  1. 根据不同的密码类型,使用指定的struct sockaddr_alg参数创建一个类型为AF_ALG的套接字。

  2. 使用套接字描述符调用bind函数。

  3. 使用套接字描述符调用accept函数。accept系统调用将返回一个新的文件描述符,该文件描述符将用于与特定的密码实例进行交互。当使用send/write或recv/read系统调用向内核发送数据或从内核获取数据时,必须使用accept返回的文件描述符。

原地密码操作

就像内核加密 API 的内核中(in-kernel)操作一样,用户空间接口允许就地(in-place)进行密码操作。这意味着用于 send/write 统调用的输入缓冲区和用于 read/recv 系统调用的输出缓冲区可以是同一个。这对于对称密码操作特别有用,因为可以避免将输出数据复制到最终目的地。

另一方面,如果消费者希望将明文和密文维护在不同的内存位置,则消费者需要做的就是为加密和解密操作提供不同的内存指针。

消息摘要 API

调用 bind 系统调用时,将选择用于密码操作的消息摘要类型。bind 要求调用方提供填充的结构 sockaddr 数据结构。此数据结构必须按如下方式填写:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "hash", /* this selects the hash logic in the kernel */
    .salg_name = "sha1" /* this is the cipher name */
};

salg_type 值 “hash” 适用于消息摘要和有密钥消息摘要。然而,有密钥消息摘要是通过适当的 salg_name 引用的。请参阅下面的 setsockopt 接口,了解如何为有密钥消息摘要设置密钥。

使用 send() 系统调用,应用程序提供应使用消息摘要处理的数据。send 系统调用允许指定以下标志:

  • MSG_MORE:如果设置了此标志,则 send 系统调用的作用类似于消息摘要更新函数,其中尚未计算最终哈希值。如果未设置该标志,则发送系统调用将立即计算最终消息摘要。

通过 recv() 系统调用,应用程序可以从内核加密 API 读取消息摘要。如果缓冲区对于消息摘要来说太小,则内核将设置MSG_TRUNC标志。

为了设置消息摘要密钥,调用应用程序必须使用 setsockopt() 函数的 ALG_SET_KEY 或 ALG_SET_KEY_BY_KEY_SERIAL 选项。如果没有设置密钥,则HMAC操作将在没有密钥引起的初始HMAC状态改变的情况下执行。

对称密码 API

该操作与消息摘要讨论非常相似。在初始化过程中,必须按如下方式填充 struct sockaddr 数据结构:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "skcipher", /* this selects the symmetric cipher */
    .salg_name = "cbc(aes)" /* this is the cipher name */
};

在使用 write/send 系统调用族将数据发送到内核之前,使用者必须设置密钥。密钥的设置可以通过下面的setsockopt调用来描述。

使用sendmsg()系统调用,应用程序提供应该进行加密或解密处理的数据。此外,通过sendmsg()系统调用提供的数据结构指定了IV(初始化向量)。

sendmsg系统调用的struct msghdr参数被嵌入到struct cmsghdr数据结构中。有关cmsghdr数据结构如何与send/recv系统调用族一起使用的更多信息,请参阅recv(2)和cmsg(3)。该cmsghdr数据结构包含以下通过单独的头实例指定的信息:

  • 使用以下标志之一指定密码操作类型:

    • ALG_OP_ENCRYPT - 数据加密
    • ALG_OP_DECRYPT - 数据解密
  • 使用标志ALG_SET_IV标记的IV信息。

send 系统调用系列允许指定以下标志:

MSG_MORE:如果设置了此标志,则send系统调用的作用类似于密码更新函数,其中后续调用send系统调用时会收到更多输入数据。

注意:内核对于任何意外数据会返回-EINVAL。调用者必须确保所有数据都符合在 /proc/crypto 中选定的密码所给出的约束条件。

通过 recv() 系统调用,应用程序可以从内核加密 API 读取密码操作的结果。输出缓冲区必须至少与容纳加密或解密数据的所有块一样大。如果输出数据大小较小,则仅返回适合该输出缓冲区大小的块数。

AEAD 密码 API

该操作与对称密码讨论非常相似。在初始化过程中,必须按如下方式填充 struct sockaddr 数据结构:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "aead", /* this selects the symmetric cipher */
    .salg_name = "gcm(aes)" /* this is the cipher name */
};

在使用 write/send 系统调用族将数据发送到内核之前,使用者必须设置密钥。密钥设置在下面的 setsockopt 调用中描述。

此外,在使用 write/send 系统调用系列将数据发送到内核之前,使用者必须设置身份验证标记(authentication tag)大小。若要设置身份验证标记大小,调用方必须使用下面所述的 setsockopt 调用。

使用sendmsg()系统调用,应用程序提供应该进行加密或解密处理的数据。此外,通过sendmsg()系统调用提供的数据结构指定了IV(初始化向量)。

struct msghdr的sendmsg系统调用参数被嵌入到struct cmsghdr数据结构中。有关cmsghdr数据结构与send/recv系统调用族一起使用的更多信息,请参见recv(2)和cmsg(3)。该cmsghdr数据结构包含用单独的头实例指定的以下信息:

  • 使用以下标志之一规范密码操作类型:

    • ALG_OP_ENCRYPT - 数据加密
    • ALG_OP_DECRYPT - 数据解密
  • 使用标志 ALG_SET_IV 标记的IV信息。

  • 使用标志 ALG_SET_AEAD_ASSOCLEN 标记的关联身份验证数据(AAD:associated authentication data)。AAD与明文/密文一起发送到内核。有关内存结构,请参见下文。

send 系统调用系列允许指定以下标志:

MSG_MORE:如果设置了此标志,则发送系统调用的作用类似于密码更新函数,其中后续调用发送系统调用时会收到更多输入数据。

注意:对于任何意外数据的内核会报告 -EINVAL。调用方必须确保所有数据都与所选密码的 /proc/crypto 中给出的约束匹配。

通过recv() 系统调用,应用程序可以从内核的加密API中读取密码操作的结果。输出缓冲区的大小必须至少与下面的内存结构定义的大小相同。如果输出数据大小较小,则不会执行密码操作。

验证的解密操作可能会指示出完整性错误。这种完整性违规将用 -EBADMSG 错误代码标记。

AEAD存储器结构

AEAD 密码使用以下信息进行操作,这些信息在用户和内核空间之间作为一个数据流进行通信:

  • 明文或密文
  • 关联的身份验证数据 (AAD:associated authentication data)
  • authentication 标签(authentication tag)

AAD和authentication标签的大小由sendmsg和setsockopt调用提供(参见此处)。由于内核知道整个数据流的大小,内核现在能够计算数据流中数据分量的正确偏移量。

用户空间调用方必须按以下顺序排列上述信息:

  • AEAD 加密输入:AAD ||明文
  • AEAD解密输入:AAD ||密文 ||身份验证标记

用户空间调用者提供的输出缓冲区必须至少足够大以容纳以下数据:

  • AEAD加密输出:密文||authentication 标签
  • AEAD解密输出:明文

随机数生成器 API

同样,该操作与其他 API 非常相似。在初始化过程中,必须按如下方式填充 struct sockaddr 数据结构:

struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type = "rng", /* this selects the random number generator */
    .salg_name = "drbg_nopr_sha256" /* this is the RNG name */
};

根据随机数生成器(RNG)的类型,RNG必须进行种子初始化。种子是使用setsockopt接口设置密钥时提供的。例如,ansi_cprng需要一个种子。DRBGs不需要种子,但可以进行种子初始化。在NIST SP 800-90A标准中,种子也被称为个性化字符串(Personalization String)。

使用 read()/recvmsg() 系统调用,可以获得随机数。内核在一次调用中最多生成 128 个字节。如果用户空间需要更多数据,则必须多次调用 read()/recvmsg()。

警告:用户空间调用方可能会多次调用最初提到的 accept 系统调用。在这种情况下,返回的文件描述符具有相同的状态。

当使用CRYPTO_USER_API_RNG_CAVP选项构建内核时,将启用以下 CAVP 测试接口:

  • 可以通过ALG_SET_DRBG_ENTROPY setsockopt接口将熵和Nonce的串联提供给RNG。设置熵需要CAP_SYS_ADMIN权限。
  • 可以使用send() / sendmsg()系统调用提供附加数据,但必须在熵设置后才能提供。

零拷贝接口

除了 send/write/read/recv 系统调用系列之外,还可以使用 splice/vmsplice 的零拷贝接口访问 AF_ALG 接口。顾名思义,内核试图避免对内核空间进行复制操作。

零复制操作要求数据对齐到页边界。也可以使用非对齐数据,但可能需要内核进行更多的操作,这将抵消零拷贝接口带来的速度提升。

一个零拷贝操作的系统固有限制大小为16页。如果要发送更多数据到AF_ALG,则用户空间必须将输入分成最大为16页的多个片段。

零拷贝可以与以下代码示例一起使用(libkcapi 提供了完整的工作示例):

int pipes[2];

pipe(pipes);
/* input data in iov */
vmsplice(pipes[1], iov, iovlen, SPLICE_F_GIFT);
/* opfd is the file descriptor returned from accept() system call */
splice(pipes[0], NULL, opfd, NULL, ret, 0);
read(opfd, out, outlen);

Setsockopt 接口

除了 read/recv 和 send/write 系统调用用于发送和获取经过密码操作的数据之外,使用者还需要设置密码操作的附加信息。此附加信息使用 setsockopt 系统调用进行设置,必须使用打开的密码文件描述符(即由接受系统调用返回的文件描述符)来调用该系统调用。

每个 setsockopt 调用都必须使用级别SOL_ALG。

setsockopt 接口允许使用提到的 optname 设置以下数据:

  • ALG_SET_KEY – 设置密钥。密钥设置适用于以下密码类型:

    • skcipher 密码类型(对称密码)
    • hash 密码类型(带密钥的消息摘要)
    • AEAD 密码类型
    • RNG 密码类型以提供种子
  • ALG_SET_KEY_BY_KEY_SERIAL – 通过 keyring key_serial_t 设置密钥。
    此操作与 ALG_SET_KEY 相同。解密的数据从密钥环的密钥中拷贝,并将其作为对称加密的密钥使用。
    传入的 key_serial_t 必须具有 KEY_(POS|USR|GRP|OTH)_SEARCH 权限设置,否则会返回 -EPERM。支持的密钥类型包括用户、登录、加密和可信键类型。

  • ALG_SET_AEAD_AUTHSIZE – 设置 AEAD 密码中的认证标签大小。对于加密操作,将生成给定大小的认证标签。对于解密操作,则假定提供的密文包含给定大小的认证标签(请参阅下面关于 AEAD 内存布局的部分)。

  • ALG_SET_DRBG_ENTROPY – 设置随机数生成器的熵。此选项仅适用于 RNG 密码类型。

用户空间 API 示例

请参见 [1] 中的 libkcapi,它提供了一个易于使用的包装器,可方便地使用上述的 Netlink 内核接口。[1] 还包含一个测试应用程序,该应用程序调用了所有的 libkcapi API 调用。

[1] https://www.chronox.de/libkcapi.html

ref: https://www.kernel.org/doc/html/latest/crypto/userspace-if.html

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值