非对称/公钥密码密钥类型

概述

“非对称”密钥类型旨在成为公钥密码术中使用的密钥的容器,而不对密码术的形式或机制或密钥的形式施加任何特定限制。

非对称密钥被赋予一个子类型,该子类型定义了什么类型的数据与该密钥关联,并提供了描述和销毁该数据的操作。但是,并不要求密钥数据实际存储在密钥中。

可以定义一个完全在内核内的密钥保留和操作子类型,但也可以提供对密码硬件(例如 TPM)的访问,该硬件可用于保留相关密钥并使用该密钥执行操作。在这种情况下,非对称密钥将仅仅是 TPM 驱动程序的接口。

还提供了数据解析器的概念。数据解析器负责从传递给实例化函数的数据 blob 中提取信息。第一个识别 blob 的数据解析器可以设置密钥的子类型并定义可以对该密钥执行的操作。

数据解析器可以将数据 blob 解释为包含表示密钥的位,或者将其解释为对系统中其他位置持有的密钥的引用(例如,TPM)。

密钥识别

如果添加的密钥具有空名称,则实例化数据解析器有机会预解析密钥并从密钥内容中确定应给出的密钥描述。

然后,可以使用完整匹配或部分匹配来引用密钥。密钥类型也可以使用其他标准来引用密钥。

非对称密钥类型的匹配函数可以执行比直接比较描述与标准字符串更广泛的比较。

  1. 如果标准字符串的形式为“id:<hexdigits>”,则匹配函数将检查密钥的指纹,以查看“id:”之后给出的十六进制数字是否与尾部匹配。例如

    keyctl search @s asymmetric id:5acc2142
    

    将匹配具有指纹的密钥

    1A00 2040 7601 7889 DE11  882C 3823 04AD 5ACC 2142
    
  2. 如果标准字符串的形式为“<subtype>:<hexdigits>”,则匹配将匹配 ID,如 (1) 中所示,但添加了仅匹配指定子类型(例如 tpm)的密钥的限制。例如

    keyctl search @s asymmetric tpm:5acc2142
    

在 /proc/keys 中查找,密钥指纹的最后 8 个十六进制数字与子类型一起显示

1a39e171 I-----     1 perm 3f010000     0     0 asymmetric modsign.0: DSA 5acc2142 []

访问非对称密钥

对于从内核中对非对称密钥的常规访问,需要包含以下内容

#include <crypto/public_key.h>

这提供了用于处理非对称/公钥的函数。那里定义了三个枚举,用于表示公钥密码算法

enum pkey_algo

这些算法使用的摘要算法

enum pkey_hash_algo

和密钥标识符表示

enum pkey_id_type

请注意,需要密钥类型表示类型,因为来自不同标准的密钥标识符不一定兼容。例如,PGP 通过散列密钥数据加上一些 PGP 特定的元数据来生成密钥标识符,而 X.509 具有任意证书标识符。

密钥上定义的操作是

  1. 签名验证。

其他操作(例如加密)可以使用与验证所需的相同密钥数据,但目前不支持,其他操作(例如解密和签名生成)需要额外的密钥数据。

签名验证

提供了一个操作来执行密码签名验证,使用非对称密钥来提供或访问公钥

int verify_signature(const struct key *key,
                     const struct public_key_signature *sig);

调用者必须已经从某个来源获得了密钥,然后可以使用它来检查签名。调用者必须已解析签名并将相关位传输到 sig 指向的结构。

struct public_key_signature {
        u8 *digest;
        u8 digest_size;
        enum pkey_hash_algo pkey_hash_algo : 8;
        u8 nr_mpi;
        union {
                MPI mpi[2];
                ...
        };
};

使用的算法必须在 sig->pkey_hash_algo 中注明,并且构成实际签名的所有 MPI 必须存储在 sig->mpi[] 中,并且 MPI 的计数必须放置在 sig->nr_mpi 中。

此外,数据必须已被调用者摘要,并且生成的哈希必须由 sig->digest 指向,并且哈希的大小必须放置在 sig->digest_size 中。

如果签名匹配,该函数将返回 0,否则返回 -EKEYREJECTED。

如果指定了不受支持的公钥算法或公钥/哈希算法组合,或者密钥不支持该操作,则该函数也可能返回 -ENOTSUPP;如果某些参数具有奇怪的数据,则返回 -EBADMSG 或 -ERANGE;或者如果无法执行分配,则返回 -ENOMEM。如果 key 参数类型错误或未完全设置,则可以返回 -EINVAL。

非对称密钥子类型

非对称密钥具有一个子类型,该子类型定义了可以对该密钥执行的一组操作,并确定附加为密钥有效负载的数据。有效负载格式完全由子类型决定。

子类型由密钥数据解析器选择,解析器必须初始化所需的数据。非对称密钥保留对子类型模块的引用。

子类型定义结构可以在中找到

#include <keys/asymmetric-subtype.h>

看起来像下面这样

struct asymmetric_key_subtype {
        struct module           *owner;
        const char              *name;

        void (*describe)(const struct key *key, struct seq_file *m);
        void (*destroy)(void *payload);
        int (*query)(const struct kernel_pkey_params *params,
                     struct kernel_pkey_query *info);
        int (*eds_op)(struct kernel_pkey_params *params,
                      const void *in, void *out);
        int (*verify_signature)(const struct key *key,
                                const struct public_key_signature *sig);
};

非对称密钥使用其 payload[asym_subtype] 成员指向此结构。

所有者和名称字段应设置为拥有的模块和子类型的名称。目前,名称仅用于打印语句。

子类型定义了许多操作

  1. describe()。

    必需。这允许子类型在 /proc/keys 中针对密钥显示一些内容。例如,可以显示公钥算法类型的名称。密钥类型将在此之后显示密钥标识字符串的尾部。

  2. destroy()。

    必需。这应该释放与密钥关联的内存。非对称密钥将负责释放指纹并释放对子类型模块的引用。

  3. query()。

    必需。这是一个用于查询密钥功能的函数。

  4. eds_op()。

    可选。这是加密、解密和签名创建操作的入口点(这些操作通过参数结构中的操作 ID 来区分)。子类型可以执行任何它喜欢的操作来实现操作,包括卸载到硬件。

  5. verify_signature()。

    可选。这是签名验证的入口点。子类型可以执行任何它喜欢的操作来实现操作,包括卸载到硬件。

实例化数据解析器

非对称密钥类型通常不想存储或处理保存密钥数据的原始数据 blob。每次想要使用它时,它都必须解析它并进行错误检查。此外,blob 的内容可能具有可以对其执行的各种检查(例如,自签名、有效期),并且可能包含有关密钥的有用数据(标识符、功能)。

此外,blob 可能表示指向包含密钥的某些硬件的指针,而不是密钥本身。

可以实现解析器的 blob 格式示例包括

  • OpenPGP 数据包流 [RFC 4880]。

  • X.509 ASN.1 流。

  • 指向 TPM 密钥的指针。

  • 指向 UEFI 密钥的指针。

  • PKCS#8 私钥 [RFC 5208]。

  • PKCS#5 加密私钥 [RFC 2898]。

在密钥实例化期间,将尝试列表中的每个解析器,直到有一个不返回 -EBADMSG。

解析器定义结构可以在中找到

#include <keys/asymmetric-parser.h>

看起来像下面这样

struct asymmetric_key_parser {
        struct module   *owner;
        const char      *name;

        int (*parse)(struct key_preparsed_payload *prep);
};

所有者和名称字段应设置为拥有的模块和解析器的名称。

目前解析器只定义了一个操作,它是必需的

  1. parse()。

    调用此函数以从密钥创建和更新路径中预解析密钥。特别是,它在分配密钥_之前_在密钥创建期间被调用,因此,如果调用者拒绝这样做,则允许提供密钥的描述。

    调用者传递一个指向以下结构的指针,其中除数据、datalen 和 quotalen 之外的所有字段都已清除 [请参阅内核密钥保留服务]

    struct key_preparsed_payload {
            char            *description;
            void            *payload[4];
            const void      *data;
            size_t          datalen;
            size_t          quotalen;
    };
    

    实例化数据位于数据指向的 blob 中,大小为 datalen。不允许 parse() 函数完全更改这两个值,并且不应更改任何其他值_除非_它们识别 blob 格式,并且不会返回 -EBADMSG 来指示它不是它们的。

    如果解析器对 blob 感到满意,它应该为密钥提出一个描述并将其附加到 ->description,->payload[asym_subtype] 应设置为指向要使用的子类型,->payload[asym_crypto] 应设置为指向该子类型的已初始化数据,->payload[asym_key_ids] 应指向一个或多个十六进制指纹,并且应更新 quotalen 以指示此密钥应占用多少配额。

    清除时,附加到 ->payload[asym_key_ids] 和 ->description 的数据将被kfree(),并且附加到 ->payload[asm_crypto] 的数据将被传递给子类型的 ->destroy() 方法进行处理。将放置 ->payload[asym_subtype] 指向的子类型的模块引用。

    如果无法识别数据格式,则应返回 -EBADMSG。如果已识别,但由于某种原因无法设置密钥,则应返回其他一些负错误代码。成功时,应返回 0。

    密钥的指纹字符串可能会被部分匹配。对于诸如 RSA 和 DSA 之类的公钥算法,这很可能是密钥指纹的可打印十六进制版本。

提供了注册和注销解析器的函数

int register_asymmetric_key_parser(struct asymmetric_key_parser *parser);
void unregister_asymmetric_key_parser(struct asymmetric_key_parser *subtype);

解析器可能没有相同的名称。否则,这些名称仅用于在调试消息中显示。