diff options
Diffstat (limited to 'crypto/asymmetric_keys')
22 files changed, 4655 insertions, 0 deletions
diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig new file mode 100644 index 000000000..d8a73d94b --- /dev/null +++ b/crypto/asymmetric_keys/Kconfig @@ -0,0 +1,68 @@ +# SPDX-License-Identifier: GPL-2.0 +menuconfig ASYMMETRIC_KEY_TYPE + bool "Asymmetric (public-key cryptographic) key type" + depends on KEYS + help + This option provides support for a key type that holds the data for + the asymmetric keys used for public key cryptographic operations such + as encryption, decryption, signature generation and signature + verification. + +if ASYMMETRIC_KEY_TYPE + +config ASYMMETRIC_PUBLIC_KEY_SUBTYPE + tristate "Asymmetric public-key crypto algorithm subtype" + select MPILIB + select CRYPTO_HASH_INFO + select CRYPTO_AKCIPHER + select CRYPTO_HASH + help + This option provides support for asymmetric public key type handling. + If signature generation and/or verification are to be used, + appropriate hash algorithms (such as SHA-1) must be available. + ENOPKG will be reported if the requisite algorithm is unavailable. + +config X509_CERTIFICATE_PARSER + tristate "X.509 certificate parser" + depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE + select ASN1 + select OID_REGISTRY + help + This option provides support for parsing X.509 format blobs for key + data and provides the ability to instantiate a crypto key from a + public key packet found inside the certificate. + +config PKCS7_MESSAGE_PARSER + tristate "PKCS#7 message parser" + depends on X509_CERTIFICATE_PARSER + select CRYPTO_HASH + select ASN1 + select OID_REGISTRY + help + This option provides support for parsing PKCS#7 format messages for + signature data and provides the ability to verify the signature. + +config PKCS7_TEST_KEY + tristate "PKCS#7 testing key type" + depends on SYSTEM_DATA_VERIFICATION + help + This option provides a type of key that can be loaded up from a + PKCS#7 message - provided the message is signed by a trusted key. If + it is, the PKCS#7 wrapper is discarded and reading the key returns + just the payload. If it isn't, adding the key will fail with an + error. + + This is intended for testing the PKCS#7 parser. + +config SIGNED_PE_FILE_VERIFICATION + bool "Support for PE file signature verification" + depends on PKCS7_MESSAGE_PARSER=y + depends on SYSTEM_DATA_VERIFICATION + select CRYPTO_HASH + select ASN1 + select OID_REGISTRY + help + This option provides support for verifying the signature(s) on a + signed PE binary. + +endif # ASYMMETRIC_KEY_TYPE diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile new file mode 100644 index 000000000..d4b2e1b2d --- /dev/null +++ b/crypto/asymmetric_keys/Makefile @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for asymmetric cryptographic keys +# + +obj-$(CONFIG_ASYMMETRIC_KEY_TYPE) += asymmetric_keys.o + +asymmetric_keys-y := \ + asymmetric_type.o \ + restrict.o \ + signature.o + +obj-$(CONFIG_ASYMMETRIC_PUBLIC_KEY_SUBTYPE) += public_key.o + +# +# X.509 Certificate handling +# +obj-$(CONFIG_X509_CERTIFICATE_PARSER) += x509_key_parser.o +x509_key_parser-y := \ + x509.asn1.o \ + x509_akid.asn1.o \ + x509_cert_parser.o \ + x509_public_key.o + +$(obj)/x509_cert_parser.o: \ + $(obj)/x509.asn1.h \ + $(obj)/x509_akid.asn1.h + +$(obj)/x509.asn1.o: $(obj)/x509.asn1.c $(obj)/x509.asn1.h +$(obj)/x509_akid.asn1.o: $(obj)/x509_akid.asn1.c $(obj)/x509_akid.asn1.h + +# +# PKCS#7 message handling +# +obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o +pkcs7_message-y := \ + pkcs7.asn1.o \ + pkcs7_parser.o \ + pkcs7_trust.o \ + pkcs7_verify.o + +$(obj)/pkcs7_parser.o: $(obj)/pkcs7.asn1.h +$(obj)/pkcs7.asn1.o: $(obj)/pkcs7.asn1.c $(obj)/pkcs7.asn1.h + +# +# PKCS#7 parser testing key +# +obj-$(CONFIG_PKCS7_TEST_KEY) += pkcs7_test_key.o +pkcs7_test_key-y := \ + pkcs7_key_type.o + +# +# Signed PE binary-wrapped key handling +# +obj-$(CONFIG_SIGNED_PE_FILE_VERIFICATION) += verify_signed_pefile.o + +verify_signed_pefile-y := \ + verify_pefile.o \ + mscode_parser.o \ + mscode.asn1.o + +$(obj)/mscode_parser.o: $(obj)/mscode.asn1.h $(obj)/mscode.asn1.h +$(obj)/mscode.asn1.o: $(obj)/mscode.asn1.c $(obj)/mscode.asn1.h diff --git a/crypto/asymmetric_keys/asymmetric_keys.h b/crypto/asymmetric_keys/asymmetric_keys.h new file mode 100644 index 000000000..ca8e9ac34 --- /dev/null +++ b/crypto/asymmetric_keys/asymmetric_keys.h @@ -0,0 +1,18 @@ +/* Internal definitions for asymmetric key type + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <keys/asymmetric-type.h> + +extern struct asymmetric_key_id *asymmetric_key_hex_to_key_id(const char *id); + +extern int __asymmetric_key_hex_to_key_id(const char *id, + struct asymmetric_key_id *match_id, + size_t hexlen); diff --git a/crypto/asymmetric_keys/asymmetric_type.c b/crypto/asymmetric_keys/asymmetric_type.c new file mode 100644 index 000000000..26539e9a8 --- /dev/null +++ b/crypto/asymmetric_keys/asymmetric_type.c @@ -0,0 +1,613 @@ +/* Asymmetric public-key cryptography key type + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ +#include <keys/asymmetric-subtype.h> +#include <keys/asymmetric-parser.h> +#include <crypto/public_key.h> +#include <linux/seq_file.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/ctype.h> +#include <keys/system_keyring.h> +#include "asymmetric_keys.h" + +MODULE_LICENSE("GPL"); + +const char *const key_being_used_for[NR__KEY_BEING_USED_FOR] = { + [VERIFYING_MODULE_SIGNATURE] = "mod sig", + [VERIFYING_FIRMWARE_SIGNATURE] = "firmware sig", + [VERIFYING_KEXEC_PE_SIGNATURE] = "kexec PE sig", + [VERIFYING_KEY_SIGNATURE] = "key sig", + [VERIFYING_KEY_SELF_SIGNATURE] = "key self sig", + [VERIFYING_UNSPECIFIED_SIGNATURE] = "unspec sig", +}; +EXPORT_SYMBOL_GPL(key_being_used_for); + +static LIST_HEAD(asymmetric_key_parsers); +static DECLARE_RWSEM(asymmetric_key_parsers_sem); + +/** + * find_asymmetric_key - Find a key by ID. + * @keyring: The keys to search. + * @id_0: The first ID to look for or NULL. + * @id_1: The second ID to look for or NULL. + * @partial: Use partial match if true, exact if false. + * + * Find a key in the given keyring by identifier. The preferred identifier is + * the id_0 and the fallback identifier is the id_1. If both are given, the + * lookup is by the former, but the latter must also match. + */ +struct key *find_asymmetric_key(struct key *keyring, + const struct asymmetric_key_id *id_0, + const struct asymmetric_key_id *id_1, + bool partial) +{ + struct key *key; + key_ref_t ref; + const char *lookup; + char *req, *p; + int len; + + BUG_ON(!id_0 && !id_1); + + if (id_0) { + lookup = id_0->data; + len = id_0->len; + } else { + lookup = id_1->data; + len = id_1->len; + } + + /* Construct an identifier "id:<keyid>". */ + p = req = kmalloc(2 + 1 + len * 2 + 1, GFP_KERNEL); + if (!req) + return ERR_PTR(-ENOMEM); + + if (partial) { + *p++ = 'i'; + *p++ = 'd'; + } else { + *p++ = 'e'; + *p++ = 'x'; + } + *p++ = ':'; + p = bin2hex(p, lookup, len); + *p = 0; + + pr_debug("Look up: \"%s\"\n", req); + + ref = keyring_search(make_key_ref(keyring, 1), + &key_type_asymmetric, req); + if (IS_ERR(ref)) + pr_debug("Request for key '%s' err %ld\n", req, PTR_ERR(ref)); + kfree(req); + + if (IS_ERR(ref)) { + switch (PTR_ERR(ref)) { + /* Hide some search errors */ + case -EACCES: + case -ENOTDIR: + case -EAGAIN: + return ERR_PTR(-ENOKEY); + default: + return ERR_CAST(ref); + } + } + + key = key_ref_to_ptr(ref); + if (id_0 && id_1) { + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + + if (!kids->id[1]) { + pr_debug("First ID matches, but second is missing\n"); + goto reject; + } + if (!asymmetric_key_id_same(id_1, kids->id[1])) { + pr_debug("First ID matches, but second does not\n"); + goto reject; + } + } + + pr_devel("<==%s() = 0 [%x]\n", __func__, key_serial(key)); + return key; + +reject: + key_put(key); + return ERR_PTR(-EKEYREJECTED); +} +EXPORT_SYMBOL_GPL(find_asymmetric_key); + +/** + * asymmetric_key_generate_id: Construct an asymmetric key ID + * @val_1: First binary blob + * @len_1: Length of first binary blob + * @val_2: Second binary blob + * @len_2: Length of second binary blob + * + * Construct an asymmetric key ID from a pair of binary blobs. + */ +struct asymmetric_key_id *asymmetric_key_generate_id(const void *val_1, + size_t len_1, + const void *val_2, + size_t len_2) +{ + struct asymmetric_key_id *kid; + + kid = kmalloc(sizeof(struct asymmetric_key_id) + len_1 + len_2, + GFP_KERNEL); + if (!kid) + return ERR_PTR(-ENOMEM); + kid->len = len_1 + len_2; + memcpy(kid->data, val_1, len_1); + memcpy(kid->data + len_1, val_2, len_2); + return kid; +} +EXPORT_SYMBOL_GPL(asymmetric_key_generate_id); + +/** + * asymmetric_key_id_same - Return true if two asymmetric keys IDs are the same. + * @kid_1, @kid_2: The key IDs to compare + */ +bool asymmetric_key_id_same(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2) +{ + if (!kid1 || !kid2) + return false; + if (kid1->len != kid2->len) + return false; + return memcmp(kid1->data, kid2->data, kid1->len) == 0; +} +EXPORT_SYMBOL_GPL(asymmetric_key_id_same); + +/** + * asymmetric_key_id_partial - Return true if two asymmetric keys IDs + * partially match + * @kid_1, @kid_2: The key IDs to compare + */ +bool asymmetric_key_id_partial(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2) +{ + if (!kid1 || !kid2) + return false; + if (kid1->len < kid2->len) + return false; + return memcmp(kid1->data + (kid1->len - kid2->len), + kid2->data, kid2->len) == 0; +} +EXPORT_SYMBOL_GPL(asymmetric_key_id_partial); + +/** + * asymmetric_match_key_ids - Search asymmetric key IDs + * @kids: The list of key IDs to check + * @match_id: The key ID we're looking for + * @match: The match function to use + */ +static bool asymmetric_match_key_ids( + const struct asymmetric_key_ids *kids, + const struct asymmetric_key_id *match_id, + bool (*match)(const struct asymmetric_key_id *kid1, + const struct asymmetric_key_id *kid2)) +{ + int i; + + if (!kids || !match_id) + return false; + for (i = 0; i < ARRAY_SIZE(kids->id); i++) + if (match(kids->id[i], match_id)) + return true; + return false; +} + +/* helper function can be called directly with pre-allocated memory */ +inline int __asymmetric_key_hex_to_key_id(const char *id, + struct asymmetric_key_id *match_id, + size_t hexlen) +{ + match_id->len = hexlen; + return hex2bin(match_id->data, id, hexlen); +} + +/** + * asymmetric_key_hex_to_key_id - Convert a hex string into a key ID. + * @id: The ID as a hex string. + */ +struct asymmetric_key_id *asymmetric_key_hex_to_key_id(const char *id) +{ + struct asymmetric_key_id *match_id; + size_t asciihexlen; + int ret; + + if (!*id) + return ERR_PTR(-EINVAL); + asciihexlen = strlen(id); + if (asciihexlen & 1) + return ERR_PTR(-EINVAL); + + match_id = kmalloc(sizeof(struct asymmetric_key_id) + asciihexlen / 2, + GFP_KERNEL); + if (!match_id) + return ERR_PTR(-ENOMEM); + ret = __asymmetric_key_hex_to_key_id(id, match_id, asciihexlen / 2); + if (ret < 0) { + kfree(match_id); + return ERR_PTR(-EINVAL); + } + return match_id; +} + +/* + * Match asymmetric keys by an exact match on an ID. + */ +static bool asymmetric_key_cmp(const struct key *key, + const struct key_match_data *match_data) +{ + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + const struct asymmetric_key_id *match_id = match_data->preparsed; + + return asymmetric_match_key_ids(kids, match_id, + asymmetric_key_id_same); +} + +/* + * Match asymmetric keys by a partial match on an IDs. + */ +static bool asymmetric_key_cmp_partial(const struct key *key, + const struct key_match_data *match_data) +{ + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + const struct asymmetric_key_id *match_id = match_data->preparsed; + + return asymmetric_match_key_ids(kids, match_id, + asymmetric_key_id_partial); +} + +/* + * Preparse the match criterion. If we don't set lookup_type and cmp, + * the default will be an exact match on the key description. + * + * There are some specifiers for matching key IDs rather than by the key + * description: + * + * "id:<id>" - find a key by partial match on any available ID + * "ex:<id>" - find a key by exact match on any available ID + * + * These have to be searched by iteration rather than by direct lookup because + * the key is hashed according to its description. + */ +static int asymmetric_key_match_preparse(struct key_match_data *match_data) +{ + struct asymmetric_key_id *match_id; + const char *spec = match_data->raw_data; + const char *id; + bool (*cmp)(const struct key *, const struct key_match_data *) = + asymmetric_key_cmp; + + if (!spec || !*spec) + return -EINVAL; + if (spec[0] == 'i' && + spec[1] == 'd' && + spec[2] == ':') { + id = spec + 3; + cmp = asymmetric_key_cmp_partial; + } else if (spec[0] == 'e' && + spec[1] == 'x' && + spec[2] == ':') { + id = spec + 3; + } else { + goto default_match; + } + + match_id = asymmetric_key_hex_to_key_id(id); + if (IS_ERR(match_id)) + return PTR_ERR(match_id); + + match_data->preparsed = match_id; + match_data->cmp = cmp; + match_data->lookup_type = KEYRING_SEARCH_LOOKUP_ITERATE; + return 0; + +default_match: + return 0; +} + +/* + * Free the preparsed the match criterion. + */ +static void asymmetric_key_match_free(struct key_match_data *match_data) +{ + kfree(match_data->preparsed); +} + +/* + * Describe the asymmetric key + */ +static void asymmetric_key_describe(const struct key *key, struct seq_file *m) +{ + const struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key); + const struct asymmetric_key_ids *kids = asymmetric_key_ids(key); + const struct asymmetric_key_id *kid; + const unsigned char *p; + int n; + + seq_puts(m, key->description); + + if (subtype) { + seq_puts(m, ": "); + subtype->describe(key, m); + + if (kids && kids->id[1]) { + kid = kids->id[1]; + seq_putc(m, ' '); + n = kid->len; + p = kid->data; + if (n > 4) { + p += n - 4; + n = 4; + } + seq_printf(m, "%*phN", n, p); + } + + seq_puts(m, " ["); + /* put something here to indicate the key's capabilities */ + seq_putc(m, ']'); + } +} + +/* + * Preparse a asymmetric payload to get format the contents appropriately for the + * internal payload to cut down on the number of scans of the data performed. + * + * We also generate a proposed description from the contents of the key that + * can be used to name the key if the user doesn't want to provide one. + */ +static int asymmetric_key_preparse(struct key_preparsed_payload *prep) +{ + struct asymmetric_key_parser *parser; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (prep->datalen == 0) + return -EINVAL; + + down_read(&asymmetric_key_parsers_sem); + + ret = -EBADMSG; + list_for_each_entry(parser, &asymmetric_key_parsers, link) { + pr_debug("Trying parser '%s'\n", parser->name); + + ret = parser->parse(prep); + if (ret != -EBADMSG) { + pr_debug("Parser recognised the format (ret %d)\n", + ret); + break; + } + } + + up_read(&asymmetric_key_parsers_sem); + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +/* + * Clean up the key ID list + */ +static void asymmetric_key_free_kids(struct asymmetric_key_ids *kids) +{ + int i; + + if (kids) { + for (i = 0; i < ARRAY_SIZE(kids->id); i++) + kfree(kids->id[i]); + kfree(kids); + } +} + +/* + * Clean up the preparse data + */ +static void asymmetric_key_free_preparse(struct key_preparsed_payload *prep) +{ + struct asymmetric_key_subtype *subtype = prep->payload.data[asym_subtype]; + struct asymmetric_key_ids *kids = prep->payload.data[asym_key_ids]; + + pr_devel("==>%s()\n", __func__); + + if (subtype) { + subtype->destroy(prep->payload.data[asym_crypto], + prep->payload.data[asym_auth]); + module_put(subtype->owner); + } + asymmetric_key_free_kids(kids); + kfree(prep->description); +} + +/* + * dispose of the data dangling from the corpse of a asymmetric key + */ +static void asymmetric_key_destroy(struct key *key) +{ + struct asymmetric_key_subtype *subtype = asymmetric_key_subtype(key); + struct asymmetric_key_ids *kids = key->payload.data[asym_key_ids]; + void *data = key->payload.data[asym_crypto]; + void *auth = key->payload.data[asym_auth]; + + key->payload.data[asym_crypto] = NULL; + key->payload.data[asym_subtype] = NULL; + key->payload.data[asym_key_ids] = NULL; + key->payload.data[asym_auth] = NULL; + + if (subtype) { + subtype->destroy(data, auth); + module_put(subtype->owner); + } + + asymmetric_key_free_kids(kids); +} + +static struct key_restriction *asymmetric_restriction_alloc( + key_restrict_link_func_t check, + struct key *key) +{ + struct key_restriction *keyres = + kzalloc(sizeof(struct key_restriction), GFP_KERNEL); + + if (!keyres) + return ERR_PTR(-ENOMEM); + + keyres->check = check; + keyres->key = key; + keyres->keytype = &key_type_asymmetric; + + return keyres; +} + +/* + * look up keyring restrict functions for asymmetric keys + */ +static struct key_restriction *asymmetric_lookup_restriction( + const char *restriction) +{ + char *restrict_method; + char *parse_buf; + char *next; + struct key_restriction *ret = ERR_PTR(-EINVAL); + + if (strcmp("builtin_trusted", restriction) == 0) + return asymmetric_restriction_alloc( + restrict_link_by_builtin_trusted, NULL); + + if (strcmp("builtin_and_secondary_trusted", restriction) == 0) + return asymmetric_restriction_alloc( + restrict_link_by_builtin_and_secondary_trusted, NULL); + + parse_buf = kstrndup(restriction, PAGE_SIZE, GFP_KERNEL); + if (!parse_buf) + return ERR_PTR(-ENOMEM); + + next = parse_buf; + restrict_method = strsep(&next, ":"); + + if ((strcmp(restrict_method, "key_or_keyring") == 0) && next) { + char *key_text; + key_serial_t serial; + struct key *key; + key_restrict_link_func_t link_fn = + restrict_link_by_key_or_keyring; + bool allow_null_key = false; + + key_text = strsep(&next, ":"); + + if (next) { + if (strcmp(next, "chain") != 0) + goto out; + + link_fn = restrict_link_by_key_or_keyring_chain; + allow_null_key = true; + } + + if (kstrtos32(key_text, 0, &serial) < 0) + goto out; + + if ((serial == 0) && allow_null_key) { + key = NULL; + } else { + key = key_lookup(serial); + if (IS_ERR(key)) { + ret = ERR_CAST(key); + goto out; + } + } + + ret = asymmetric_restriction_alloc(link_fn, key); + if (IS_ERR(ret)) + key_put(key); + } + +out: + kfree(parse_buf); + return ret; +} + +struct key_type key_type_asymmetric = { + .name = "asymmetric", + .preparse = asymmetric_key_preparse, + .free_preparse = asymmetric_key_free_preparse, + .instantiate = generic_key_instantiate, + .match_preparse = asymmetric_key_match_preparse, + .match_free = asymmetric_key_match_free, + .destroy = asymmetric_key_destroy, + .describe = asymmetric_key_describe, + .lookup_restriction = asymmetric_lookup_restriction, +}; +EXPORT_SYMBOL_GPL(key_type_asymmetric); + +/** + * register_asymmetric_key_parser - Register a asymmetric key blob parser + * @parser: The parser to register + */ +int register_asymmetric_key_parser(struct asymmetric_key_parser *parser) +{ + struct asymmetric_key_parser *cursor; + int ret; + + down_write(&asymmetric_key_parsers_sem); + + list_for_each_entry(cursor, &asymmetric_key_parsers, link) { + if (strcmp(cursor->name, parser->name) == 0) { + pr_err("Asymmetric key parser '%s' already registered\n", + parser->name); + ret = -EEXIST; + goto out; + } + } + + list_add_tail(&parser->link, &asymmetric_key_parsers); + + pr_notice("Asymmetric key parser '%s' registered\n", parser->name); + ret = 0; + +out: + up_write(&asymmetric_key_parsers_sem); + return ret; +} +EXPORT_SYMBOL_GPL(register_asymmetric_key_parser); + +/** + * unregister_asymmetric_key_parser - Unregister a asymmetric key blob parser + * @parser: The parser to unregister + */ +void unregister_asymmetric_key_parser(struct asymmetric_key_parser *parser) +{ + down_write(&asymmetric_key_parsers_sem); + list_del(&parser->link); + up_write(&asymmetric_key_parsers_sem); + + pr_notice("Asymmetric key parser '%s' unregistered\n", parser->name); +} +EXPORT_SYMBOL_GPL(unregister_asymmetric_key_parser); + +/* + * Module stuff + */ +static int __init asymmetric_key_init(void) +{ + return register_key_type(&key_type_asymmetric); +} + +static void __exit asymmetric_key_cleanup(void) +{ + unregister_key_type(&key_type_asymmetric); +} + +module_init(asymmetric_key_init); +module_exit(asymmetric_key_cleanup); diff --git a/crypto/asymmetric_keys/mscode.asn1 b/crypto/asymmetric_keys/mscode.asn1 new file mode 100644 index 000000000..6d09ba48c --- /dev/null +++ b/crypto/asymmetric_keys/mscode.asn1 @@ -0,0 +1,28 @@ +--- Microsoft individual code signing data blob parser +--- +--- Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. +--- Written by David Howells (dhowells@redhat.com) +--- +--- This program is free software; you can redistribute it and/or +--- modify it under the terms of the GNU General Public Licence +--- as published by the Free Software Foundation; either version +--- 2 of the Licence, or (at your option) any later version. +--- + +MSCode ::= SEQUENCE { + type SEQUENCE { + contentType ContentType, + parameters ANY + }, + content SEQUENCE { + digestAlgorithm DigestAlgorithmIdentifier, + digest OCTET STRING ({ mscode_note_digest }) + } +} + +ContentType ::= OBJECT IDENTIFIER ({ mscode_note_content_type }) + +DigestAlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ mscode_note_digest_algo }), + parameters ANY OPTIONAL +} diff --git a/crypto/asymmetric_keys/mscode_parser.c b/crypto/asymmetric_keys/mscode_parser.c new file mode 100644 index 000000000..83d2e9b38 --- /dev/null +++ b/crypto/asymmetric_keys/mscode_parser.c @@ -0,0 +1,133 @@ +/* Parse a Microsoft Individual Code Signing blob + * + * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "MSCODE: "fmt +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/oid_registry.h> +#include <crypto/pkcs7.h> +#include "verify_pefile.h" +#include "mscode.asn1.h" + +/* + * Parse a Microsoft Individual Code Signing blob + */ +int mscode_parse(void *_ctx, const void *content_data, size_t data_len, + size_t asn1hdrlen) +{ + struct pefile_context *ctx = _ctx; + + content_data -= asn1hdrlen; + data_len += asn1hdrlen; + pr_devel("Data: %zu [%*ph]\n", data_len, (unsigned)(data_len), + content_data); + + return asn1_ber_decoder(&mscode_decoder, ctx, content_data, data_len); +} + +/* + * Check the content type OID + */ +int mscode_note_content_type(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + enum OID oid; + + oid = look_up_OID(value, vlen); + if (oid == OID__NR) { + char buffer[50]; + + sprint_oid(value, vlen, buffer, sizeof(buffer)); + pr_err("Unknown OID: %s\n", buffer); + return -EBADMSG; + } + + /* + * pesign utility had a bug where it was putting + * OID_msIndividualSPKeyPurpose instead of OID_msPeImageDataObjId + * So allow both OIDs. + */ + if (oid != OID_msPeImageDataObjId && + oid != OID_msIndividualSPKeyPurpose) { + pr_err("Unexpected content type OID %u\n", oid); + return -EBADMSG; + } + + return 0; +} + +/* + * Note the digest algorithm OID + */ +int mscode_note_digest_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pefile_context *ctx = context; + char buffer[50]; + enum OID oid; + + oid = look_up_OID(value, vlen); + switch (oid) { + case OID_md4: + ctx->digest_algo = "md4"; + break; + case OID_md5: + ctx->digest_algo = "md5"; + break; + case OID_sha1: + ctx->digest_algo = "sha1"; + break; + case OID_sha256: + ctx->digest_algo = "sha256"; + break; + case OID_sha384: + ctx->digest_algo = "sha384"; + break; + case OID_sha512: + ctx->digest_algo = "sha512"; + break; + case OID_sha224: + ctx->digest_algo = "sha224"; + break; + + case OID__NR: + sprint_oid(value, vlen, buffer, sizeof(buffer)); + pr_err("Unknown OID: %s\n", buffer); + return -EBADMSG; + + default: + pr_err("Unsupported content type: %u\n", oid); + return -ENOPKG; + } + + return 0; +} + +/* + * Note the digest we're guaranteeing with this certificate + */ +int mscode_note_digest(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pefile_context *ctx = context; + + ctx->digest = kmemdup(value, vlen, GFP_KERNEL); + if (!ctx->digest) + return -ENOMEM; + + ctx->digest_len = vlen; + + return 0; +} diff --git a/crypto/asymmetric_keys/pkcs7.asn1 b/crypto/asymmetric_keys/pkcs7.asn1 new file mode 100644 index 000000000..1eca740b8 --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7.asn1 @@ -0,0 +1,135 @@ +PKCS7ContentInfo ::= SEQUENCE { + contentType ContentType ({ pkcs7_check_content_type }), + content [0] EXPLICIT SignedData OPTIONAL +} + +ContentType ::= OBJECT IDENTIFIER ({ pkcs7_note_OID }) + +SignedData ::= SEQUENCE { + version INTEGER ({ pkcs7_note_signeddata_version }), + digestAlgorithms DigestAlgorithmIdentifiers, + contentInfo ContentInfo ({ pkcs7_note_content }), + certificates CHOICE { + certSet [0] IMPLICIT ExtendedCertificatesAndCertificates, + certSequence [2] IMPLICIT Certificates + } OPTIONAL ({ pkcs7_note_certificate_list }), + crls CHOICE { + crlSet [1] IMPLICIT CertificateRevocationLists, + crlSequence [3] IMPLICIT CRLSequence + } OPTIONAL, + signerInfos SignerInfos +} + +ContentInfo ::= SEQUENCE { + contentType ContentType ({ pkcs7_note_OID }), + content [0] EXPLICIT Data OPTIONAL +} + +Data ::= ANY ({ pkcs7_note_data }) + +DigestAlgorithmIdentifiers ::= CHOICE { + daSet SET OF DigestAlgorithmIdentifier, + daSequence SEQUENCE OF DigestAlgorithmIdentifier +} + +DigestAlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ pkcs7_note_OID }), + parameters ANY OPTIONAL +} + +-- +-- Certificates and certificate lists +-- +ExtendedCertificatesAndCertificates ::= SET OF ExtendedCertificateOrCertificate + +ExtendedCertificateOrCertificate ::= CHOICE { + certificate Certificate, -- X.509 + extendedCertificate [0] IMPLICIT ExtendedCertificate -- PKCS#6 +} + +ExtendedCertificate ::= Certificate -- cheating + +Certificates ::= SEQUENCE OF Certificate + +CertificateRevocationLists ::= SET OF CertificateList + +CertificateList ::= SEQUENCE OF Certificate -- This may be defined incorrectly + +CRLSequence ::= SEQUENCE OF CertificateList + +Certificate ::= ANY ({ pkcs7_extract_cert }) -- X.509 + +-- +-- Signer information +-- +SignerInfos ::= CHOICE { + siSet SET OF SignerInfo, + siSequence SEQUENCE OF SignerInfo +} + +SignerInfo ::= SEQUENCE { + version INTEGER ({ pkcs7_note_signerinfo_version }), + sid SignerIdentifier, -- CMS variant, not PKCS#7 + digestAlgorithm DigestAlgorithmIdentifier ({ pkcs7_sig_note_digest_algo }), + authenticatedAttributes CHOICE { + aaSet [0] IMPLICIT SetOfAuthenticatedAttribute + ({ pkcs7_sig_note_set_of_authattrs }), + aaSequence [2] EXPLICIT SEQUENCE OF AuthenticatedAttribute + -- Explicit because easier to compute digest on + -- sequence of attributes and then reuse encoded + -- sequence in aaSequence. + } OPTIONAL, + digestEncryptionAlgorithm + DigestEncryptionAlgorithmIdentifier ({ pkcs7_sig_note_pkey_algo }), + encryptedDigest EncryptedDigest, + unauthenticatedAttributes CHOICE { + uaSet [1] IMPLICIT SET OF UnauthenticatedAttribute, + uaSequence [3] IMPLICIT SEQUENCE OF UnauthenticatedAttribute + } OPTIONAL +} ({ pkcs7_note_signed_info }) + +SignerIdentifier ::= CHOICE { + -- RFC5652 sec 5.3 + issuerAndSerialNumber IssuerAndSerialNumber, + subjectKeyIdentifier [0] IMPLICIT SubjectKeyIdentifier +} + +IssuerAndSerialNumber ::= SEQUENCE { + issuer Name ({ pkcs7_sig_note_issuer }), + serialNumber CertificateSerialNumber ({ pkcs7_sig_note_serial }) +} + +CertificateSerialNumber ::= INTEGER + +SubjectKeyIdentifier ::= OCTET STRING ({ pkcs7_sig_note_skid }) + +SetOfAuthenticatedAttribute ::= SET OF AuthenticatedAttribute + +AuthenticatedAttribute ::= SEQUENCE { + type OBJECT IDENTIFIER ({ pkcs7_note_OID }), + values SET OF ANY ({ pkcs7_sig_note_authenticated_attr }) +} + +UnauthenticatedAttribute ::= SEQUENCE { + type OBJECT IDENTIFIER, + values SET OF ANY +} + +DigestEncryptionAlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ pkcs7_note_OID }), + parameters ANY OPTIONAL +} + +EncryptedDigest ::= OCTET STRING ({ pkcs7_sig_note_signature }) + +--- +--- X.500 Name +--- +Name ::= SEQUENCE OF RelativeDistinguishedName + +RelativeDistinguishedName ::= SET OF AttributeValueAssertion + +AttributeValueAssertion ::= SEQUENCE { + attributeType OBJECT IDENTIFIER ({ pkcs7_note_OID }), + attributeValue ANY +} diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c new file mode 100644 index 000000000..5b2f6a2b5 --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_key_type.c @@ -0,0 +1,99 @@ +/* Testing module to load key from trusted PKCS#7 message + * + * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PKCS7key: "fmt +#include <linux/key.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/verification.h> +#include <linux/key-type.h> +#include <keys/user-type.h> + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PKCS#7 testing key type"); +MODULE_AUTHOR("Red Hat, Inc."); + +static unsigned pkcs7_usage; +module_param_named(usage, pkcs7_usage, uint, S_IWUSR | S_IRUGO); +MODULE_PARM_DESC(pkcs7_usage, + "Usage to specify when verifying the PKCS#7 message"); + +/* + * Retrieve the PKCS#7 message content. + */ +static int pkcs7_view_content(void *ctx, const void *data, size_t len, + size_t asn1hdrlen) +{ + struct key_preparsed_payload *prep = ctx; + const void *saved_prep_data; + size_t saved_prep_datalen; + int ret; + + saved_prep_data = prep->data; + saved_prep_datalen = prep->datalen; + prep->data = data; + prep->datalen = len; + + ret = user_preparse(prep); + + prep->data = saved_prep_data; + prep->datalen = saved_prep_datalen; + return ret; +} + +/* + * Preparse a PKCS#7 wrapped and validated data blob. + */ +static int pkcs7_preparse(struct key_preparsed_payload *prep) +{ + enum key_being_used_for usage = pkcs7_usage; + + if (usage >= NR__KEY_BEING_USED_FOR) { + pr_err("Invalid usage type %d\n", usage); + return -EINVAL; + } + + return verify_pkcs7_signature(NULL, 0, + prep->data, prep->datalen, + VERIFY_USE_SECONDARY_KEYRING, usage, + pkcs7_view_content, prep); +} + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +static struct key_type key_type_pkcs7 = { + .name = "pkcs7_test", + .preparse = pkcs7_preparse, + .free_preparse = user_free_preparse, + .instantiate = generic_key_instantiate, + .revoke = user_revoke, + .destroy = user_destroy, + .describe = user_describe, + .read = user_read, +}; + +/* + * Module stuff + */ +static int __init pkcs7_key_init(void) +{ + return register_key_type(&key_type_pkcs7); +} + +static void __exit pkcs7_key_cleanup(void) +{ + unregister_key_type(&key_type_pkcs7); +} + +module_init(pkcs7_key_init); +module_exit(pkcs7_key_cleanup); diff --git a/crypto/asymmetric_keys/pkcs7_parser.c b/crypto/asymmetric_keys/pkcs7_parser.c new file mode 100644 index 000000000..0f134162c --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_parser.c @@ -0,0 +1,685 @@ +/* PKCS#7 parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PKCS7: "fmt +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/oid_registry.h> +#include <crypto/public_key.h> +#include "pkcs7_parser.h" +#include "pkcs7.asn1.h" + +MODULE_DESCRIPTION("PKCS#7 parser"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +struct pkcs7_parse_context { + struct pkcs7_message *msg; /* Message being constructed */ + struct pkcs7_signed_info *sinfo; /* SignedInfo being constructed */ + struct pkcs7_signed_info **ppsinfo; + struct x509_certificate *certs; /* Certificate cache */ + struct x509_certificate **ppcerts; + unsigned long data; /* Start of data */ + enum OID last_oid; /* Last OID encountered */ + unsigned x509_index; + unsigned sinfo_index; + const void *raw_serial; + unsigned raw_serial_size; + unsigned raw_issuer_size; + const void *raw_issuer; + const void *raw_skid; + unsigned raw_skid_size; + bool expect_skid; +}; + +/* + * Free a signed information block. + */ +static void pkcs7_free_signed_info(struct pkcs7_signed_info *sinfo) +{ + if (sinfo) { + public_key_signature_free(sinfo->sig); + kfree(sinfo); + } +} + +/** + * pkcs7_free_message - Free a PKCS#7 message + * @pkcs7: The PKCS#7 message to free + */ +void pkcs7_free_message(struct pkcs7_message *pkcs7) +{ + struct x509_certificate *cert; + struct pkcs7_signed_info *sinfo; + + if (pkcs7) { + while (pkcs7->certs) { + cert = pkcs7->certs; + pkcs7->certs = cert->next; + x509_free_certificate(cert); + } + while (pkcs7->crl) { + cert = pkcs7->crl; + pkcs7->crl = cert->next; + x509_free_certificate(cert); + } + while (pkcs7->signed_infos) { + sinfo = pkcs7->signed_infos; + pkcs7->signed_infos = sinfo->next; + pkcs7_free_signed_info(sinfo); + } + kfree(pkcs7); + } +} +EXPORT_SYMBOL_GPL(pkcs7_free_message); + +/* + * Check authenticatedAttributes are provided or not provided consistently. + */ +static int pkcs7_check_authattrs(struct pkcs7_message *msg) +{ + struct pkcs7_signed_info *sinfo; + bool want = false; + + sinfo = msg->signed_infos; + if (!sinfo) + goto inconsistent; + + if (sinfo->authattrs) { + want = true; + msg->have_authattrs = true; + } + + for (sinfo = sinfo->next; sinfo; sinfo = sinfo->next) + if (!!sinfo->authattrs != want) + goto inconsistent; + return 0; + +inconsistent: + pr_warn("Inconsistently supplied authAttrs\n"); + return -EINVAL; +} + +/** + * pkcs7_parse_message - Parse a PKCS#7 message + * @data: The raw binary ASN.1 encoded message to be parsed + * @datalen: The size of the encoded message + */ +struct pkcs7_message *pkcs7_parse_message(const void *data, size_t datalen) +{ + struct pkcs7_parse_context *ctx; + struct pkcs7_message *msg = ERR_PTR(-ENOMEM); + int ret; + + ctx = kzalloc(sizeof(struct pkcs7_parse_context), GFP_KERNEL); + if (!ctx) + goto out_no_ctx; + ctx->msg = kzalloc(sizeof(struct pkcs7_message), GFP_KERNEL); + if (!ctx->msg) + goto out_no_msg; + ctx->sinfo = kzalloc(sizeof(struct pkcs7_signed_info), GFP_KERNEL); + if (!ctx->sinfo) + goto out_no_sinfo; + ctx->sinfo->sig = kzalloc(sizeof(struct public_key_signature), + GFP_KERNEL); + if (!ctx->sinfo->sig) + goto out_no_sig; + + ctx->data = (unsigned long)data; + ctx->ppcerts = &ctx->certs; + ctx->ppsinfo = &ctx->msg->signed_infos; + + /* Attempt to decode the signature */ + ret = asn1_ber_decoder(&pkcs7_decoder, ctx, data, datalen); + if (ret < 0) { + msg = ERR_PTR(ret); + goto out; + } + + ret = pkcs7_check_authattrs(ctx->msg); + if (ret < 0) { + msg = ERR_PTR(ret); + goto out; + } + + msg = ctx->msg; + ctx->msg = NULL; + +out: + while (ctx->certs) { + struct x509_certificate *cert = ctx->certs; + ctx->certs = cert->next; + x509_free_certificate(cert); + } +out_no_sig: + pkcs7_free_signed_info(ctx->sinfo); +out_no_sinfo: + pkcs7_free_message(ctx->msg); +out_no_msg: + kfree(ctx); +out_no_ctx: + return msg; +} +EXPORT_SYMBOL_GPL(pkcs7_parse_message); + +/** + * pkcs7_get_content_data - Get access to the PKCS#7 content + * @pkcs7: The preparsed PKCS#7 message to access + * @_data: Place to return a pointer to the data + * @_data_len: Place to return the data length + * @_headerlen: Size of ASN.1 header not included in _data + * + * Get access to the data content of the PKCS#7 message. The size of the + * header of the ASN.1 object that contains it is also provided and can be used + * to adjust *_data and *_data_len to get the entire object. + * + * Returns -ENODATA if the data object was missing from the message. + */ +int pkcs7_get_content_data(const struct pkcs7_message *pkcs7, + const void **_data, size_t *_data_len, + size_t *_headerlen) +{ + if (!pkcs7->data) + return -ENODATA; + + *_data = pkcs7->data; + *_data_len = pkcs7->data_len; + if (_headerlen) + *_headerlen = pkcs7->data_hdrlen; + return 0; +} +EXPORT_SYMBOL_GPL(pkcs7_get_content_data); + +/* + * Note an OID when we find one for later processing when we know how + * to interpret it. + */ +int pkcs7_note_OID(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + ctx->last_oid = look_up_OID(value, vlen); + if (ctx->last_oid == OID__NR) { + char buffer[50]; + sprint_oid(value, vlen, buffer, sizeof(buffer)); + printk("PKCS7: Unknown OID: [%lu] %s\n", + (unsigned long)value - ctx->data, buffer); + } + return 0; +} + +/* + * Note the digest algorithm for the signature. + */ +int pkcs7_sig_note_digest_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + switch (ctx->last_oid) { + case OID_md4: + ctx->sinfo->sig->hash_algo = "md4"; + break; + case OID_md5: + ctx->sinfo->sig->hash_algo = "md5"; + break; + case OID_sha1: + ctx->sinfo->sig->hash_algo = "sha1"; + break; + case OID_sha256: + ctx->sinfo->sig->hash_algo = "sha256"; + break; + case OID_sha384: + ctx->sinfo->sig->hash_algo = "sha384"; + break; + case OID_sha512: + ctx->sinfo->sig->hash_algo = "sha512"; + break; + case OID_sha224: + ctx->sinfo->sig->hash_algo = "sha224"; + break; + default: + printk("Unsupported digest algo: %u\n", ctx->last_oid); + return -ENOPKG; + } + return 0; +} + +/* + * Note the public key algorithm for the signature. + */ +int pkcs7_sig_note_pkey_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + switch (ctx->last_oid) { + case OID_rsaEncryption: + ctx->sinfo->sig->pkey_algo = "rsa"; + break; + default: + printk("Unsupported pkey algo: %u\n", ctx->last_oid); + return -ENOPKG; + } + return 0; +} + +/* + * We only support signed data [RFC2315 sec 9]. + */ +int pkcs7_check_content_type(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + if (ctx->last_oid != OID_signed_data) { + pr_warn("Only support pkcs7_signedData type\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Note the SignedData version + */ +int pkcs7_note_signeddata_version(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + unsigned version; + + if (vlen != 1) + goto unsupported; + + ctx->msg->version = version = *(const u8 *)value; + switch (version) { + case 1: + /* PKCS#7 SignedData [RFC2315 sec 9.1] + * CMS ver 1 SignedData [RFC5652 sec 5.1] + */ + break; + case 3: + /* CMS ver 3 SignedData [RFC2315 sec 5.1] */ + break; + default: + goto unsupported; + } + + return 0; + +unsupported: + pr_warn("Unsupported SignedData version\n"); + return -EINVAL; +} + +/* + * Note the SignerInfo version + */ +int pkcs7_note_signerinfo_version(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + unsigned version; + + if (vlen != 1) + goto unsupported; + + version = *(const u8 *)value; + switch (version) { + case 1: + /* PKCS#7 SignerInfo [RFC2315 sec 9.2] + * CMS ver 1 SignerInfo [RFC5652 sec 5.3] + */ + if (ctx->msg->version != 1) + goto version_mismatch; + ctx->expect_skid = false; + break; + case 3: + /* CMS ver 3 SignerInfo [RFC2315 sec 5.3] */ + if (ctx->msg->version == 1) + goto version_mismatch; + ctx->expect_skid = true; + break; + default: + goto unsupported; + } + + return 0; + +unsupported: + pr_warn("Unsupported SignerInfo version\n"); + return -EINVAL; +version_mismatch: + pr_warn("SignedData-SignerInfo version mismatch\n"); + return -EBADMSG; +} + +/* + * Extract a certificate and store it in the context. + */ +int pkcs7_extract_cert(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct x509_certificate *x509; + + if (tag != ((ASN1_UNIV << 6) | ASN1_CONS_BIT | ASN1_SEQ)) { + pr_debug("Cert began with tag %02x at %lu\n", + tag, (unsigned long)ctx - ctx->data); + return -EBADMSG; + } + + /* We have to correct for the header so that the X.509 parser can start + * from the beginning. Note that since X.509 stipulates DER, there + * probably shouldn't be an EOC trailer - but it is in PKCS#7 (which + * stipulates BER). + */ + value -= hdrlen; + vlen += hdrlen; + + if (((u8*)value)[1] == 0x80) + vlen += 2; /* Indefinite length - there should be an EOC */ + + x509 = x509_cert_parse(value, vlen); + if (IS_ERR(x509)) + return PTR_ERR(x509); + + x509->index = ++ctx->x509_index; + pr_debug("Got cert %u for %s\n", x509->index, x509->subject); + pr_debug("- fingerprint %*phN\n", x509->id->len, x509->id->data); + + *ctx->ppcerts = x509; + ctx->ppcerts = &x509->next; + return 0; +} + +/* + * Save the certificate list + */ +int pkcs7_note_certificate_list(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + pr_devel("Got cert list (%02x)\n", tag); + + *ctx->ppcerts = ctx->msg->certs; + ctx->msg->certs = ctx->certs; + ctx->certs = NULL; + ctx->ppcerts = &ctx->certs; + return 0; +} + +/* + * Note the content type. + */ +int pkcs7_note_content(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + if (ctx->last_oid != OID_data && + ctx->last_oid != OID_msIndirectData) { + pr_warn("Unsupported data type %d\n", ctx->last_oid); + return -EINVAL; + } + + ctx->msg->data_type = ctx->last_oid; + return 0; +} + +/* + * Extract the data from the message and store that and its content type OID in + * the context. + */ +int pkcs7_note_data(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + pr_debug("Got data\n"); + + ctx->msg->data = value; + ctx->msg->data_len = vlen; + ctx->msg->data_hdrlen = hdrlen; + return 0; +} + +/* + * Parse authenticated attributes. + */ +int pkcs7_sig_note_authenticated_attr(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct pkcs7_signed_info *sinfo = ctx->sinfo; + enum OID content_type; + + pr_devel("AuthAttr: %02x %zu [%*ph]\n", tag, vlen, (unsigned)vlen, value); + + switch (ctx->last_oid) { + case OID_contentType: + if (__test_and_set_bit(sinfo_has_content_type, &sinfo->aa_set)) + goto repeated; + content_type = look_up_OID(value, vlen); + if (content_type != ctx->msg->data_type) { + pr_warn("Mismatch between global data type (%d) and sinfo %u (%d)\n", + ctx->msg->data_type, sinfo->index, + content_type); + return -EBADMSG; + } + return 0; + + case OID_signingTime: + if (__test_and_set_bit(sinfo_has_signing_time, &sinfo->aa_set)) + goto repeated; + /* Should we check that the signing time is consistent + * with the signer's X.509 cert? + */ + return x509_decode_time(&sinfo->signing_time, + hdrlen, tag, value, vlen); + + case OID_messageDigest: + if (__test_and_set_bit(sinfo_has_message_digest, &sinfo->aa_set)) + goto repeated; + if (tag != ASN1_OTS) + return -EBADMSG; + sinfo->msgdigest = value; + sinfo->msgdigest_len = vlen; + return 0; + + case OID_smimeCapabilites: + if (__test_and_set_bit(sinfo_has_smime_caps, &sinfo->aa_set)) + goto repeated; + if (ctx->msg->data_type != OID_msIndirectData) { + pr_warn("S/MIME Caps only allowed with Authenticode\n"); + return -EKEYREJECTED; + } + return 0; + + /* Microsoft SpOpusInfo seems to be contain cont[0] 16-bit BE + * char URLs and cont[1] 8-bit char URLs. + * + * Microsoft StatementType seems to contain a list of OIDs that + * are also used as extendedKeyUsage types in X.509 certs. + */ + case OID_msSpOpusInfo: + if (__test_and_set_bit(sinfo_has_ms_opus_info, &sinfo->aa_set)) + goto repeated; + goto authenticode_check; + case OID_msStatementType: + if (__test_and_set_bit(sinfo_has_ms_statement_type, &sinfo->aa_set)) + goto repeated; + authenticode_check: + if (ctx->msg->data_type != OID_msIndirectData) { + pr_warn("Authenticode AuthAttrs only allowed with Authenticode\n"); + return -EKEYREJECTED; + } + /* I'm not sure how to validate these */ + return 0; + default: + return 0; + } + +repeated: + /* We permit max one item per AuthenticatedAttribute and no repeats */ + pr_warn("Repeated/multivalue AuthAttrs not permitted\n"); + return -EKEYREJECTED; +} + +/* + * Note the set of auth attributes for digestion purposes [RFC2315 sec 9.3] + */ +int pkcs7_sig_note_set_of_authattrs(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct pkcs7_signed_info *sinfo = ctx->sinfo; + + if (!test_bit(sinfo_has_content_type, &sinfo->aa_set) || + !test_bit(sinfo_has_message_digest, &sinfo->aa_set)) { + pr_warn("Missing required AuthAttr\n"); + return -EBADMSG; + } + + if (ctx->msg->data_type != OID_msIndirectData && + test_bit(sinfo_has_ms_opus_info, &sinfo->aa_set)) { + pr_warn("Unexpected Authenticode AuthAttr\n"); + return -EBADMSG; + } + + /* We need to switch the 'CONT 0' to a 'SET OF' when we digest */ + sinfo->authattrs = value - (hdrlen - 1); + sinfo->authattrs_len = vlen + (hdrlen - 1); + return 0; +} + +/* + * Note the issuing certificate serial number + */ +int pkcs7_sig_note_serial(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + ctx->raw_serial = value; + ctx->raw_serial_size = vlen; + return 0; +} + +/* + * Note the issuer's name + */ +int pkcs7_sig_note_issuer(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + ctx->raw_issuer = value; + ctx->raw_issuer_size = vlen; + return 0; +} + +/* + * Note the issuing cert's subjectKeyIdentifier + */ +int pkcs7_sig_note_skid(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + pr_devel("SKID: %02x %zu [%*ph]\n", tag, vlen, (unsigned)vlen, value); + + ctx->raw_skid = value; + ctx->raw_skid_size = vlen; + return 0; +} + +/* + * Note the signature data + */ +int pkcs7_sig_note_signature(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + + ctx->sinfo->sig->s = kmemdup(value, vlen, GFP_KERNEL); + if (!ctx->sinfo->sig->s) + return -ENOMEM; + + ctx->sinfo->sig->s_size = vlen; + return 0; +} + +/* + * Note a signature information block + */ +int pkcs7_note_signed_info(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct pkcs7_parse_context *ctx = context; + struct pkcs7_signed_info *sinfo = ctx->sinfo; + struct asymmetric_key_id *kid; + + if (ctx->msg->data_type == OID_msIndirectData && !sinfo->authattrs) { + pr_warn("Authenticode requires AuthAttrs\n"); + return -EBADMSG; + } + + /* Generate cert issuer + serial number key ID */ + if (!ctx->expect_skid) { + kid = asymmetric_key_generate_id(ctx->raw_serial, + ctx->raw_serial_size, + ctx->raw_issuer, + ctx->raw_issuer_size); + } else { + kid = asymmetric_key_generate_id(ctx->raw_skid, + ctx->raw_skid_size, + "", 0); + } + if (IS_ERR(kid)) + return PTR_ERR(kid); + + pr_devel("SINFO KID: %u [%*phN]\n", kid->len, kid->len, kid->data); + + sinfo->sig->auth_ids[0] = kid; + sinfo->index = ++ctx->sinfo_index; + *ctx->ppsinfo = sinfo; + ctx->ppsinfo = &sinfo->next; + ctx->sinfo = kzalloc(sizeof(struct pkcs7_signed_info), GFP_KERNEL); + if (!ctx->sinfo) + return -ENOMEM; + ctx->sinfo->sig = kzalloc(sizeof(struct public_key_signature), + GFP_KERNEL); + if (!ctx->sinfo->sig) + return -ENOMEM; + return 0; +} diff --git a/crypto/asymmetric_keys/pkcs7_parser.h b/crypto/asymmetric_keys/pkcs7_parser.h new file mode 100644 index 000000000..ac341e19e --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_parser.h @@ -0,0 +1,69 @@ +/* PKCS#7 crypto data parser internal definitions + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/oid_registry.h> +#include <crypto/pkcs7.h> +#include "x509_parser.h" + +#define kenter(FMT, ...) \ + pr_devel("==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + pr_devel("<== %s()"FMT"\n", __func__, ##__VA_ARGS__) + +struct pkcs7_signed_info { + struct pkcs7_signed_info *next; + struct x509_certificate *signer; /* Signing certificate (in msg->certs) */ + unsigned index; + bool unsupported_crypto; /* T if not usable due to missing crypto */ + bool blacklisted; + + /* Message digest - the digest of the Content Data (or NULL) */ + const void *msgdigest; + unsigned msgdigest_len; + + /* Authenticated Attribute data (or NULL) */ + unsigned authattrs_len; + const void *authattrs; + unsigned long aa_set; +#define sinfo_has_content_type 0 +#define sinfo_has_signing_time 1 +#define sinfo_has_message_digest 2 +#define sinfo_has_smime_caps 3 +#define sinfo_has_ms_opus_info 4 +#define sinfo_has_ms_statement_type 5 + time64_t signing_time; + + /* Message signature. + * + * This contains the generated digest of _either_ the Content Data or + * the Authenticated Attributes [RFC2315 9.3]. If the latter, one of + * the attributes contains the digest of the the Content Data within + * it. + * + * THis also contains the issuing cert serial number and issuer's name + * [PKCS#7 or CMS ver 1] or issuing cert's SKID [CMS ver 3]. + */ + struct public_key_signature *sig; +}; + +struct pkcs7_message { + struct x509_certificate *certs; /* Certificate list */ + struct x509_certificate *crl; /* Revocation list */ + struct pkcs7_signed_info *signed_infos; + u8 version; /* Version of cert (1 -> PKCS#7 or CMS; 3 -> CMS) */ + bool have_authattrs; /* T if have authattrs */ + + /* Content Data (or NULL) */ + enum OID data_type; /* Type of Data */ + size_t data_len; /* Length of Data */ + size_t data_hdrlen; /* Length of Data ASN.1 header */ + const void *data; /* Content Data (or 0) */ +}; diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c new file mode 100644 index 000000000..598906b1e --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_trust.c @@ -0,0 +1,192 @@ +/* Validate the trust chain of a PKCS#7 message. + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PKCS7: "fmt +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/asn1.h> +#include <linux/key.h> +#include <keys/asymmetric-type.h> +#include <crypto/public_key.h> +#include "pkcs7_parser.h" + +/** + * Check the trust on one PKCS#7 SignedInfo block. + */ +static int pkcs7_validate_trust_one(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo, + struct key *trust_keyring) +{ + struct public_key_signature *sig = sinfo->sig; + struct x509_certificate *x509, *last = NULL, *p; + struct key *key; + int ret; + + kenter(",%u,", sinfo->index); + + if (sinfo->unsupported_crypto) { + kleave(" = -ENOPKG [cached]"); + return -ENOPKG; + } + + for (x509 = sinfo->signer; x509; x509 = x509->signer) { + if (x509->seen) { + if (x509->verified) + goto verified; + kleave(" = -ENOKEY [cached]"); + return -ENOKEY; + } + x509->seen = true; + + /* Look to see if this certificate is present in the trusted + * keys. + */ + key = find_asymmetric_key(trust_keyring, + x509->id, x509->skid, false); + if (!IS_ERR(key)) { + /* One of the X.509 certificates in the PKCS#7 message + * is apparently the same as one we already trust. + * Verify that the trusted variant can also validate + * the signature on the descendant. + */ + pr_devel("sinfo %u: Cert %u as key %x\n", + sinfo->index, x509->index, key_serial(key)); + goto matched; + } + if (key == ERR_PTR(-ENOMEM)) + return -ENOMEM; + + /* Self-signed certificates form roots of their own, and if we + * don't know them, then we can't accept them. + */ + if (x509->signer == x509) { + kleave(" = -ENOKEY [unknown self-signed]"); + return -ENOKEY; + } + + might_sleep(); + last = x509; + sig = last->sig; + } + + /* No match - see if the root certificate has a signer amongst the + * trusted keys. + */ + if (last && (last->sig->auth_ids[0] || last->sig->auth_ids[1])) { + key = find_asymmetric_key(trust_keyring, + last->sig->auth_ids[0], + last->sig->auth_ids[1], + false); + if (!IS_ERR(key)) { + x509 = last; + pr_devel("sinfo %u: Root cert %u signer is key %x\n", + sinfo->index, x509->index, key_serial(key)); + goto matched; + } + if (PTR_ERR(key) != -ENOKEY) + return PTR_ERR(key); + } + + /* As a last resort, see if we have a trusted public key that matches + * the signed info directly. + */ + key = find_asymmetric_key(trust_keyring, + sinfo->sig->auth_ids[0], NULL, false); + if (!IS_ERR(key)) { + pr_devel("sinfo %u: Direct signer is key %x\n", + sinfo->index, key_serial(key)); + x509 = NULL; + sig = sinfo->sig; + goto matched; + } + if (PTR_ERR(key) != -ENOKEY) + return PTR_ERR(key); + + kleave(" = -ENOKEY [no backref]"); + return -ENOKEY; + +matched: + ret = verify_signature(key, sig); + key_put(key); + if (ret < 0) { + if (ret == -ENOMEM) + return ret; + kleave(" = -EKEYREJECTED [verify %d]", ret); + return -EKEYREJECTED; + } + +verified: + if (x509) { + x509->verified = true; + for (p = sinfo->signer; p != x509; p = p->signer) + p->verified = true; + } + kleave(" = 0"); + return 0; +} + +/** + * pkcs7_validate_trust - Validate PKCS#7 trust chain + * @pkcs7: The PKCS#7 certificate to validate + * @trust_keyring: Signing certificates to use as starting points + * + * Validate that the certificate chain inside the PKCS#7 message intersects + * keys we already know and trust. + * + * Returns, in order of descending priority: + * + * (*) -EKEYREJECTED if a signature failed to match for which we have a valid + * key, or: + * + * (*) 0 if at least one signature chain intersects with the keys in the trust + * keyring, or: + * + * (*) -ENOPKG if a suitable crypto module couldn't be found for a check on a + * chain. + * + * (*) -ENOKEY if we couldn't find a match for any of the signature chains in + * the message. + * + * May also return -ENOMEM. + */ +int pkcs7_validate_trust(struct pkcs7_message *pkcs7, + struct key *trust_keyring) +{ + struct pkcs7_signed_info *sinfo; + struct x509_certificate *p; + int cached_ret = -ENOKEY; + int ret; + + for (p = pkcs7->certs; p; p = p->next) + p->seen = false; + + for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) { + ret = pkcs7_validate_trust_one(pkcs7, sinfo, trust_keyring); + switch (ret) { + case -ENOKEY: + continue; + case -ENOPKG: + if (cached_ret == -ENOKEY) + cached_ret = -ENOPKG; + continue; + case 0: + cached_ret = 0; + continue; + default: + return ret; + } + } + + return cached_ret; +} +EXPORT_SYMBOL_GPL(pkcs7_validate_trust); diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c new file mode 100644 index 000000000..97c77f66b --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_verify.c @@ -0,0 +1,469 @@ +/* Verify the signature on a PKCS#7 message. + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PKCS7: "fmt +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/asn1.h> +#include <crypto/hash.h> +#include <crypto/public_key.h> +#include "pkcs7_parser.h" + +/* + * Digest the relevant parts of the PKCS#7 data + */ +static int pkcs7_digest(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo) +{ + struct public_key_signature *sig = sinfo->sig; + struct crypto_shash *tfm; + struct shash_desc *desc; + size_t desc_size; + int ret; + + kenter(",%u,%s", sinfo->index, sinfo->sig->hash_algo); + + if (!sinfo->sig->hash_algo) + return -ENOPKG; + + /* Allocate the hashing algorithm we're going to need and find out how + * big the hash operational data will be. + */ + tfm = crypto_alloc_shash(sinfo->sig->hash_algo, 0, 0); + if (IS_ERR(tfm)) + return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm); + + desc_size = crypto_shash_descsize(tfm) + sizeof(*desc); + sig->digest_size = crypto_shash_digestsize(tfm); + + ret = -ENOMEM; + sig->digest = kmalloc(sig->digest_size, GFP_KERNEL); + if (!sig->digest) + goto error_no_desc; + + desc = kzalloc(desc_size, GFP_KERNEL); + if (!desc) + goto error_no_desc; + + desc->tfm = tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + + /* Digest the message [RFC2315 9.3] */ + ret = crypto_shash_digest(desc, pkcs7->data, pkcs7->data_len, + sig->digest); + if (ret < 0) + goto error; + pr_devel("MsgDigest = [%*ph]\n", 8, sig->digest); + + /* However, if there are authenticated attributes, there must be a + * message digest attribute amongst them which corresponds to the + * digest we just calculated. + */ + if (sinfo->authattrs) { + u8 tag; + + if (!sinfo->msgdigest) { + pr_warn("Sig %u: No messageDigest\n", sinfo->index); + ret = -EKEYREJECTED; + goto error; + } + + if (sinfo->msgdigest_len != sig->digest_size) { + pr_debug("Sig %u: Invalid digest size (%u)\n", + sinfo->index, sinfo->msgdigest_len); + ret = -EBADMSG; + goto error; + } + + if (memcmp(sig->digest, sinfo->msgdigest, + sinfo->msgdigest_len) != 0) { + pr_debug("Sig %u: Message digest doesn't match\n", + sinfo->index); + ret = -EKEYREJECTED; + goto error; + } + + /* We then calculate anew, using the authenticated attributes + * as the contents of the digest instead. Note that we need to + * convert the attributes from a CONT.0 into a SET before we + * hash it. + */ + memset(sig->digest, 0, sig->digest_size); + + ret = crypto_shash_init(desc); + if (ret < 0) + goto error; + tag = ASN1_CONS_BIT | ASN1_SET; + ret = crypto_shash_update(desc, &tag, 1); + if (ret < 0) + goto error; + ret = crypto_shash_finup(desc, sinfo->authattrs, + sinfo->authattrs_len, sig->digest); + if (ret < 0) + goto error; + pr_devel("AADigest = [%*ph]\n", 8, sig->digest); + } + +error: + kfree(desc); +error_no_desc: + crypto_free_shash(tfm); + kleave(" = %d", ret); + return ret; +} + +/* + * Find the key (X.509 certificate) to use to verify a PKCS#7 message. PKCS#7 + * uses the issuer's name and the issuing certificate serial number for + * matching purposes. These must match the certificate issuer's name (not + * subject's name) and the certificate serial number [RFC 2315 6.7]. + */ +static int pkcs7_find_key(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo) +{ + struct x509_certificate *x509; + unsigned certix = 1; + + kenter("%u", sinfo->index); + + for (x509 = pkcs7->certs; x509; x509 = x509->next, certix++) { + /* I'm _assuming_ that the generator of the PKCS#7 message will + * encode the fields from the X.509 cert in the same way in the + * PKCS#7 message - but I can't be 100% sure of that. It's + * possible this will need element-by-element comparison. + */ + if (!asymmetric_key_id_same(x509->id, sinfo->sig->auth_ids[0])) + continue; + pr_devel("Sig %u: Found cert serial match X.509[%u]\n", + sinfo->index, certix); + + if (strcmp(x509->pub->pkey_algo, sinfo->sig->pkey_algo) != 0) { + pr_warn("Sig %u: X.509 algo and PKCS#7 sig algo don't match\n", + sinfo->index); + continue; + } + + sinfo->signer = x509; + return 0; + } + + /* The relevant X.509 cert isn't found here, but it might be found in + * the trust keyring. + */ + pr_debug("Sig %u: Issuing X.509 cert not found (#%*phN)\n", + sinfo->index, + sinfo->sig->auth_ids[0]->len, sinfo->sig->auth_ids[0]->data); + return 0; +} + +/* + * Verify the internal certificate chain as best we can. + */ +static int pkcs7_verify_sig_chain(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo) +{ + struct public_key_signature *sig; + struct x509_certificate *x509 = sinfo->signer, *p; + struct asymmetric_key_id *auth; + int ret; + + kenter(""); + + for (p = pkcs7->certs; p; p = p->next) + p->seen = false; + + for (;;) { + pr_debug("verify %s: %*phN\n", + x509->subject, + x509->raw_serial_size, x509->raw_serial); + x509->seen = true; + + if (x509->blacklisted) { + /* If this cert is blacklisted, then mark everything + * that depends on this as blacklisted too. + */ + sinfo->blacklisted = true; + for (p = sinfo->signer; p != x509; p = p->signer) + p->blacklisted = true; + pr_debug("- blacklisted\n"); + return 0; + } + + if (x509->unsupported_key) + goto unsupported_crypto_in_x509; + + pr_debug("- issuer %s\n", x509->issuer); + sig = x509->sig; + if (sig->auth_ids[0]) + pr_debug("- authkeyid.id %*phN\n", + sig->auth_ids[0]->len, sig->auth_ids[0]->data); + if (sig->auth_ids[1]) + pr_debug("- authkeyid.skid %*phN\n", + sig->auth_ids[1]->len, sig->auth_ids[1]->data); + + if (x509->self_signed) { + /* If there's no authority certificate specified, then + * the certificate must be self-signed and is the root + * of the chain. Likewise if the cert is its own + * authority. + */ + if (x509->unsupported_sig) + goto unsupported_crypto_in_x509; + x509->signer = x509; + pr_debug("- self-signed\n"); + return 0; + } + + /* Look through the X.509 certificates in the PKCS#7 message's + * list to see if the next one is there. + */ + auth = sig->auth_ids[0]; + if (auth) { + pr_debug("- want %*phN\n", auth->len, auth->data); + for (p = pkcs7->certs; p; p = p->next) { + pr_debug("- cmp [%u] %*phN\n", + p->index, p->id->len, p->id->data); + if (asymmetric_key_id_same(p->id, auth)) + goto found_issuer_check_skid; + } + } else if (sig->auth_ids[1]) { + auth = sig->auth_ids[1]; + pr_debug("- want %*phN\n", auth->len, auth->data); + for (p = pkcs7->certs; p; p = p->next) { + if (!p->skid) + continue; + pr_debug("- cmp [%u] %*phN\n", + p->index, p->skid->len, p->skid->data); + if (asymmetric_key_id_same(p->skid, auth)) + goto found_issuer; + } + } + + /* We didn't find the root of this chain */ + pr_debug("- top\n"); + return 0; + + found_issuer_check_skid: + /* We matched issuer + serialNumber, but if there's an + * authKeyId.keyId, that must match the CA subjKeyId also. + */ + if (sig->auth_ids[1] && + !asymmetric_key_id_same(p->skid, sig->auth_ids[1])) { + pr_warn("Sig %u: X.509 chain contains auth-skid nonmatch (%u->%u)\n", + sinfo->index, x509->index, p->index); + return -EKEYREJECTED; + } + found_issuer: + pr_debug("- subject %s\n", p->subject); + if (p->seen) { + pr_warn("Sig %u: X.509 chain contains loop\n", + sinfo->index); + return 0; + } + ret = public_key_verify_signature(p->pub, x509->sig); + if (ret < 0) + return ret; + x509->signer = p; + if (x509 == p) { + pr_debug("- self-signed\n"); + return 0; + } + x509 = p; + might_sleep(); + } + +unsupported_crypto_in_x509: + /* Just prune the certificate chain at this point if we lack some + * crypto module to go further. Note, however, we don't want to set + * sinfo->unsupported_crypto as the signed info block may still be + * validatable against an X.509 cert lower in the chain that we have a + * trusted copy of. + */ + return 0; +} + +/* + * Verify one signed information block from a PKCS#7 message. + */ +static int pkcs7_verify_one(struct pkcs7_message *pkcs7, + struct pkcs7_signed_info *sinfo) +{ + int ret; + + kenter(",%u", sinfo->index); + + /* First of all, digest the data in the PKCS#7 message and the + * signed information block + */ + ret = pkcs7_digest(pkcs7, sinfo); + if (ret < 0) + return ret; + + /* Find the key for the signature if there is one */ + ret = pkcs7_find_key(pkcs7, sinfo); + if (ret < 0) + return ret; + + if (!sinfo->signer) + return 0; + + pr_devel("Using X.509[%u] for sig %u\n", + sinfo->signer->index, sinfo->index); + + /* Check that the PKCS#7 signing time is valid according to the X.509 + * certificate. We can't, however, check against the system clock + * since that may not have been set yet and may be wrong. + */ + if (test_bit(sinfo_has_signing_time, &sinfo->aa_set)) { + if (sinfo->signing_time < sinfo->signer->valid_from || + sinfo->signing_time > sinfo->signer->valid_to) { + pr_warn("Message signed outside of X.509 validity window\n"); + return -EKEYREJECTED; + } + } + + /* Verify the PKCS#7 binary against the key */ + ret = public_key_verify_signature(sinfo->signer->pub, sinfo->sig); + if (ret < 0) + return ret; + + pr_devel("Verified signature %u\n", sinfo->index); + + /* Verify the internal certificate chain */ + return pkcs7_verify_sig_chain(pkcs7, sinfo); +} + +/** + * pkcs7_verify - Verify a PKCS#7 message + * @pkcs7: The PKCS#7 message to be verified + * @usage: The use to which the key is being put + * + * Verify a PKCS#7 message is internally consistent - that is, the data digest + * matches the digest in the AuthAttrs and any signature in the message or one + * of the X.509 certificates it carries that matches another X.509 cert in the + * message can be verified. + * + * This does not look to match the contents of the PKCS#7 message against any + * external public keys. + * + * Returns, in order of descending priority: + * + * (*) -EKEYREJECTED if a key was selected that had a usage restriction at + * odds with the specified usage, or: + * + * (*) -EKEYREJECTED if a signature failed to match for which we found an + * appropriate X.509 certificate, or: + * + * (*) -EBADMSG if some part of the message was invalid, or: + * + * (*) 0 if a signature chain passed verification, or: + * + * (*) -EKEYREJECTED if a blacklisted key was encountered, or: + * + * (*) -ENOPKG if none of the signature chains are verifiable because suitable + * crypto modules couldn't be found. + */ +int pkcs7_verify(struct pkcs7_message *pkcs7, + enum key_being_used_for usage) +{ + struct pkcs7_signed_info *sinfo; + int actual_ret = -ENOPKG; + int ret; + + kenter(""); + + switch (usage) { + case VERIFYING_MODULE_SIGNATURE: + if (pkcs7->data_type != OID_data) { + pr_warn("Invalid module sig (not pkcs7-data)\n"); + return -EKEYREJECTED; + } + if (pkcs7->have_authattrs) { + pr_warn("Invalid module sig (has authattrs)\n"); + return -EKEYREJECTED; + } + break; + case VERIFYING_FIRMWARE_SIGNATURE: + if (pkcs7->data_type != OID_data) { + pr_warn("Invalid firmware sig (not pkcs7-data)\n"); + return -EKEYREJECTED; + } + if (!pkcs7->have_authattrs) { + pr_warn("Invalid firmware sig (missing authattrs)\n"); + return -EKEYREJECTED; + } + break; + case VERIFYING_KEXEC_PE_SIGNATURE: + if (pkcs7->data_type != OID_msIndirectData) { + pr_warn("Invalid kexec sig (not Authenticode)\n"); + return -EKEYREJECTED; + } + /* Authattr presence checked in parser */ + break; + case VERIFYING_UNSPECIFIED_SIGNATURE: + if (pkcs7->data_type != OID_data) { + pr_warn("Invalid unspecified sig (not pkcs7-data)\n"); + return -EKEYREJECTED; + } + break; + default: + return -EINVAL; + } + + for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) { + ret = pkcs7_verify_one(pkcs7, sinfo); + if (sinfo->blacklisted) { + if (actual_ret == -ENOPKG) + actual_ret = -EKEYREJECTED; + continue; + } + if (ret < 0) { + if (ret == -ENOPKG) { + sinfo->unsupported_crypto = true; + continue; + } + kleave(" = %d", ret); + return ret; + } + actual_ret = 0; + } + + kleave(" = %d", actual_ret); + return actual_ret; +} +EXPORT_SYMBOL_GPL(pkcs7_verify); + +/** + * pkcs7_supply_detached_data - Supply the data needed to verify a PKCS#7 message + * @pkcs7: The PKCS#7 message + * @data: The data to be verified + * @datalen: The amount of data + * + * Supply the detached data needed to verify a PKCS#7 message. Note that no + * attempt to retain/pin the data is made. That is left to the caller. The + * data will not be modified by pkcs7_verify() and will not be freed when the + * PKCS#7 message is freed. + * + * Returns -EINVAL if data is already supplied in the message, 0 otherwise. + */ +int pkcs7_supply_detached_data(struct pkcs7_message *pkcs7, + const void *data, size_t datalen) +{ + if (pkcs7->data) { + pr_debug("Data already supplied\n"); + return -EINVAL; + } + pkcs7->data = data; + pkcs7->data_len = datalen; + return 0; +} diff --git a/crypto/asymmetric_keys/public_key.c b/crypto/asymmetric_keys/public_key.c new file mode 100644 index 000000000..e929fe1e4 --- /dev/null +++ b/crypto/asymmetric_keys/public_key.c @@ -0,0 +1,172 @@ +/* In-software asymmetric public-key crypto subtype + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PKEY: "fmt +#include <linux/module.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/seq_file.h> +#include <linux/scatterlist.h> +#include <keys/asymmetric-subtype.h> +#include <crypto/public_key.h> +#include <crypto/akcipher.h> + +MODULE_DESCRIPTION("In-software asymmetric public-key subtype"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); + +/* + * Provide a part of a description of the key for /proc/keys. + */ +static void public_key_describe(const struct key *asymmetric_key, + struct seq_file *m) +{ + struct public_key *key = asymmetric_key->payload.data[asym_crypto]; + + if (key) + seq_printf(m, "%s.%s", key->id_type, key->pkey_algo); +} + +/* + * Destroy a public key algorithm key. + */ +void public_key_free(struct public_key *key) +{ + if (key) { + kfree(key->key); + kfree(key); + } +} +EXPORT_SYMBOL_GPL(public_key_free); + +/* + * Destroy a public key algorithm key. + */ +static void public_key_destroy(void *payload0, void *payload3) +{ + public_key_free(payload0); + public_key_signature_free(payload3); +} + +/* + * Verify a signature using a public key. + */ +int public_key_verify_signature(const struct public_key *pkey, + const struct public_key_signature *sig) +{ + struct crypto_wait cwait; + struct crypto_akcipher *tfm; + struct akcipher_request *req; + struct scatterlist sig_sg, digest_sg; + const char *alg_name; + char alg_name_buf[CRYPTO_MAX_ALG_NAME]; + void *output; + unsigned int outlen; + int ret; + + pr_devel("==>%s()\n", __func__); + + BUG_ON(!pkey); + BUG_ON(!sig); + BUG_ON(!sig->s); + + if (!sig->digest) + return -ENOPKG; + + alg_name = sig->pkey_algo; + if (strcmp(sig->pkey_algo, "rsa") == 0) { + /* The data wangled by the RSA algorithm is typically padded + * and encoded in some manner, such as EMSA-PKCS1-1_5 [RFC3447 + * sec 8.2]. + */ + if (snprintf(alg_name_buf, CRYPTO_MAX_ALG_NAME, + "pkcs1pad(rsa,%s)", sig->hash_algo + ) >= CRYPTO_MAX_ALG_NAME) + return -EINVAL; + alg_name = alg_name_buf; + } + + tfm = crypto_alloc_akcipher(alg_name, 0, 0); + if (IS_ERR(tfm)) + return PTR_ERR(tfm); + + ret = -ENOMEM; + req = akcipher_request_alloc(tfm, GFP_KERNEL); + if (!req) + goto error_free_tfm; + + ret = crypto_akcipher_set_pub_key(tfm, pkey->key, pkey->keylen); + if (ret) + goto error_free_req; + + ret = -ENOMEM; + outlen = crypto_akcipher_maxsize(tfm); + output = kmalloc(outlen, GFP_KERNEL); + if (!output) + goto error_free_req; + + sg_init_one(&sig_sg, sig->s, sig->s_size); + sg_init_one(&digest_sg, output, outlen); + akcipher_request_set_crypt(req, &sig_sg, &digest_sg, sig->s_size, + outlen); + crypto_init_wait(&cwait); + akcipher_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG | + CRYPTO_TFM_REQ_MAY_SLEEP, + crypto_req_done, &cwait); + + /* Perform the verification calculation. This doesn't actually do the + * verification, but rather calculates the hash expected by the + * signature and returns that to us. + */ + ret = crypto_wait_req(crypto_akcipher_verify(req), &cwait); + if (ret) + goto out_free_output; + + /* Do the actual verification step. */ + if (req->dst_len != sig->digest_size || + memcmp(sig->digest, output, sig->digest_size) != 0) + ret = -EKEYREJECTED; + +out_free_output: + kfree(output); +error_free_req: + akcipher_request_free(req); +error_free_tfm: + crypto_free_akcipher(tfm); + pr_devel("<==%s() = %d\n", __func__, ret); + if (WARN_ON_ONCE(ret > 0)) + ret = -EINVAL; + return ret; +} +EXPORT_SYMBOL_GPL(public_key_verify_signature); + +static int public_key_verify_signature_2(const struct key *key, + const struct public_key_signature *sig) +{ + const struct public_key *pk = key->payload.data[asym_crypto]; + return public_key_verify_signature(pk, sig); +} + +/* + * Public key algorithm asymmetric key subtype + */ +struct asymmetric_key_subtype public_key_subtype = { + .owner = THIS_MODULE, + .name = "public_key", + .name_len = sizeof("public_key") - 1, + .describe = public_key_describe, + .destroy = public_key_destroy, + .verify_signature = public_key_verify_signature_2, +}; +EXPORT_SYMBOL_GPL(public_key_subtype); diff --git a/crypto/asymmetric_keys/restrict.c b/crypto/asymmetric_keys/restrict.c new file mode 100644 index 000000000..7c93c7728 --- /dev/null +++ b/crypto/asymmetric_keys/restrict.c @@ -0,0 +1,268 @@ +/* Instantiate a public key crypto key from an X.509 Certificate + * + * Copyright (C) 2012, 2016 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "ASYM: "fmt +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <crypto/public_key.h> +#include "asymmetric_keys.h" + +static bool use_builtin_keys; +static struct asymmetric_key_id *ca_keyid; + +#ifndef MODULE +static struct { + struct asymmetric_key_id id; + unsigned char data[10]; +} cakey; + +static int __init ca_keys_setup(char *str) +{ + if (!str) /* default system keyring */ + return 1; + + if (strncmp(str, "id:", 3) == 0) { + struct asymmetric_key_id *p = &cakey.id; + size_t hexlen = (strlen(str) - 3) / 2; + int ret; + + if (hexlen == 0 || hexlen > sizeof(cakey.data)) { + pr_err("Missing or invalid ca_keys id\n"); + return 1; + } + + ret = __asymmetric_key_hex_to_key_id(str + 3, p, hexlen); + if (ret < 0) + pr_err("Unparsable ca_keys id hex string\n"); + else + ca_keyid = p; /* owner key 'id:xxxxxx' */ + } else if (strcmp(str, "builtin") == 0) { + use_builtin_keys = true; + } + + return 1; +} +__setup("ca_keys=", ca_keys_setup); +#endif + +/** + * restrict_link_by_signature - Restrict additions to a ring of public keys + * @dest_keyring: Keyring being linked to. + * @type: The type of key being added. + * @payload: The payload of the new key. + * @trust_keyring: A ring of keys that can be used to vouch for the new cert. + * + * Check the new certificate against the ones in the trust keyring. If one of + * those is the signing key and validates the new certificate, then mark the + * new certificate as being trusted. + * + * Returns 0 if the new certificate was accepted, -ENOKEY if we couldn't find a + * matching parent certificate in the trusted list, -EKEYREJECTED if the + * signature check fails or the key is blacklisted, -ENOPKG if the signature + * uses unsupported crypto, or some other error if there is a matching + * certificate but the signature check cannot be performed. + */ +int restrict_link_by_signature(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trust_keyring) +{ + const struct public_key_signature *sig; + struct key *key; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (!trust_keyring) + return -ENOKEY; + + if (type != &key_type_asymmetric) + return -EOPNOTSUPP; + + sig = payload->data[asym_auth]; + if (!sig) + return -ENOPKG; + if (!sig->auth_ids[0] && !sig->auth_ids[1]) + return -ENOKEY; + + if (ca_keyid && !asymmetric_key_id_partial(sig->auth_ids[1], ca_keyid)) + return -EPERM; + + /* See if we have a key that signed this one. */ + key = find_asymmetric_key(trust_keyring, + sig->auth_ids[0], sig->auth_ids[1], + false); + if (IS_ERR(key)) + return -ENOKEY; + + if (use_builtin_keys && !test_bit(KEY_FLAG_BUILTIN, &key->flags)) + ret = -ENOKEY; + else + ret = verify_signature(key, sig); + key_put(key); + return ret; +} + +static bool match_either_id(const struct asymmetric_key_ids *pair, + const struct asymmetric_key_id *single) +{ + return (asymmetric_key_id_same(pair->id[0], single) || + asymmetric_key_id_same(pair->id[1], single)); +} + +static int key_or_keyring_common(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trusted, bool check_dest) +{ + const struct public_key_signature *sig; + struct key *key = NULL; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (!dest_keyring) + return -ENOKEY; + else if (dest_keyring->type != &key_type_keyring) + return -EOPNOTSUPP; + + if (!trusted && !check_dest) + return -ENOKEY; + + if (type != &key_type_asymmetric) + return -EOPNOTSUPP; + + sig = payload->data[asym_auth]; + if (!sig) + return -ENOPKG; + if (!sig->auth_ids[0] && !sig->auth_ids[1]) + return -ENOKEY; + + if (trusted) { + if (trusted->type == &key_type_keyring) { + /* See if we have a key that signed this one. */ + key = find_asymmetric_key(trusted, sig->auth_ids[0], + sig->auth_ids[1], false); + if (IS_ERR(key)) + key = NULL; + } else if (trusted->type == &key_type_asymmetric) { + const struct asymmetric_key_ids *signer_ids; + + signer_ids = asymmetric_key_ids(trusted); + + /* + * The auth_ids come from the candidate key (the + * one that is being considered for addition to + * dest_keyring) and identify the key that was + * used to sign. + * + * The signer_ids are identifiers for the + * signing key specified for dest_keyring. + * + * The first auth_id is the preferred id, and + * the second is the fallback. If only one + * auth_id is present, it may match against + * either signer_id. If two auth_ids are + * present, the first auth_id must match one + * signer_id and the second auth_id must match + * the second signer_id. + */ + if (!sig->auth_ids[0] || !sig->auth_ids[1]) { + const struct asymmetric_key_id *auth_id; + + auth_id = sig->auth_ids[0] ?: sig->auth_ids[1]; + if (match_either_id(signer_ids, auth_id)) + key = __key_get(trusted); + + } else if (asymmetric_key_id_same(signer_ids->id[1], + sig->auth_ids[1]) && + match_either_id(signer_ids, + sig->auth_ids[0])) { + key = __key_get(trusted); + } + } else { + return -EOPNOTSUPP; + } + } + + if (check_dest && !key) { + /* See if the destination has a key that signed this one. */ + key = find_asymmetric_key(dest_keyring, sig->auth_ids[0], + sig->auth_ids[1], false); + if (IS_ERR(key)) + key = NULL; + } + + if (!key) + return -ENOKEY; + + ret = key_validate(key); + if (ret == 0) + ret = verify_signature(key, sig); + + key_put(key); + return ret; +} + +/** + * restrict_link_by_key_or_keyring - Restrict additions to a ring of public + * keys using the restrict_key information stored in the ring. + * @dest_keyring: Keyring being linked to. + * @type: The type of key being added. + * @payload: The payload of the new key. + * @trusted: A key or ring of keys that can be used to vouch for the new cert. + * + * Check the new certificate only against the key or keys passed in the data + * parameter. If one of those is the signing key and validates the new + * certificate, then mark the new certificate as being ok to link. + * + * Returns 0 if the new certificate was accepted, -ENOKEY if we + * couldn't find a matching parent certificate in the trusted list, + * -EKEYREJECTED if the signature check fails, -ENOPKG if the signature uses + * unsupported crypto, or some other error if there is a matching certificate + * but the signature check cannot be performed. + */ +int restrict_link_by_key_or_keyring(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trusted) +{ + return key_or_keyring_common(dest_keyring, type, payload, trusted, + false); +} + +/** + * restrict_link_by_key_or_keyring_chain - Restrict additions to a ring of + * public keys using the restrict_key information stored in the ring. + * @dest_keyring: Keyring being linked to. + * @type: The type of key being added. + * @payload: The payload of the new key. + * @trusted: A key or ring of keys that can be used to vouch for the new cert. + * + * Check the new certificate only against the key or keys passed in the data + * parameter. If one of those is the signing key and validates the new + * certificate, then mark the new certificate as being ok to link. + * + * Returns 0 if the new certificate was accepted, -ENOKEY if we + * couldn't find a matching parent certificate in the trusted list, + * -EKEYREJECTED if the signature check fails, -ENOPKG if the signature uses + * unsupported crypto, or some other error if there is a matching certificate + * but the signature check cannot be performed. + */ +int restrict_link_by_key_or_keyring_chain(struct key *dest_keyring, + const struct key_type *type, + const union key_payload *payload, + struct key *trusted) +{ + return key_or_keyring_common(dest_keyring, type, payload, trusted, + true); +} diff --git a/crypto/asymmetric_keys/signature.c b/crypto/asymmetric_keys/signature.c new file mode 100644 index 000000000..28198314b --- /dev/null +++ b/crypto/asymmetric_keys/signature.c @@ -0,0 +1,68 @@ +/* Signature verification with an asymmetric key + * + * See Documentation/crypto/asymmetric-keys.txt + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "SIG: "fmt +#include <keys/asymmetric-subtype.h> +#include <linux/export.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <crypto/public_key.h> +#include "asymmetric_keys.h" + +/* + * Destroy a public key signature. + */ +void public_key_signature_free(struct public_key_signature *sig) +{ + int i; + + if (sig) { + for (i = 0; i < ARRAY_SIZE(sig->auth_ids); i++) + kfree(sig->auth_ids[i]); + kfree(sig->s); + kfree(sig->digest); + kfree(sig); + } +} +EXPORT_SYMBOL_GPL(public_key_signature_free); + +/** + * verify_signature - Initiate the use of an asymmetric key to verify a signature + * @key: The asymmetric key to verify against + * @sig: The signature to check + * + * Returns 0 if successful or else an error. + */ +int verify_signature(const struct key *key, + const struct public_key_signature *sig) +{ + const struct asymmetric_key_subtype *subtype; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (key->type != &key_type_asymmetric) + return -EINVAL; + subtype = asymmetric_key_subtype(key); + if (!subtype || + !key->payload.data[0]) + return -EINVAL; + if (!subtype->verify_signature) + return -ENOTSUPP; + + ret = subtype->verify_signature(key, sig); + + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} +EXPORT_SYMBOL_GPL(verify_signature); diff --git a/crypto/asymmetric_keys/verify_pefile.c b/crypto/asymmetric_keys/verify_pefile.c new file mode 100644 index 000000000..d178650fd --- /dev/null +++ b/crypto/asymmetric_keys/verify_pefile.c @@ -0,0 +1,455 @@ +/* Parse a signed PE binary + * + * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "PEFILE: "fmt +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/pe.h> +#include <linux/asn1.h> +#include <linux/verification.h> +#include <crypto/hash.h> +#include "verify_pefile.h" + +/* + * Parse a PE binary. + */ +static int pefile_parse_binary(const void *pebuf, unsigned int pelen, + struct pefile_context *ctx) +{ + const struct mz_hdr *mz = pebuf; + const struct pe_hdr *pe; + const struct pe32_opt_hdr *pe32; + const struct pe32plus_opt_hdr *pe64; + const struct data_directory *ddir; + const struct data_dirent *dde; + const struct section_header *secs, *sec; + size_t cursor, datalen = pelen; + + kenter(""); + +#define chkaddr(base, x, s) \ + do { \ + if ((x) < base || (s) >= datalen || (x) > datalen - (s)) \ + return -ELIBBAD; \ + } while (0) + + chkaddr(0, 0, sizeof(*mz)); + if (mz->magic != MZ_MAGIC) + return -ELIBBAD; + cursor = sizeof(*mz); + + chkaddr(cursor, mz->peaddr, sizeof(*pe)); + pe = pebuf + mz->peaddr; + if (pe->magic != PE_MAGIC) + return -ELIBBAD; + cursor = mz->peaddr + sizeof(*pe); + + chkaddr(0, cursor, sizeof(pe32->magic)); + pe32 = pebuf + cursor; + pe64 = pebuf + cursor; + + switch (pe32->magic) { + case PE_OPT_MAGIC_PE32: + chkaddr(0, cursor, sizeof(*pe32)); + ctx->image_checksum_offset = + (unsigned long)&pe32->csum - (unsigned long)pebuf; + ctx->header_size = pe32->header_size; + cursor += sizeof(*pe32); + ctx->n_data_dirents = pe32->data_dirs; + break; + + case PE_OPT_MAGIC_PE32PLUS: + chkaddr(0, cursor, sizeof(*pe64)); + ctx->image_checksum_offset = + (unsigned long)&pe64->csum - (unsigned long)pebuf; + ctx->header_size = pe64->header_size; + cursor += sizeof(*pe64); + ctx->n_data_dirents = pe64->data_dirs; + break; + + default: + pr_debug("Unknown PEOPT magic = %04hx\n", pe32->magic); + return -ELIBBAD; + } + + pr_debug("checksum @ %x\n", ctx->image_checksum_offset); + pr_debug("header size = %x\n", ctx->header_size); + + if (cursor >= ctx->header_size || ctx->header_size >= datalen) + return -ELIBBAD; + + if (ctx->n_data_dirents > (ctx->header_size - cursor) / sizeof(*dde)) + return -ELIBBAD; + + ddir = pebuf + cursor; + cursor += sizeof(*dde) * ctx->n_data_dirents; + + ctx->cert_dirent_offset = + (unsigned long)&ddir->certs - (unsigned long)pebuf; + ctx->certs_size = ddir->certs.size; + + if (!ddir->certs.virtual_address || !ddir->certs.size) { + pr_debug("Unsigned PE binary\n"); + return -EKEYREJECTED; + } + + chkaddr(ctx->header_size, ddir->certs.virtual_address, + ddir->certs.size); + ctx->sig_offset = ddir->certs.virtual_address; + ctx->sig_len = ddir->certs.size; + pr_debug("cert = %x @%x [%*ph]\n", + ctx->sig_len, ctx->sig_offset, + ctx->sig_len, pebuf + ctx->sig_offset); + + ctx->n_sections = pe->sections; + if (ctx->n_sections > (ctx->header_size - cursor) / sizeof(*sec)) + return -ELIBBAD; + ctx->secs = secs = pebuf + cursor; + + return 0; +} + +/* + * Check and strip the PE wrapper from around the signature and check that the + * remnant looks something like PKCS#7. + */ +static int pefile_strip_sig_wrapper(const void *pebuf, + struct pefile_context *ctx) +{ + struct win_certificate wrapper; + const u8 *pkcs7; + unsigned len; + + if (ctx->sig_len < sizeof(wrapper)) { + pr_debug("Signature wrapper too short\n"); + return -ELIBBAD; + } + + memcpy(&wrapper, pebuf + ctx->sig_offset, sizeof(wrapper)); + pr_debug("sig wrapper = { %x, %x, %x }\n", + wrapper.length, wrapper.revision, wrapper.cert_type); + + /* Both pesign and sbsign round up the length of certificate table + * (in optional header data directories) to 8 byte alignment. + */ + if (round_up(wrapper.length, 8) != ctx->sig_len) { + pr_debug("Signature wrapper len wrong\n"); + return -ELIBBAD; + } + if (wrapper.revision != WIN_CERT_REVISION_2_0) { + pr_debug("Signature is not revision 2.0\n"); + return -ENOTSUPP; + } + if (wrapper.cert_type != WIN_CERT_TYPE_PKCS_SIGNED_DATA) { + pr_debug("Signature certificate type is not PKCS\n"); + return -ENOTSUPP; + } + + /* It looks like the pkcs signature length in wrapper->length and the + * size obtained from the data dir entries, which lists the total size + * of certificate table, are both aligned to an octaword boundary, so + * we may have to deal with some padding. + */ + ctx->sig_len = wrapper.length; + ctx->sig_offset += sizeof(wrapper); + ctx->sig_len -= sizeof(wrapper); + if (ctx->sig_len < 4) { + pr_debug("Signature data missing\n"); + return -EKEYREJECTED; + } + + /* What's left should be a PKCS#7 cert */ + pkcs7 = pebuf + ctx->sig_offset; + if (pkcs7[0] != (ASN1_CONS_BIT | ASN1_SEQ)) + goto not_pkcs7; + + switch (pkcs7[1]) { + case 0 ... 0x7f: + len = pkcs7[1] + 2; + goto check_len; + case ASN1_INDEFINITE_LENGTH: + return 0; + case 0x81: + len = pkcs7[2] + 3; + goto check_len; + case 0x82: + len = ((pkcs7[2] << 8) | pkcs7[3]) + 4; + goto check_len; + case 0x83 ... 0xff: + return -EMSGSIZE; + default: + goto not_pkcs7; + } + +check_len: + if (len <= ctx->sig_len) { + /* There may be padding */ + ctx->sig_len = len; + return 0; + } +not_pkcs7: + pr_debug("Signature data not PKCS#7\n"); + return -ELIBBAD; +} + +/* + * Compare two sections for canonicalisation. + */ +static int pefile_compare_shdrs(const void *a, const void *b) +{ + const struct section_header *shdra = a; + const struct section_header *shdrb = b; + int rc; + + if (shdra->data_addr > shdrb->data_addr) + return 1; + if (shdrb->data_addr > shdra->data_addr) + return -1; + + if (shdra->virtual_address > shdrb->virtual_address) + return 1; + if (shdrb->virtual_address > shdra->virtual_address) + return -1; + + rc = strcmp(shdra->name, shdrb->name); + if (rc != 0) + return rc; + + if (shdra->virtual_size > shdrb->virtual_size) + return 1; + if (shdrb->virtual_size > shdra->virtual_size) + return -1; + + if (shdra->raw_data_size > shdrb->raw_data_size) + return 1; + if (shdrb->raw_data_size > shdra->raw_data_size) + return -1; + + return 0; +} + +/* + * Load the contents of the PE binary into the digest, leaving out the image + * checksum and the certificate data block. + */ +static int pefile_digest_pe_contents(const void *pebuf, unsigned int pelen, + struct pefile_context *ctx, + struct shash_desc *desc) +{ + unsigned *canon, tmp, loop, i, hashed_bytes; + int ret; + + /* Digest the header and data directory, but leave out the image + * checksum and the data dirent for the signature. + */ + ret = crypto_shash_update(desc, pebuf, ctx->image_checksum_offset); + if (ret < 0) + return ret; + + tmp = ctx->image_checksum_offset + sizeof(uint32_t); + ret = crypto_shash_update(desc, pebuf + tmp, + ctx->cert_dirent_offset - tmp); + if (ret < 0) + return ret; + + tmp = ctx->cert_dirent_offset + sizeof(struct data_dirent); + ret = crypto_shash_update(desc, pebuf + tmp, ctx->header_size - tmp); + if (ret < 0) + return ret; + + canon = kcalloc(ctx->n_sections, sizeof(unsigned), GFP_KERNEL); + if (!canon) + return -ENOMEM; + + /* We have to canonicalise the section table, so we perform an + * insertion sort. + */ + canon[0] = 0; + for (loop = 1; loop < ctx->n_sections; loop++) { + for (i = 0; i < loop; i++) { + if (pefile_compare_shdrs(&ctx->secs[canon[i]], + &ctx->secs[loop]) > 0) { + memmove(&canon[i + 1], &canon[i], + (loop - i) * sizeof(canon[0])); + break; + } + } + canon[i] = loop; + } + + hashed_bytes = ctx->header_size; + for (loop = 0; loop < ctx->n_sections; loop++) { + i = canon[loop]; + if (ctx->secs[i].raw_data_size == 0) + continue; + ret = crypto_shash_update(desc, + pebuf + ctx->secs[i].data_addr, + ctx->secs[i].raw_data_size); + if (ret < 0) { + kfree(canon); + return ret; + } + hashed_bytes += ctx->secs[i].raw_data_size; + } + kfree(canon); + + if (pelen > hashed_bytes) { + tmp = hashed_bytes + ctx->certs_size; + ret = crypto_shash_update(desc, + pebuf + hashed_bytes, + pelen - tmp); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * Digest the contents of the PE binary, leaving out the image checksum and the + * certificate data block. + */ +static int pefile_digest_pe(const void *pebuf, unsigned int pelen, + struct pefile_context *ctx) +{ + struct crypto_shash *tfm; + struct shash_desc *desc; + size_t digest_size, desc_size; + void *digest; + int ret; + + kenter(",%s", ctx->digest_algo); + + /* Allocate the hashing algorithm we're going to need and find out how + * big the hash operational data will be. + */ + tfm = crypto_alloc_shash(ctx->digest_algo, 0, 0); + if (IS_ERR(tfm)) + return (PTR_ERR(tfm) == -ENOENT) ? -ENOPKG : PTR_ERR(tfm); + + desc_size = crypto_shash_descsize(tfm) + sizeof(*desc); + digest_size = crypto_shash_digestsize(tfm); + + if (digest_size != ctx->digest_len) { + pr_debug("Digest size mismatch (%zx != %x)\n", + digest_size, ctx->digest_len); + ret = -EBADMSG; + goto error_no_desc; + } + pr_debug("Digest: desc=%zu size=%zu\n", desc_size, digest_size); + + ret = -ENOMEM; + desc = kzalloc(desc_size + digest_size, GFP_KERNEL); + if (!desc) + goto error_no_desc; + + desc->tfm = tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + ret = crypto_shash_init(desc); + if (ret < 0) + goto error; + + ret = pefile_digest_pe_contents(pebuf, pelen, ctx, desc); + if (ret < 0) + goto error; + + digest = (void *)desc + desc_size; + ret = crypto_shash_final(desc, digest); + if (ret < 0) + goto error; + + pr_debug("Digest calc = [%*ph]\n", ctx->digest_len, digest); + + /* Check that the PE file digest matches that in the MSCODE part of the + * PKCS#7 certificate. + */ + if (memcmp(digest, ctx->digest, ctx->digest_len) != 0) { + pr_debug("Digest mismatch\n"); + ret = -EKEYREJECTED; + } else { + pr_debug("The digests match!\n"); + } + +error: + kzfree(desc); +error_no_desc: + crypto_free_shash(tfm); + kleave(" = %d", ret); + return ret; +} + +/** + * verify_pefile_signature - Verify the signature on a PE binary image + * @pebuf: Buffer containing the PE binary image + * @pelen: Length of the binary image + * @trust_keys: Signing certificate(s) to use as starting points + * @usage: The use to which the key is being put. + * + * Validate that the certificate chain inside the PKCS#7 message inside the PE + * binary image intersects keys we already know and trust. + * + * Returns, in order of descending priority: + * + * (*) -ELIBBAD if the image cannot be parsed, or: + * + * (*) -EKEYREJECTED if a signature failed to match for which we have a valid + * key, or: + * + * (*) 0 if at least one signature chain intersects with the keys in the trust + * keyring, or: + * + * (*) -ENOPKG if a suitable crypto module couldn't be found for a check on a + * chain. + * + * (*) -ENOKEY if we couldn't find a match for any of the signature chains in + * the message. + * + * May also return -ENOMEM. + */ +int verify_pefile_signature(const void *pebuf, unsigned pelen, + struct key *trusted_keys, + enum key_being_used_for usage) +{ + struct pefile_context ctx; + int ret; + + kenter(""); + + memset(&ctx, 0, sizeof(ctx)); + ret = pefile_parse_binary(pebuf, pelen, &ctx); + if (ret < 0) + return ret; + + ret = pefile_strip_sig_wrapper(pebuf, &ctx); + if (ret < 0) + return ret; + + ret = verify_pkcs7_signature(NULL, 0, + pebuf + ctx.sig_offset, ctx.sig_len, + trusted_keys, usage, + mscode_parse, &ctx); + if (ret < 0) + goto error; + + pr_debug("Digest: %u [%*ph]\n", + ctx.digest_len, ctx.digest_len, ctx.digest); + + /* Generate the digest and check against the PKCS7 certificate + * contents. + */ + ret = pefile_digest_pe(pebuf, pelen, &ctx); + +error: + kzfree(ctx.digest); + return ret; +} diff --git a/crypto/asymmetric_keys/verify_pefile.h b/crypto/asymmetric_keys/verify_pefile.h new file mode 100644 index 000000000..cd4d20930 --- /dev/null +++ b/crypto/asymmetric_keys/verify_pefile.h @@ -0,0 +1,41 @@ +/* PE Binary parser bits + * + * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <crypto/pkcs7.h> +#include <crypto/hash_info.h> + +struct pefile_context { + unsigned header_size; + unsigned image_checksum_offset; + unsigned cert_dirent_offset; + unsigned n_data_dirents; + unsigned n_sections; + unsigned certs_size; + unsigned sig_offset; + unsigned sig_len; + const struct section_header *secs; + + /* PKCS#7 MS Individual Code Signing content */ + const void *digest; /* Digest */ + unsigned digest_len; /* Digest length */ + const char *digest_algo; /* Digest algorithm */ +}; + +#define kenter(FMT, ...) \ + pr_devel("==> %s("FMT")\n", __func__, ##__VA_ARGS__) +#define kleave(FMT, ...) \ + pr_devel("<== %s()"FMT"\n", __func__, ##__VA_ARGS__) + +/* + * mscode_parser.c + */ +extern int mscode_parse(void *_ctx, const void *content_data, size_t data_len, + size_t asn1hdrlen); diff --git a/crypto/asymmetric_keys/x509.asn1 b/crypto/asymmetric_keys/x509.asn1 new file mode 100644 index 000000000..aae0cde41 --- /dev/null +++ b/crypto/asymmetric_keys/x509.asn1 @@ -0,0 +1,60 @@ +Certificate ::= SEQUENCE { + tbsCertificate TBSCertificate ({ x509_note_tbs_certificate }), + signatureAlgorithm AlgorithmIdentifier, + signature BIT STRING ({ x509_note_signature }) + } + +TBSCertificate ::= SEQUENCE { + version [ 0 ] Version DEFAULT, + serialNumber CertificateSerialNumber ({ x509_note_serial }), + signature AlgorithmIdentifier ({ x509_note_pkey_algo }), + issuer Name ({ x509_note_issuer }), + validity Validity, + subject Name ({ x509_note_subject }), + subjectPublicKeyInfo SubjectPublicKeyInfo, + issuerUniqueID [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL, + subjectUniqueID [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL, + extensions [ 3 ] Extensions OPTIONAL + } + +Version ::= INTEGER +CertificateSerialNumber ::= INTEGER + +AlgorithmIdentifier ::= SEQUENCE { + algorithm OBJECT IDENTIFIER ({ x509_note_OID }), + parameters ANY OPTIONAL +} + +Name ::= SEQUENCE OF RelativeDistinguishedName + +RelativeDistinguishedName ::= SET OF AttributeValueAssertion + +AttributeValueAssertion ::= SEQUENCE { + attributeType OBJECT IDENTIFIER ({ x509_note_OID }), + attributeValue ANY ({ x509_extract_name_segment }) + } + +Validity ::= SEQUENCE { + notBefore Time ({ x509_note_not_before }), + notAfter Time ({ x509_note_not_after }) + } + +Time ::= CHOICE { + utcTime UTCTime, + generalTime GeneralizedTime + } + +SubjectPublicKeyInfo ::= SEQUENCE { + algorithm AlgorithmIdentifier, + subjectPublicKey BIT STRING ({ x509_extract_key_data }) + } + +UniqueIdentifier ::= BIT STRING + +Extensions ::= SEQUENCE OF Extension + +Extension ::= SEQUENCE { + extnid OBJECT IDENTIFIER ({ x509_note_OID }), + critical BOOLEAN DEFAULT, + extnValue OCTET STRING ({ x509_process_extension }) + } diff --git a/crypto/asymmetric_keys/x509_akid.asn1 b/crypto/asymmetric_keys/x509_akid.asn1 new file mode 100644 index 000000000..1a33231a7 --- /dev/null +++ b/crypto/asymmetric_keys/x509_akid.asn1 @@ -0,0 +1,35 @@ +-- X.509 AuthorityKeyIdentifier +-- rfc5280 section 4.2.1.1 + +AuthorityKeyIdentifier ::= SEQUENCE { + keyIdentifier [0] IMPLICIT KeyIdentifier OPTIONAL, + authorityCertIssuer [1] IMPLICIT GeneralNames OPTIONAL, + authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL + } + +KeyIdentifier ::= OCTET STRING ({ x509_akid_note_kid }) + +CertificateSerialNumber ::= INTEGER ({ x509_akid_note_serial }) + +GeneralNames ::= SEQUENCE OF GeneralName + +GeneralName ::= CHOICE { + otherName [0] ANY, + rfc822Name [1] IA5String, + dNSName [2] IA5String, + x400Address [3] ANY, + directoryName [4] Name ({ x509_akid_note_name }), + ediPartyName [5] ANY, + uniformResourceIdentifier [6] IA5String, + iPAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER + } + +Name ::= SEQUENCE OF RelativeDistinguishedName + +RelativeDistinguishedName ::= SET OF AttributeValueAssertion + +AttributeValueAssertion ::= SEQUENCE { + attributeType OBJECT IDENTIFIER ({ x509_note_OID }), + attributeValue ANY ({ x509_extract_name_segment }) + } diff --git a/crypto/asymmetric_keys/x509_cert_parser.c b/crypto/asymmetric_keys/x509_cert_parser.c new file mode 100644 index 000000000..b6cabac4b --- /dev/null +++ b/crypto/asymmetric_keys/x509_cert_parser.c @@ -0,0 +1,648 @@ +/* X.509 certificate parser + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "X.509: "fmt +#include <linux/kernel.h> +#include <linux/export.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/oid_registry.h> +#include <crypto/public_key.h> +#include "x509_parser.h" +#include "x509.asn1.h" +#include "x509_akid.asn1.h" + +struct x509_parse_context { + struct x509_certificate *cert; /* Certificate being constructed */ + unsigned long data; /* Start of data */ + const void *cert_start; /* Start of cert content */ + const void *key; /* Key data */ + size_t key_size; /* Size of key data */ + enum OID last_oid; /* Last OID encountered */ + enum OID algo_oid; /* Algorithm OID */ + unsigned char nr_mpi; /* Number of MPIs stored */ + u8 o_size; /* Size of organizationName (O) */ + u8 cn_size; /* Size of commonName (CN) */ + u8 email_size; /* Size of emailAddress */ + u16 o_offset; /* Offset of organizationName (O) */ + u16 cn_offset; /* Offset of commonName (CN) */ + u16 email_offset; /* Offset of emailAddress */ + unsigned raw_akid_size; + const void *raw_akid; /* Raw authorityKeyId in ASN.1 */ + const void *akid_raw_issuer; /* Raw directoryName in authorityKeyId */ + unsigned akid_raw_issuer_size; +}; + +/* + * Free an X.509 certificate + */ +void x509_free_certificate(struct x509_certificate *cert) +{ + if (cert) { + public_key_free(cert->pub); + public_key_signature_free(cert->sig); + kfree(cert->issuer); + kfree(cert->subject); + kfree(cert->id); + kfree(cert->skid); + kfree(cert); + } +} +EXPORT_SYMBOL_GPL(x509_free_certificate); + +/* + * Parse an X.509 certificate + */ +struct x509_certificate *x509_cert_parse(const void *data, size_t datalen) +{ + struct x509_certificate *cert; + struct x509_parse_context *ctx; + struct asymmetric_key_id *kid; + long ret; + + ret = -ENOMEM; + cert = kzalloc(sizeof(struct x509_certificate), GFP_KERNEL); + if (!cert) + goto error_no_cert; + cert->pub = kzalloc(sizeof(struct public_key), GFP_KERNEL); + if (!cert->pub) + goto error_no_ctx; + cert->sig = kzalloc(sizeof(struct public_key_signature), GFP_KERNEL); + if (!cert->sig) + goto error_no_ctx; + ctx = kzalloc(sizeof(struct x509_parse_context), GFP_KERNEL); + if (!ctx) + goto error_no_ctx; + + ctx->cert = cert; + ctx->data = (unsigned long)data; + + /* Attempt to decode the certificate */ + ret = asn1_ber_decoder(&x509_decoder, ctx, data, datalen); + if (ret < 0) + goto error_decode; + + /* Decode the AuthorityKeyIdentifier */ + if (ctx->raw_akid) { + pr_devel("AKID: %u %*phN\n", + ctx->raw_akid_size, ctx->raw_akid_size, ctx->raw_akid); + ret = asn1_ber_decoder(&x509_akid_decoder, ctx, + ctx->raw_akid, ctx->raw_akid_size); + if (ret < 0) { + pr_warn("Couldn't decode AuthKeyIdentifier\n"); + goto error_decode; + } + } + + ret = -ENOMEM; + cert->pub->key = kmemdup(ctx->key, ctx->key_size, GFP_KERNEL); + if (!cert->pub->key) + goto error_decode; + + cert->pub->keylen = ctx->key_size; + + /* Grab the signature bits */ + ret = x509_get_sig_params(cert); + if (ret < 0) + goto error_decode; + + /* Generate cert issuer + serial number key ID */ + kid = asymmetric_key_generate_id(cert->raw_serial, + cert->raw_serial_size, + cert->raw_issuer, + cert->raw_issuer_size); + if (IS_ERR(kid)) { + ret = PTR_ERR(kid); + goto error_decode; + } + cert->id = kid; + + /* Detect self-signed certificates */ + ret = x509_check_for_self_signed(cert); + if (ret < 0) + goto error_decode; + + kfree(ctx); + return cert; + +error_decode: + kfree(ctx); +error_no_ctx: + x509_free_certificate(cert); +error_no_cert: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(x509_cert_parse); + +/* + * Note an OID when we find one for later processing when we know how + * to interpret it. + */ +int x509_note_OID(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + ctx->last_oid = look_up_OID(value, vlen); + if (ctx->last_oid == OID__NR) { + char buffer[50]; + sprint_oid(value, vlen, buffer, sizeof(buffer)); + pr_debug("Unknown OID: [%lu] %s\n", + (unsigned long)value - ctx->data, buffer); + } + return 0; +} + +/* + * Save the position of the TBS data so that we can check the signature over it + * later. + */ +int x509_note_tbs_certificate(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("x509_note_tbs_certificate(,%zu,%02x,%ld,%zu)!\n", + hdrlen, tag, (unsigned long)value - ctx->data, vlen); + + ctx->cert->tbs = value - hdrlen; + ctx->cert->tbs_size = vlen + hdrlen; + return 0; +} + +/* + * Record the public key algorithm + */ +int x509_note_pkey_algo(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("PubKey Algo: %u\n", ctx->last_oid); + + switch (ctx->last_oid) { + case OID_md2WithRSAEncryption: + case OID_md3WithRSAEncryption: + default: + return -ENOPKG; /* Unsupported combination */ + + case OID_md4WithRSAEncryption: + ctx->cert->sig->hash_algo = "md4"; + ctx->cert->sig->pkey_algo = "rsa"; + break; + + case OID_sha1WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha1"; + ctx->cert->sig->pkey_algo = "rsa"; + break; + + case OID_sha256WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha256"; + ctx->cert->sig->pkey_algo = "rsa"; + break; + + case OID_sha384WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha384"; + ctx->cert->sig->pkey_algo = "rsa"; + break; + + case OID_sha512WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha512"; + ctx->cert->sig->pkey_algo = "rsa"; + break; + + case OID_sha224WithRSAEncryption: + ctx->cert->sig->hash_algo = "sha224"; + ctx->cert->sig->pkey_algo = "rsa"; + break; + } + + ctx->algo_oid = ctx->last_oid; + return 0; +} + +/* + * Note the whereabouts and type of the signature. + */ +int x509_note_signature(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("Signature type: %u size %zu\n", ctx->last_oid, vlen); + + if (ctx->last_oid != ctx->algo_oid) { + pr_warn("Got cert with pkey (%u) and sig (%u) algorithm OIDs\n", + ctx->algo_oid, ctx->last_oid); + return -EINVAL; + } + + if (strcmp(ctx->cert->sig->pkey_algo, "rsa") == 0) { + /* Discard the BIT STRING metadata */ + if (vlen < 1 || *(const u8 *)value != 0) + return -EBADMSG; + + value++; + vlen--; + } + + ctx->cert->raw_sig = value; + ctx->cert->raw_sig_size = vlen; + return 0; +} + +/* + * Note the certificate serial number + */ +int x509_note_serial(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + ctx->cert->raw_serial = value; + ctx->cert->raw_serial_size = vlen; + return 0; +} + +/* + * Note some of the name segments from which we'll fabricate a name. + */ +int x509_extract_name_segment(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + switch (ctx->last_oid) { + case OID_commonName: + ctx->cn_size = vlen; + ctx->cn_offset = (unsigned long)value - ctx->data; + break; + case OID_organizationName: + ctx->o_size = vlen; + ctx->o_offset = (unsigned long)value - ctx->data; + break; + case OID_email_address: + ctx->email_size = vlen; + ctx->email_offset = (unsigned long)value - ctx->data; + break; + default: + break; + } + + return 0; +} + +/* + * Fabricate and save the issuer and subject names + */ +static int x509_fabricate_name(struct x509_parse_context *ctx, size_t hdrlen, + unsigned char tag, + char **_name, size_t vlen) +{ + const void *name, *data = (const void *)ctx->data; + size_t namesize; + char *buffer; + + if (*_name) + return -EINVAL; + + /* Empty name string if no material */ + if (!ctx->cn_size && !ctx->o_size && !ctx->email_size) { + buffer = kmalloc(1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + buffer[0] = 0; + goto done; + } + + if (ctx->cn_size && ctx->o_size) { + /* Consider combining O and CN, but use only the CN if it is + * prefixed by the O, or a significant portion thereof. + */ + namesize = ctx->cn_size; + name = data + ctx->cn_offset; + if (ctx->cn_size >= ctx->o_size && + memcmp(data + ctx->cn_offset, data + ctx->o_offset, + ctx->o_size) == 0) + goto single_component; + if (ctx->cn_size >= 7 && + ctx->o_size >= 7 && + memcmp(data + ctx->cn_offset, data + ctx->o_offset, 7) == 0) + goto single_component; + + buffer = kmalloc(ctx->o_size + 2 + ctx->cn_size + 1, + GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + memcpy(buffer, + data + ctx->o_offset, ctx->o_size); + buffer[ctx->o_size + 0] = ':'; + buffer[ctx->o_size + 1] = ' '; + memcpy(buffer + ctx->o_size + 2, + data + ctx->cn_offset, ctx->cn_size); + buffer[ctx->o_size + 2 + ctx->cn_size] = 0; + goto done; + + } else if (ctx->cn_size) { + namesize = ctx->cn_size; + name = data + ctx->cn_offset; + } else if (ctx->o_size) { + namesize = ctx->o_size; + name = data + ctx->o_offset; + } else { + namesize = ctx->email_size; + name = data + ctx->email_offset; + } + +single_component: + buffer = kmalloc(namesize + 1, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + memcpy(buffer, name, namesize); + buffer[namesize] = 0; + +done: + *_name = buffer; + ctx->cn_size = 0; + ctx->o_size = 0; + ctx->email_size = 0; + return 0; +} + +int x509_note_issuer(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + ctx->cert->raw_issuer = value; + ctx->cert->raw_issuer_size = vlen; + return x509_fabricate_name(ctx, hdrlen, tag, &ctx->cert->issuer, vlen); +} + +int x509_note_subject(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + ctx->cert->raw_subject = value; + ctx->cert->raw_subject_size = vlen; + return x509_fabricate_name(ctx, hdrlen, tag, &ctx->cert->subject, vlen); +} + +/* + * Extract the data for the public key algorithm + */ +int x509_extract_key_data(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + if (ctx->last_oid != OID_rsaEncryption) + return -ENOPKG; + + ctx->cert->pub->pkey_algo = "rsa"; + + /* Discard the BIT STRING metadata */ + if (vlen < 1 || *(const u8 *)value != 0) + return -EBADMSG; + ctx->key = value + 1; + ctx->key_size = vlen - 1; + return 0; +} + +/* The keyIdentifier in AuthorityKeyIdentifier SEQUENCE is tag(CONT,PRIM,0) */ +#define SEQ_TAG_KEYID (ASN1_CONT << 6) + +/* + * Process certificate extensions that are used to qualify the certificate. + */ +int x509_process_extension(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + struct asymmetric_key_id *kid; + const unsigned char *v = value; + + pr_debug("Extension: %u\n", ctx->last_oid); + + if (ctx->last_oid == OID_subjectKeyIdentifier) { + /* Get hold of the key fingerprint */ + if (ctx->cert->skid || vlen < 3) + return -EBADMSG; + if (v[0] != ASN1_OTS || v[1] != vlen - 2) + return -EBADMSG; + v += 2; + vlen -= 2; + + ctx->cert->raw_skid_size = vlen; + ctx->cert->raw_skid = v; + kid = asymmetric_key_generate_id(v, vlen, "", 0); + if (IS_ERR(kid)) + return PTR_ERR(kid); + ctx->cert->skid = kid; + pr_debug("subjkeyid %*phN\n", kid->len, kid->data); + return 0; + } + + if (ctx->last_oid == OID_authorityKeyIdentifier) { + /* Get hold of the CA key fingerprint */ + ctx->raw_akid = v; + ctx->raw_akid_size = vlen; + return 0; + } + + return 0; +} + +/** + * x509_decode_time - Decode an X.509 time ASN.1 object + * @_t: The time to fill in + * @hdrlen: The length of the object header + * @tag: The object tag + * @value: The object value + * @vlen: The size of the object value + * + * Decode an ASN.1 universal time or generalised time field into a struct the + * kernel can handle and check it for validity. The time is decoded thus: + * + * [RFC5280 ยง4.1.2.5] + * CAs conforming to this profile MUST always encode certificate validity + * dates through the year 2049 as UTCTime; certificate validity dates in + * 2050 or later MUST be encoded as GeneralizedTime. Conforming + * applications MUST be able to process validity dates that are encoded in + * either UTCTime or GeneralizedTime. + */ +int x509_decode_time(time64_t *_t, size_t hdrlen, + unsigned char tag, + const unsigned char *value, size_t vlen) +{ + static const unsigned char month_lengths[] = { 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31 }; + const unsigned char *p = value; + unsigned year, mon, day, hour, min, sec, mon_len; + +#define dec2bin(X) ({ unsigned char x = (X) - '0'; if (x > 9) goto invalid_time; x; }) +#define DD2bin(P) ({ unsigned x = dec2bin(P[0]) * 10 + dec2bin(P[1]); P += 2; x; }) + + if (tag == ASN1_UNITIM) { + /* UTCTime: YYMMDDHHMMSSZ */ + if (vlen != 13) + goto unsupported_time; + year = DD2bin(p); + if (year >= 50) + year += 1900; + else + year += 2000; + } else if (tag == ASN1_GENTIM) { + /* GenTime: YYYYMMDDHHMMSSZ */ + if (vlen != 15) + goto unsupported_time; + year = DD2bin(p) * 100 + DD2bin(p); + if (year >= 1950 && year <= 2049) + goto invalid_time; + } else { + goto unsupported_time; + } + + mon = DD2bin(p); + day = DD2bin(p); + hour = DD2bin(p); + min = DD2bin(p); + sec = DD2bin(p); + + if (*p != 'Z') + goto unsupported_time; + + if (year < 1970 || + mon < 1 || mon > 12) + goto invalid_time; + + mon_len = month_lengths[mon - 1]; + if (mon == 2) { + if (year % 4 == 0) { + mon_len = 29; + if (year % 100 == 0) { + mon_len = 28; + if (year % 400 == 0) + mon_len = 29; + } + } + } + + if (day < 1 || day > mon_len || + hour > 24 || /* ISO 8601 permits 24:00:00 as midnight tomorrow */ + min > 59 || + sec > 60) /* ISO 8601 permits leap seconds [X.680 46.3] */ + goto invalid_time; + + *_t = mktime64(year, mon, day, hour, min, sec); + return 0; + +unsupported_time: + pr_debug("Got unsupported time [tag %02x]: '%*phN'\n", + tag, (int)vlen, value); + return -EBADMSG; +invalid_time: + pr_debug("Got invalid time [tag %02x]: '%*phN'\n", + tag, (int)vlen, value); + return -EBADMSG; +} +EXPORT_SYMBOL_GPL(x509_decode_time); + +int x509_note_not_before(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + return x509_decode_time(&ctx->cert->valid_from, hdrlen, tag, value, vlen); +} + +int x509_note_not_after(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + return x509_decode_time(&ctx->cert->valid_to, hdrlen, tag, value, vlen); +} + +/* + * Note a key identifier-based AuthorityKeyIdentifier + */ +int x509_akid_note_kid(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + struct asymmetric_key_id *kid; + + pr_debug("AKID: keyid: %*phN\n", (int)vlen, value); + + if (ctx->cert->sig->auth_ids[1]) + return 0; + + kid = asymmetric_key_generate_id(value, vlen, "", 0); + if (IS_ERR(kid)) + return PTR_ERR(kid); + pr_debug("authkeyid %*phN\n", kid->len, kid->data); + ctx->cert->sig->auth_ids[1] = kid; + return 0; +} + +/* + * Note a directoryName in an AuthorityKeyIdentifier + */ +int x509_akid_note_name(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + + pr_debug("AKID: name: %*phN\n", (int)vlen, value); + + ctx->akid_raw_issuer = value; + ctx->akid_raw_issuer_size = vlen; + return 0; +} + +/* + * Note a serial number in an AuthorityKeyIdentifier + */ +int x509_akid_note_serial(void *context, size_t hdrlen, + unsigned char tag, + const void *value, size_t vlen) +{ + struct x509_parse_context *ctx = context; + struct asymmetric_key_id *kid; + + pr_debug("AKID: serial: %*phN\n", (int)vlen, value); + + if (!ctx->akid_raw_issuer || ctx->cert->sig->auth_ids[0]) + return 0; + + kid = asymmetric_key_generate_id(value, + vlen, + ctx->akid_raw_issuer, + ctx->akid_raw_issuer_size); + if (IS_ERR(kid)) + return PTR_ERR(kid); + + pr_debug("authkeyid %*phN\n", kid->len, kid->data); + ctx->cert->sig->auth_ids[0] = kid; + return 0; +} diff --git a/crypto/asymmetric_keys/x509_parser.h b/crypto/asymmetric_keys/x509_parser.h new file mode 100644 index 000000000..e373e7483 --- /dev/null +++ b/crypto/asymmetric_keys/x509_parser.h @@ -0,0 +1,61 @@ +/* X.509 certificate parser internal definitions + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#include <linux/time.h> +#include <crypto/public_key.h> +#include <keys/asymmetric-type.h> + +struct x509_certificate { + struct x509_certificate *next; + struct x509_certificate *signer; /* Certificate that signed this one */ + struct public_key *pub; /* Public key details */ + struct public_key_signature *sig; /* Signature parameters */ + char *issuer; /* Name of certificate issuer */ + char *subject; /* Name of certificate subject */ + struct asymmetric_key_id *id; /* Issuer + Serial number */ + struct asymmetric_key_id *skid; /* Subject + subjectKeyId (optional) */ + time64_t valid_from; + time64_t valid_to; + const void *tbs; /* Signed data */ + unsigned tbs_size; /* Size of signed data */ + unsigned raw_sig_size; /* Size of sigature */ + const void *raw_sig; /* Signature data */ + const void *raw_serial; /* Raw serial number in ASN.1 */ + unsigned raw_serial_size; + unsigned raw_issuer_size; + const void *raw_issuer; /* Raw issuer name in ASN.1 */ + const void *raw_subject; /* Raw subject name in ASN.1 */ + unsigned raw_subject_size; + unsigned raw_skid_size; + const void *raw_skid; /* Raw subjectKeyId in ASN.1 */ + unsigned index; + bool seen; /* Infinite recursion prevention */ + bool verified; + bool self_signed; /* T if self-signed (check unsupported_sig too) */ + bool unsupported_key; /* T if key uses unsupported crypto */ + bool unsupported_sig; /* T if signature uses unsupported crypto */ + bool blacklisted; +}; + +/* + * x509_cert_parser.c + */ +extern void x509_free_certificate(struct x509_certificate *cert); +extern struct x509_certificate *x509_cert_parse(const void *data, size_t datalen); +extern int x509_decode_time(time64_t *_t, size_t hdrlen, + unsigned char tag, + const unsigned char *value, size_t vlen); + +/* + * x509_public_key.c + */ +extern int x509_get_sig_params(struct x509_certificate *cert); +extern int x509_check_for_self_signed(struct x509_certificate *cert); diff --git a/crypto/asymmetric_keys/x509_public_key.c b/crypto/asymmetric_keys/x509_public_key.c new file mode 100644 index 000000000..9338b4558 --- /dev/null +++ b/crypto/asymmetric_keys/x509_public_key.c @@ -0,0 +1,275 @@ +/* Instantiate a public key crypto key from an X.509 Certificate + * + * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved. + * Written by David Howells (dhowells@redhat.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public Licence + * as published by the Free Software Foundation; either version + * 2 of the Licence, or (at your option) any later version. + */ + +#define pr_fmt(fmt) "X.509: "fmt +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <keys/asymmetric-subtype.h> +#include <keys/asymmetric-parser.h> +#include <keys/system_keyring.h> +#include <crypto/hash.h> +#include "asymmetric_keys.h" +#include "x509_parser.h" + +/* + * Set up the signature parameters in an X.509 certificate. This involves + * digesting the signed data and extracting the signature. + */ +int x509_get_sig_params(struct x509_certificate *cert) +{ + struct public_key_signature *sig = cert->sig; + struct crypto_shash *tfm; + struct shash_desc *desc; + size_t desc_size; + int ret; + + pr_devel("==>%s()\n", __func__); + + if (!cert->pub->pkey_algo) + cert->unsupported_key = true; + + if (!sig->pkey_algo) + cert->unsupported_sig = true; + + /* We check the hash if we can - even if we can't then verify it */ + if (!sig->hash_algo) { + cert->unsupported_sig = true; + return 0; + } + + sig->s = kmemdup(cert->raw_sig, cert->raw_sig_size, GFP_KERNEL); + if (!sig->s) + return -ENOMEM; + + sig->s_size = cert->raw_sig_size; + + /* Allocate the hashing algorithm we're going to need and find out how + * big the hash operational data will be. + */ + tfm = crypto_alloc_shash(sig->hash_algo, 0, 0); + if (IS_ERR(tfm)) { + if (PTR_ERR(tfm) == -ENOENT) { + cert->unsupported_sig = true; + return 0; + } + return PTR_ERR(tfm); + } + + desc_size = crypto_shash_descsize(tfm) + sizeof(*desc); + sig->digest_size = crypto_shash_digestsize(tfm); + + ret = -ENOMEM; + sig->digest = kmalloc(sig->digest_size, GFP_KERNEL); + if (!sig->digest) + goto error; + + desc = kzalloc(desc_size, GFP_KERNEL); + if (!desc) + goto error; + + desc->tfm = tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + + ret = crypto_shash_digest(desc, cert->tbs, cert->tbs_size, sig->digest); + if (ret < 0) + goto error_2; + + ret = is_hash_blacklisted(sig->digest, sig->digest_size, "tbs"); + if (ret == -EKEYREJECTED) { + pr_err("Cert %*phN is blacklisted\n", + sig->digest_size, sig->digest); + cert->blacklisted = true; + ret = 0; + } + +error_2: + kfree(desc); +error: + crypto_free_shash(tfm); + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; +} + +/* + * Check for self-signedness in an X.509 cert and if found, check the signature + * immediately if we can. + */ +int x509_check_for_self_signed(struct x509_certificate *cert) +{ + int ret = 0; + + pr_devel("==>%s()\n", __func__); + + if (cert->raw_subject_size != cert->raw_issuer_size || + memcmp(cert->raw_subject, cert->raw_issuer, + cert->raw_issuer_size) != 0) + goto not_self_signed; + + if (cert->sig->auth_ids[0] || cert->sig->auth_ids[1]) { + /* If the AKID is present it may have one or two parts. If + * both are supplied, both must match. + */ + bool a = asymmetric_key_id_same(cert->skid, cert->sig->auth_ids[1]); + bool b = asymmetric_key_id_same(cert->id, cert->sig->auth_ids[0]); + + if (!a && !b) + goto not_self_signed; + + ret = -EKEYREJECTED; + if (((a && !b) || (b && !a)) && + cert->sig->auth_ids[0] && cert->sig->auth_ids[1]) + goto out; + } + + ret = -EKEYREJECTED; + if (strcmp(cert->pub->pkey_algo, cert->sig->pkey_algo) != 0) + goto out; + + ret = public_key_verify_signature(cert->pub, cert->sig); + if (ret < 0) { + if (ret == -ENOPKG) { + cert->unsupported_sig = true; + ret = 0; + } + goto out; + } + + pr_devel("Cert Self-signature verified"); + cert->self_signed = true; + +out: + pr_devel("<==%s() = %d\n", __func__, ret); + return ret; + +not_self_signed: + pr_devel("<==%s() = 0 [not]\n", __func__); + return 0; +} + +/* + * Attempt to parse a data blob for a key as an X509 certificate. + */ +static int x509_key_preparse(struct key_preparsed_payload *prep) +{ + struct asymmetric_key_ids *kids; + struct x509_certificate *cert; + const char *q; + size_t srlen, sulen; + char *desc = NULL, *p; + int ret; + + cert = x509_cert_parse(prep->data, prep->datalen); + if (IS_ERR(cert)) + return PTR_ERR(cert); + + pr_devel("Cert Issuer: %s\n", cert->issuer); + pr_devel("Cert Subject: %s\n", cert->subject); + + if (cert->unsupported_key) { + ret = -ENOPKG; + goto error_free_cert; + } + + pr_devel("Cert Key Algo: %s\n", cert->pub->pkey_algo); + pr_devel("Cert Valid period: %lld-%lld\n", cert->valid_from, cert->valid_to); + + cert->pub->id_type = "X509"; + + if (cert->unsupported_sig) { + public_key_signature_free(cert->sig); + cert->sig = NULL; + } else { + pr_devel("Cert Signature: %s + %s\n", + cert->sig->pkey_algo, cert->sig->hash_algo); + } + + /* Don't permit addition of blacklisted keys */ + ret = -EKEYREJECTED; + if (cert->blacklisted) + goto error_free_cert; + + /* Propose a description */ + sulen = strlen(cert->subject); + if (cert->raw_skid) { + srlen = cert->raw_skid_size; + q = cert->raw_skid; + } else { + srlen = cert->raw_serial_size; + q = cert->raw_serial; + } + + ret = -ENOMEM; + desc = kmalloc(sulen + 2 + srlen * 2 + 1, GFP_KERNEL); + if (!desc) + goto error_free_cert; + p = memcpy(desc, cert->subject, sulen); + p += sulen; + *p++ = ':'; + *p++ = ' '; + p = bin2hex(p, q, srlen); + *p = 0; + + kids = kmalloc(sizeof(struct asymmetric_key_ids), GFP_KERNEL); + if (!kids) + goto error_free_desc; + kids->id[0] = cert->id; + kids->id[1] = cert->skid; + + /* We're pinning the module by being linked against it */ + __module_get(public_key_subtype.owner); + prep->payload.data[asym_subtype] = &public_key_subtype; + prep->payload.data[asym_key_ids] = kids; + prep->payload.data[asym_crypto] = cert->pub; + prep->payload.data[asym_auth] = cert->sig; + prep->description = desc; + prep->quotalen = 100; + + /* We've finished with the certificate */ + cert->pub = NULL; + cert->id = NULL; + cert->skid = NULL; + cert->sig = NULL; + desc = NULL; + ret = 0; + +error_free_desc: + kfree(desc); +error_free_cert: + x509_free_certificate(cert); + return ret; +} + +static struct asymmetric_key_parser x509_key_parser = { + .owner = THIS_MODULE, + .name = "x509", + .parse = x509_key_preparse, +}; + +/* + * Module stuff + */ +static int __init x509_key_init(void) +{ + return register_asymmetric_key_parser(&x509_key_parser); +} + +static void __exit x509_key_exit(void) +{ + unregister_asymmetric_key_parser(&x509_key_parser); +} + +module_init(x509_key_init); +module_exit(x509_key_exit); + +MODULE_DESCRIPTION("X.509 certificate parser"); +MODULE_AUTHOR("Red Hat, Inc."); +MODULE_LICENSE("GPL"); |