非对称/公钥密码密钥类型¶
概述¶
“非对称”密钥类型旨在作为公钥密码术中使用的密钥的容器,而不对密码术的形式或机制或密钥的形式施加任何特定限制。
非对称密钥被赋予一个子类型,该子类型定义了与密钥关联的数据类型,并提供了描述和销毁它的操作。但是,没有要求密钥数据实际存储在密钥中。
可以定义一个完全在内核中的密钥保留和操作子类型,但是也可以提供对加密硬件(例如TPM)的访问,该硬件可用于保留相关密钥并使用该密钥执行操作。在这种情况下,非对称密钥将仅仅是TPM驱动程序的接口。
还提供了数据解析器的概念。数据解析器负责从传递给实例化函数的数据块中提取信息。第一个识别该数据块的数据解析器将设置密钥的子类型,并定义可以对该密钥执行的操作。
数据解析器可以将数据块解释为包含表示密钥的位,也可以将其解释为对系统中其他位置保存的密钥的引用(例如,TPM)。
密钥识别¶
如果添加一个空名称的密钥,则实例化数据解析器将有机会预先解析密钥,并从密钥的内容中确定应给予密钥的描述。
然后可以使用它通过完全匹配或部分匹配来引用密钥。密钥类型也可以使用其他条件来引用密钥。
然后,非对称密钥类型的匹配函数可以执行比简单地将描述与条件字符串进行比较更广泛的比较
如果条件字符串的形式为“id:<hexdigits>”,则匹配函数将检查密钥的指纹,以查看“id:”之后的十六进制数字是否与尾部匹配。例如
keyctl search @s asymmetric id:5acc2142将匹配具有指纹的密钥
1A00 2040 7601 7889 DE11 882C 3823 04AD 5ACC 2142如果条件字符串的形式为“<subtype>:<hexdigits>”,则匹配将匹配(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具有任意证书标识符。
在密钥上定义的操作是
签名验证。
使用验证所需的相同密钥数据,可以进行其他操作(例如加密),但目前不支持,而其他操作(例如解密和签名生成)则需要额外的密钥数据。
签名验证¶
提供了一个操作来执行加密签名验证,使用非对称密钥来提供或提供对公钥的访问
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。如果密钥参数类型错误或未完全设置,则可以返回-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]成员指向此。
owner和name字段应设置为所有者模块和子类型的名称。当前,名称仅用于打印语句。
子类型定义了许多操作
describe()。
强制性。这允许子类型在/proc/keys中显示密钥的内容。例如,可以显示公钥算法类型的名称。密钥类型将在此之后显示密钥标识字符串的尾部。
destroy()。
强制性。这应该释放与密钥关联的内存。非对称密钥将负责释放指纹并释放对子类型模块的引用。
query()。
强制性。这是一个用于查询密钥功能的函数。
eds_op()。
可选。这是加密、解密和签名创建操作的入口点(这些操作通过参数结构中的操作ID来区分)。子类型可以执行它喜欢的任何操作来实现操作,包括卸载到硬件。
verify_signature()。
可选。这是签名验证的入口点。子类型可以执行它喜欢的任何操作来实现操作,包括卸载到硬件。
实例化数据解析器¶
非对称密钥类型通常不想存储或处理保存密钥数据的原始数据块。它必须在每次想要使用它时解析它并进行错误检查。此外,数据块的内容可能具有可以对其执行的各种检查(例如,自签名、有效期),并且可能包含有关密钥的有用数据(标识符、功能)。
此外,该数据块可能表示指向包含密钥的某些硬件的指针,而不是密钥本身。
可以实现解析器的块格式示例包括
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);
};
owner和name字段应设置为所有者模块和解析器的名称。
当前,解析器只定义了一个操作,并且是强制性的
parse()。
调用此方法以从密钥创建和更新路径预解析密钥。特别是,它在密钥分配_之前_在密钥创建期间调用,因此,如果调用者拒绝这样做,则允许它提供密钥的描述。
调用者将指向以下结构体的指针传递给所有字段都已清除(除了data、datalen和quotalen)[请参阅内核密钥保留服务]
struct key_preparsed_payload { char *description; void *payload[4]; const void *data; size_t datalen; size_t quotalen; };实例化数据位于data指向的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);
解析器可能没有相同的名称。否则,名称仅用于在调试消息中显示。
密钥环链接限制¶
使用 add_key 从用户空间创建的密钥环可以配置为检查正在链接的密钥的签名。不允许链接没有有效签名的密钥。
有几种限制方法可用
使用内核内置的受信任密钥环进行限制
与 KEYCTL_RESTRICT_KEYRING 一起使用的选项字符串: - “builtin_trusted”
将在内核内置的受信任密钥环中搜索签名密钥。 如果未配置内置受信任密钥环,则所有链接都将被拒绝。 ca_keys 内核参数也会影响哪些密钥用于签名验证。
使用内核内置和辅助受信任密钥环进行限制
与 KEYCTL_RESTRICT_KEYRING 一起使用的选项字符串: - “builtin_and_secondary_trusted”
将在内核内置和辅助受信任密钥环中搜索签名密钥。 如果未配置辅助受信任密钥环,则此限制的行为将类似于“builtin_trusted”选项。 ca_keys 内核参数也会影响哪些密钥用于签名验证。
使用单独的密钥或密钥环进行限制
与 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如果最终的实体证书已成功添加到“chain”密钥环,我们可以确定它具有返回到其中一个根证书的有效签名链。
一个密钥环可以通过在链接根证书后限制密钥环来验证签名链
# 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。 如果无法执行签名检查,则可能会返回其他错误。