Crypto API - 5. 非对称/公钥加密密钥类型

Crypto API - 5. 非对称/公钥加密密钥类型

概述

“非对称”(asymmetric)密钥类型被设计为公钥加密中使用的密钥的容器,而不对加密的形式或机制或密钥的形式施加任何特殊限制。

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

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

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

数据解析器可以将数据块解释为包含表示密钥的位,也可以将其解释为对系统中其他位置(例如 TPM)上保存的密钥的引用。

密钥识别(Key Identification)

如果添加了一个空名称的密钥,则实例化数据分析器有机会预分析密钥,并根据密钥的内容确定应为密钥提供的描述。

然后可以使用完全匹配或部分匹配来引用该密钥。密钥类型(key type)还可以使用其他标准来引用密钥。

然后,非对称键类型的匹配函数可以执行更广泛的比较,而不仅仅是将描述与条件字符串进行直接比较:

如果条件字符串的格式是 “id: <十六进制数字>”, 那么匹配函数将检查密钥的指纹,以查看在 "id:"之后给出的十六进制数字是否与尾部匹配。例如:

keyctl search @s asymmetric id:5acc2142

密钥将与指纹匹配:

1A00 2040 7601 7889 DE11  882C 3823 04AD 5ACC 2142

如果条件字符串的格式是 “:”,则匹配项将与(1)中的 ID 匹配,但增加了限制,即仅匹配指定子类型(例如 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. 签名验证(Signature verification)

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

签名验证

提供一种操作来执行加密签名验证,使用非对称密钥来提供或提供对公钥的访问:

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

调用方必须已从某个源获取密钥,然后可以使用它来检查签名。调用方必须已解析签名并将相关位(bit)传输到 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。如果密钥参数类型错误或设置不完整,则可能返回 -EINVAL。

非对称密钥子类型

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

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

子类型定义结构可在以下位置找到:

#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] 成员指向此。

“owner”和“name”字段应设置为所属模块和子类型的名称。目前,该名称仅用于打印语句。

子类型定义了许多操作:

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

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

  3. query().
    必需的。这是一个查询密钥功能的函数。

  4. eds_op().
    可选的。这是加密、解密和签名创建操作的入口点(由参数 struct 中的操作ID区分)。子类型可以采取任何喜欢的方式来实现操作,包括卸载到硬件(offloading to hardware)。

  5. verify_signature().
    可选的。这是签名验证的入口点。子类型可以采取任何喜欢的方式来实现操作,包括卸载到硬件。

实例化数据解析器

通常情况下,非对称密钥类型不希望存储或处理包含密钥数据的原始blob数据。每次想要使用它时,都需要对其进行解析和错误检查。此外,可以对blob的内容进行各种检查(例如自签名、有效日期),可能包含有关密钥的有用数据(标识符、能力)。

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

可以实现分析程序的 blob 格式示例包括:

  • OpenPGP packet stream [RFC 4880].
  • X.509 ASN.1 stream.
  • Pointer to TPM key.
  • Pointer to UEFI key.
  • PKCS#8 private key [RFC 5208].
  • PKCS#5 encrypted private key [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);
};

“owner”和“name”字段应设置为所属模块和解析器的名称。

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

  1. parse().
    调用此函数以从密钥创建和更新路径中预分析密钥。具体而言,在密钥创建期间 before 分配密钥之前调用它,因此,允许在调用方拒绝这样做的情况下提供密钥的描述。

调用方传递指向以下结构的指针,其中清除了除 data、datalen 和 quotalen 之外的所有字段 [请参阅 Kernel Key Retention Service]:

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);

解析器的名称不能相同。名称仅用于在调试信息中显示。

密钥环链路限制(Keyring Link Restrictions)

使用 add_key 从用户空间创建的密匙环可以配置为检查所链接的密匙的签名。没有有效签名的密钥不允许链接。

有几个限制方法可用:

  1. 限制使用内核内置的受信任密钥环
  • Option string used with KEYCTL_RESTRICT_KEYRING: - “builtin_trusted”

    将在内核内置的受信任密钥环中搜索签名密钥。如果未配置内置的受信任密钥环,则所有链接都将被拒绝。ca_keys 内核参数还会影响用于签名验证的密钥。

  1. 限制使用内核内置密钥环和辅助受信任密钥环
  • Option string used with KEYCTL_RESTRICT_KEYRING: - “builtin_and_secondary_trusted”

    将在内核内置密钥环和辅助受信任密钥环中搜索签名密钥。如果未配置辅助受信任密钥环,则此限制的行为类似于“builtin_trusted”选项。ca_keys 内核参数还会影响用于签名验证的密钥。

  1. 限制使用单独的密钥或密钥环
  • Option string used with KEYCTL_RESTRICT_KEYRING: - “key_or_keyring:[:chain]”

    每当请求密钥链接时,只有当链接的密钥由指定的密钥之一签名时,链接才会成功。可以通过提供一个非对称密钥的序列号来直接指定此密钥,也可以通过提供密钥环的序列号来搜索一组密钥以查找签名密钥。

当在字符串末尾提供“chain”选项时,还将搜索目标密钥环中的密钥以查找签名密钥。这允许通过将每个证书按顺序(从最接近根开始)添加到密钥环来验证证书链。例如,一个密钥环可以填充指向一组根证书的链接,并为每个要验证的证书链设置一个单独的受限密钥环:

# Create and populate a keyring for root certificates
root_id=`keyctl add keyring root-certs "" @s`
keyctl padd asymmetric "" $root_id < root1.cert
keyctl padd asymmetric "" $root_id < root2.cert

# Create and restrict a keyring for the certificate chain
chain_id=`keyctl add keyring chain "" @s`
keyctl restrict_keyring $chain_id asymmetric key_or_keyring:$root_id:chain

# Attempt to add each certificate in the chain, starting with the
# certificate closest to the root.
keyctl padd asymmetric "" $chain_id < intermediateA.cert
keyctl padd asymmetric "" $chain_id < intermediateB.cert
keyctl padd asymmetric "" $chain_id < end-entity.cert

如果最终实体证书成功添加到“链”密钥环中,我们可以确定它具有返回到其中一个根证书的有效签名链。

单个密钥环可用于验证签名链,方法是在链接根证书后限制密钥环:

# Create a keyring for the certificate chain and add the root
chain2_id=`keyctl add keyring chain2 "" @s`
keyctl padd asymmetric "" $chain2_id < root1.cert

# Restrict the keyring that already has root1.cert linked.  The cert
# will remain linked by the keyring.
keyctl restrict_keyring $chain2_id asymmetric key_or_keyring:0:chain

# Attempt to add each certificate in the chain, starting with the
# certificate closest to the root.
keyctl padd asymmetric "" $chain2_id < intermediateA.cert
keyctl padd asymmetric "" $chain2_id < intermediateB.cert
keyctl padd asymmetric "" $chain2_id < end-entity.cert

如果最终实体证书成功添加到“chain2”密钥环中,我们可以确定存在一个有效的签名链,该签名链可以追溯到在密钥环受到限制之前添加的根证书。

在所有这些情况下,如果找到签名密钥,将使用签名密钥验证要链接的密钥的签名。仅当签名成功验证时,才会将请求的密钥添加到密钥环中。如果找不到父证书,则返回 -ENOKEY,如果签名检查失败或密钥被列入黑名单,则返回 -EKEYREJECTED。如果无法执行签名检查,则可能会返回其他错误。

ref: https://www.kernel.org/doc/html/latest/crypto/asymmetric-keys.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值