From efb81a423eb745a5a4545649ea38e28229e02b6a Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 1 Jul 2014 16:40:19 +0100 Subject: PKCS#7: Implement a parser [RFC 2315] Implement a parser for a PKCS#7 signed-data message as described in part of RFC 2315. Signed-off-by: David Howells Acked-by: Vivek Goyal Reviewed-by: Kees Cook --- crypto/asymmetric_keys/Makefile | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'crypto/asymmetric_keys/Makefile') diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index 0727204a..59d8cade 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -25,3 +25,16 @@ $(obj)/x509_rsakey-asn1.o: $(obj)/x509_rsakey-asn1.c $(obj)/x509_rsakey-asn1.h clean-files += x509-asn1.c x509-asn1.h clean-files += x509_rsakey-asn1.c x509_rsakey-asn1.h + +# +# PKCS#7 message handling +# +obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o +pkcs7_message-y := \ + pkcs7-asn1.o \ + pkcs7_parser.o + +$(obj)/pkcs7_parser.o: $(obj)/pkcs7-asn1.h +$(obj)/pkcs7-asn1.o: $(obj)/pkcs7-asn1.c $(obj)/pkcs7-asn1.h + +clean-files += pkcs7-asn1.c pkcs7-asn1.h -- cgit v1.2.3 From d09d7677474d9ebee35d8ced48a24a50f7d3c12f Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 1 Jul 2014 16:40:19 +0100 Subject: PKCS#7: Digest the data in a signed-data message Digest the data in a PKCS#7 signed-data message and attach to the public_key_signature struct contained in the pkcs7_message struct. Signed-off-by: David Howells Acked-by: Vivek Goyal Reviewed-by: Kees Cook --- crypto/asymmetric_keys/Makefile | 3 +- crypto/asymmetric_keys/pkcs7_verify.c | 173 ++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 crypto/asymmetric_keys/pkcs7_verify.c (limited to 'crypto/asymmetric_keys/Makefile') diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index 59d8cade..b6b39e7b 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -32,7 +32,8 @@ clean-files += x509_rsakey-asn1.c x509_rsakey-asn1.h obj-$(CONFIG_PKCS7_MESSAGE_PARSER) += pkcs7_message.o pkcs7_message-y := \ pkcs7-asn1.o \ - pkcs7_parser.o + pkcs7_parser.o \ + pkcs7_verify.o $(obj)/pkcs7_parser.o: $(obj)/pkcs7-asn1.h $(obj)/pkcs7-asn1.o: $(obj)/pkcs7-asn1.c $(obj)/pkcs7-asn1.h diff --git a/crypto/asymmetric_keys/pkcs7_verify.c b/crypto/asymmetric_keys/pkcs7_verify.c new file mode 100644 index 00000000..0bb408a5 --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_verify.c @@ -0,0 +1,173 @@ +/* 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 +#include +#include +#include +#include +#include +#include "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 crypto_shash *tfm; + struct shash_desc *desc; + size_t digest_size, desc_size; + void *digest; + int ret; + + kenter(",%u,%u", sinfo->index, sinfo->sig.pkey_hash_algo); + + if (sinfo->sig.pkey_hash_algo >= PKEY_HASH__LAST || + !hash_algo_name[sinfo->sig.pkey_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(hash_algo_name[sinfo->sig.pkey_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); + sinfo->sig.digest_size = digest_size = crypto_shash_digestsize(tfm); + + ret = -ENOMEM; + digest = kzalloc(digest_size + desc_size, GFP_KERNEL); + if (!digest) + goto error_no_desc; + + desc = digest + digest_size; + desc->tfm = tfm; + desc->flags = CRYPTO_TFM_REQ_MAY_SLEEP; + + /* Digest the message [RFC2315 9.3] */ + ret = crypto_shash_init(desc); + if (ret < 0) + goto error; + ret = crypto_shash_finup(desc, pkcs7->data, pkcs7->data_len, digest); + if (ret < 0) + goto error; + pr_devel("MsgDigest = [%*ph]\n", 8, 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->msgdigest) { + u8 tag; + + if (sinfo->msgdigest_len != sinfo->sig.digest_size) { + pr_debug("Sig %u: Invalid digest size (%u)\n", + sinfo->index, sinfo->msgdigest_len); + ret = -EBADMSG; + goto error; + } + + if (memcmp(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(digest, 0, sinfo->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, digest); + if (ret < 0) + goto error; + pr_devel("AADigest = [%*ph]\n", 8, digest); + } + + sinfo->sig.digest = digest; + digest = NULL; + +error: + kfree(digest); +error_no_desc: + crypto_free_shash(tfm); + kleave(" = %d", ret); + return ret; +} + +/* +/* + * 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; + + return 0; +} + +/** + * pkcs7_verify - Verify a PKCS#7 message + * @pkcs7: The PKCS#7 message to be verified + */ +int pkcs7_verify(struct pkcs7_message *pkcs7) +{ + struct pkcs7_signed_info *sinfo; + struct x509_certificate *x509; + int ret, n; + + kenter(""); + + for (n = 0, x509 = pkcs7->certs; x509; x509 = x509->next, n++) { + ret = x509_get_sig_params(x509); + if (ret < 0) + return ret; + pr_debug("X.509[%u] %s\n", n, x509->authority); + } + + for (sinfo = pkcs7->signed_infos; sinfo; sinfo = sinfo->next) { + ret = pkcs7_verify_one(pkcs7, sinfo); + if (ret < 0) { + kleave(" = %d", ret); + return ret; + } + } + + kleave(" = 0"); + return 0; +} +EXPORT_SYMBOL_GPL(pkcs7_verify); -- cgit v1.2.3 From 80f78f58443ae7f405906b5c6fec9bdaf5ac9681 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 1 Jul 2014 16:40:20 +0100 Subject: PKCS#7: Find intersection between PKCS#7 message and known, trusted keys Find the intersection between the X.509 certificate chain contained in a PKCS#7 message and a set of keys that we already know and trust. Signed-off-by: David Howells Acked-by: Vivek Goyal Reviewed-by: Kees Cook --- crypto/asymmetric_keys/Makefile | 1 + crypto/asymmetric_keys/pkcs7_trust.c | 219 +++++++++++++++++++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 crypto/asymmetric_keys/pkcs7_trust.c (limited to 'crypto/asymmetric_keys/Makefile') diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index b6b39e7b..d63cb432 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -33,6 +33,7 @@ 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 diff --git a/crypto/asymmetric_keys/pkcs7_trust.c b/crypto/asymmetric_keys/pkcs7_trust.c new file mode 100644 index 00000000..b6b04513 --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_trust.c @@ -0,0 +1,219 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include "public_key.h" +#include "pkcs7_parser.h" + +/* + * Request an asymmetric key. + */ +static struct key *pkcs7_request_asymmetric_key( + struct key *keyring, + const char *signer, size_t signer_len, + const char *authority, size_t auth_len) +{ + key_ref_t key; + char *id; + + kenter(",%zu,,%zu", signer_len, auth_len); + + /* Construct an identifier. */ + id = kmalloc(signer_len + 2 + auth_len + 1, GFP_KERNEL); + if (!id) + return ERR_PTR(-ENOMEM); + + memcpy(id, signer, signer_len); + id[signer_len + 0] = ':'; + id[signer_len + 1] = ' '; + memcpy(id + signer_len + 2, authority, auth_len); + id[signer_len + 2 + auth_len] = 0; + + pr_debug("Look up: \"%s\"\n", id); + + key = keyring_search(make_key_ref(keyring, 1), + &key_type_asymmetric, id); + if (IS_ERR(key)) + pr_debug("Request for module key '%s' err %ld\n", + id, PTR_ERR(key)); + kfree(id); + + if (IS_ERR(key)) { + switch (PTR_ERR(key)) { + /* Hide some search errors */ + case -EACCES: + case -ENOTDIR: + case -EAGAIN: + return ERR_PTR(-ENOKEY); + default: + return ERR_CAST(key); + } + } + + pr_devel("<==%s() = 0 [%x]\n", __func__, key_serial(key_ref_to_ptr(key))); + return key_ref_to_ptr(key); +} + +/** + * Check the trust on one PKCS#7 SignedInfo block. + */ +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; + bool trusted; + int ret; + + kenter(",%u,", sinfo->index); + + for (x509 = sinfo->signer; x509; x509 = x509->signer) { + if (x509->seen) { + if (x509->verified) { + trusted = x509->trusted; + goto verified; + } + kleave(" = -ENOKEY [cached]"); + return -ENOKEY; + } + x509->seen = true; + + /* Look to see if this certificate is present in the trusted + * keys. + */ + key = pkcs7_request_asymmetric_key( + trust_keyring, + x509->subject, strlen(x509->subject), + x509->fingerprint, strlen(x509->fingerprint)); + 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. + */ + 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->next == 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->issuer || !last->authority) { + kleave(" = -ENOKEY [no backref]"); + return -ENOKEY; + } + + key = pkcs7_request_asymmetric_key( + trust_keyring, + last->issuer, strlen(last->issuer), + last->authority, strlen(last->authority)); + if (IS_ERR(key)) + return PTR_ERR(key) == -ENOMEM ? -ENOMEM : -ENOKEY; + x509 = last; + +matched: + ret = verify_signature(key, sig); + trusted = test_bit(KEY_FLAG_TRUSTED, &key->flags); + key_put(key); + if (ret < 0) { + if (ret == -ENOMEM) + return ret; + kleave(" = -EKEYREJECTED [verify %d]", ret); + return -EKEYREJECTED; + } + +verified: + x509->verified = true; + for (p = sinfo->signer; p != x509; p = p->signer) { + p->verified = true; + p->trusted = trusted; + } + sinfo->trusted = trusted; + 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 + * @_trusted: Set to true if trustworth, false otherwise + * + * 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, + bool *_trusted) +{ + struct pkcs7_signed_info *sinfo; + struct x509_certificate *p; + int cached_ret = 0, 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); + if (ret < 0) { + if (ret == -ENOPKG) { + cached_ret = -ENOPKG; + } else if (ret == -ENOKEY) { + if (cached_ret == 0) + cached_ret = -ENOKEY; + } else { + return ret; + } + } + *_trusted |= sinfo->trusted; + } + + return cached_ret; +} +EXPORT_SYMBOL_GPL(pkcs7_validate_trust); -- cgit v1.2.3 From c6fef8f06aa1dc39d370a0ce1d6e856e2349acb2 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 1 Jul 2014 19:06:18 +0100 Subject: PKCS#7: Provide a key type for testing PKCS#7 Provide a key type for testing the PKCS#7 parser. It is given a non-detached PKCS#7 message as payload: keyctl padd pkcs7_test a @s out stuff.txt: echo "The quick red fox jumped over the lazy brown dog" >stuff.txt certs: key1.x509 key2.x509 key3.x509 key4.x509 cat key{1,3}.x509 >$@ ############################################################################### # # Generate a signed key # # openssl x509 -text -inform PEM -noout -in key2.x509 # ############################################################################### key2.x509: key2.x509_unsigned key1.priv key1.x509 openssl x509 \ -req -in key2.x509_unsigned \ -out key2.x509 \ -extfile key2.genkey -extensions myexts \ -CA key1.x509 \ -CAkey key1.priv \ -CAcreateserial key2.priv key2.x509_unsigned: key2.genkey openssl req -new -nodes -utf8 -sha1 -days 36500 \ -batch -outform PEM \ -config key2.genkey \ -keyout key2.priv \ -out key2.x509_unsigned key2.genkey: @echo Generating X.509 key generation config @echo >$@ "[ req ]" @echo >>$@ "default_bits = 4096" @echo >>$@ "distinguished_name = req_distinguished_name" @echo >>$@ "prompt = no" @echo >>$@ "string_mask = utf8only" @echo >>$@ "x509_extensions = myexts" @echo >>$@ @echo >>$@ "[ req_distinguished_name ]" @echo >>$@ "O = Magrathea" @echo >>$@ "CN = PKCS7 key 2" @echo >>$@ "emailAddress = slartibartfast@magrathea.h2g2" @echo >>$@ @echo >>$@ "[ myexts ]" @echo >>$@ "basicConstraints=critical,CA:FALSE" @echo >>$@ "keyUsage=digitalSignature" @echo >>$@ "subjectKeyIdentifier=hash" @echo >>$@ "authorityKeyIdentifier=keyid" ############################################################################### # # Generate a couple of signing keys # # openssl x509 -text -inform PEM -noout -in key1.x509 # ############################################################################### key1.x509: key1.x509_unsigned key4.priv key4.x509 openssl x509 \ -req -in key1.x509_unsigned \ -out key1.x509 \ -extfile key1.genkey -extensions myexts \ -CA key4.x509 \ -CAkey key4.priv \ -CAcreateserial key1.priv key1.x509_unsigned: key1.genkey openssl req -new -nodes -utf8 -sha1 -days 36500 \ -batch -outform PEM \ -config key1.genkey \ -keyout key1.priv \ -out key1.x509_unsigned key1.genkey: @echo Generating X.509 key generation config @echo >$@ "[ req ]" @echo >>$@ "default_bits = 4096" @echo >>$@ "distinguished_name = req_distinguished_name" @echo >>$@ "prompt = no" @echo >>$@ "string_mask = utf8only" @echo >>$@ "x509_extensions = myexts" @echo >>$@ @echo >>$@ "[ req_distinguished_name ]" @echo >>$@ "O = Magrathea" @echo >>$@ "CN = PKCS7 key 1" @echo >>$@ "emailAddress = slartibartfast@magrathea.h2g2" @echo >>$@ @echo >>$@ "[ myexts ]" @echo >>$@ "basicConstraints=critical,CA:TRUE" @echo >>$@ "keyUsage=digitalSignature,keyCertSign" @echo >>$@ "subjectKeyIdentifier=hash" @echo >>$@ "authorityKeyIdentifier=keyid" ############################################################################### # # Generate a signed key # # openssl x509 -text -inform PEM -noout -in key4.x509 # ############################################################################### key4.x509: key4.x509_unsigned key3.priv key3.x509 openssl x509 \ -req -in key4.x509_unsigned \ -out key4.x509 \ -extfile key4.genkey -extensions myexts \ -CA key3.x509 \ -CAkey key3.priv \ -CAcreateserial key4.priv key4.x509_unsigned: key4.genkey openssl req -new -nodes -utf8 -sha1 -days 36500 \ -batch -outform PEM \ -config key4.genkey \ -keyout key4.priv \ -out key4.x509_unsigned key4.genkey: @echo Generating X.509 key generation config @echo >$@ "[ req ]" @echo >>$@ "default_bits = 4096" @echo >>$@ "distinguished_name = req_distinguished_name" @echo >>$@ "prompt = no" @echo >>$@ "string_mask = utf8only" @echo >>$@ "x509_extensions = myexts" @echo >>$@ @echo >>$@ "[ req_distinguished_name ]" @echo >>$@ "O = Magrathea" @echo >>$@ "CN = PKCS7 key 4" @echo >>$@ "emailAddress = slartibartfast@magrathea.h2g2" @echo >>$@ @echo >>$@ "[ myexts ]" @echo >>$@ "basicConstraints=critical,CA:TRUE" @echo >>$@ "keyUsage=digitalSignature,keyCertSign" @echo >>$@ "subjectKeyIdentifier=hash" @echo >>$@ "authorityKeyIdentifier=keyid" ############################################################################### # # Generate a couple of signing keys # # openssl x509 -text -inform PEM -noout -in key3.x509 # ############################################################################### key3.priv key3.x509: key3.genkey openssl req -new -nodes -utf8 -sha1 -days 36500 \ -batch -x509 -outform PEM \ -config key3.genkey \ -keyout key3.priv \ -out key3.x509 key3.genkey: @echo Generating X.509 key generation config @echo >$@ "[ req ]" @echo >>$@ "default_bits = 4096" @echo >>$@ "distinguished_name = req_distinguished_name" @echo >>$@ "prompt = no" @echo >>$@ "string_mask = utf8only" @echo >>$@ "x509_extensions = myexts" @echo >>$@ @echo >>$@ "[ req_distinguished_name ]" @echo >>$@ "O = Magrathea" @echo >>$@ "CN = PKCS7 key 3" @echo >>$@ "emailAddress = slartibartfast@magrathea.h2g2" @echo >>$@ @echo >>$@ "[ myexts ]" @echo >>$@ "basicConstraints=critical,CA:TRUE" @echo >>$@ "keyUsage=digitalSignature,keyCertSign" @echo >>$@ "subjectKeyIdentifier=hash" @echo >>$@ "authorityKeyIdentifier=keyid" clean: $(RM) *~ $(RM) key1.* key2.* key3.* key4.* stuff.* out certs Signed-off-by: David Howells --- crypto/asymmetric_keys/Kconfig | 13 +++++ crypto/asymmetric_keys/Makefile | 7 +++ crypto/asymmetric_keys/pkcs7_key_type.c | 97 +++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 crypto/asymmetric_keys/pkcs7_key_type.c (limited to 'crypto/asymmetric_keys/Makefile') diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig index a7cec9dd..b6df198d 100644 --- a/crypto/asymmetric_keys/Kconfig +++ b/crypto/asymmetric_keys/Kconfig @@ -46,4 +46,17 @@ config PKCS7_MESSAGE_PARSER 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 PKCS7_MESSAGE_PARSER + select SYSTEM_TRUSTED_KEYRING + 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. + endif # ASYMMETRIC_KEY_TYPE diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index d63cb432..92d0e9af 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -40,3 +40,10 @@ $(obj)/pkcs7_parser.o: $(obj)/pkcs7-asn1.h $(obj)/pkcs7-asn1.o: $(obj)/pkcs7-asn1.c $(obj)/pkcs7-asn1.h clean-files += pkcs7-asn1.c 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 diff --git a/crypto/asymmetric_keys/pkcs7_key_type.c b/crypto/asymmetric_keys/pkcs7_key_type.c new file mode 100644 index 00000000..b1797d25 --- /dev/null +++ b/crypto/asymmetric_keys/pkcs7_key_type.c @@ -0,0 +1,97 @@ +/* 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 +#include +#include +#include +#include +#include "pkcs7_parser.h" + +/* + * Instantiate a PKCS#7 wrapped and validated key. + */ +int pkcs7_instantiate(struct key *key, struct key_preparsed_payload *prep) +{ + struct pkcs7_message *pkcs7; + const void *data, *saved_prep_data; + size_t datalen, saved_prep_datalen; + bool trusted; + int ret; + + kenter(""); + + saved_prep_data = prep->data; + saved_prep_datalen = prep->datalen; + pkcs7 = pkcs7_parse_message(saved_prep_data, saved_prep_datalen); + if (IS_ERR(pkcs7)) { + ret = PTR_ERR(pkcs7); + goto error; + } + + ret = pkcs7_verify(pkcs7); + if (ret < 0) + goto error_free; + + ret = pkcs7_validate_trust(pkcs7, system_trusted_keyring, &trusted); + if (ret < 0) + goto error_free; + if (!trusted) + pr_warn("PKCS#7 message doesn't chain back to a trusted key\n"); + + ret = pkcs7_get_content_data(pkcs7, &data, &datalen, false); + if (ret < 0) + goto error_free; + + prep->data = data; + prep->datalen = datalen; + ret = user_instantiate(key, prep); + prep->data = saved_prep_data; + prep->datalen = saved_prep_datalen; + +error_free: + pkcs7_free_message(pkcs7); +error: + kleave(" = %d", ret); + return ret; +} + +/* + * user defined keys take an arbitrary string as the description and an + * arbitrary blob of data as the payload + */ +struct key_type key_type_pkcs7 = { + .name = "pkcs7_test", + .def_lookup_type = KEYRING_SEARCH_LOOKUP_DIRECT, + .instantiate = pkcs7_instantiate, + .match = user_match, + .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); -- cgit v1.2.3 From 0cf8e6a6bfa2cedf073fc13aeeb00e8b79d319e2 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 1 Jul 2014 16:02:51 +0100 Subject: pefile: Parse a PE binary to find a key and a signature contained therein Parse a PE binary to find a key and a signature contained therein. Later patches will check the signature and add the key if the signature checks out. Signed-off-by: David Howells Acked-by: Vivek Goyal Reviewed-by: Kees Cook --- crypto/asymmetric_keys/Kconfig | 9 ++ crypto/asymmetric_keys/Makefile | 8 ++ crypto/asymmetric_keys/verify_pefile.c | 163 +++++++++++++++++++++++++++++++++ crypto/asymmetric_keys/verify_pefile.h | 37 ++++++++ 4 files changed, 217 insertions(+) create mode 100644 crypto/asymmetric_keys/verify_pefile.c create mode 100644 crypto/asymmetric_keys/verify_pefile.h (limited to 'crypto/asymmetric_keys/Makefile') diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig index 14cac286..ca41be56 100644 --- a/crypto/asymmetric_keys/Kconfig +++ b/crypto/asymmetric_keys/Kconfig @@ -59,4 +59,13 @@ config PKCS7_TEST_KEY 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 + 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 index 92d0e9af..3e4de529 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -47,3 +47,11 @@ clean-files += pkcs7-asn1.c pkcs7-asn1.h 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 diff --git a/crypto/asymmetric_keys/verify_pefile.c b/crypto/asymmetric_keys/verify_pefile.c new file mode 100644 index 00000000..aec7c509 --- /dev/null +++ b/crypto/asymmetric_keys/verify_pefile.c @@ -0,0 +1,163 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#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; +} + +/** + * 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_keyring: Signing certificates to use as starting points + * @_trusted: Set to true if trustworth, false otherwise + * + * 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_keyring, bool *_trusted) +{ + struct pefile_context ctx; + int ret; + + kenter(""); + + memset(&ctx, 0, sizeof(ctx)); + ret = pefile_parse_binary(pebuf, pelen, &ctx); + if (ret < 0) + return ret; + + return -ENOANO; // Not yet complete +} diff --git a/crypto/asymmetric_keys/verify_pefile.h b/crypto/asymmetric_keys/verify_pefile.h new file mode 100644 index 00000000..e165d234 --- /dev/null +++ b/crypto/asymmetric_keys/verify_pefile.h @@ -0,0 +1,37 @@ +/* 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 +#include +#include + +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; + struct pkcs7_message *pkcs7; + + /* PKCS#7 MS Individual Code Signing content */ + const void *digest; /* Digest */ + unsigned digest_len; /* Digest length */ + enum hash_algo 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__) -- cgit v1.2.3 From d93f05b21d04eab7ed1a289c7731163641be8dc6 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 1 Jul 2014 16:02:52 +0100 Subject: pefile: Parse the "Microsoft individual code signing" data blob The PKCS#7 certificate should contain a "Microsoft individual code signing" data blob as its signed content. This blob contains a digest of the signed content of the PE binary and the OID of the digest algorithm used (typically SHA256). Signed-off-by: David Howells Acked-by: Vivek Goyal Reviewed-by: Kees Cook --- crypto/asymmetric_keys/Makefile | 9 ++- crypto/asymmetric_keys/mscode.asn1 | 28 ++++++++ crypto/asymmetric_keys/mscode_parser.c | 120 +++++++++++++++++++++++++++++++++ crypto/asymmetric_keys/verify_pefile.c | 7 ++ crypto/asymmetric_keys/verify_pefile.h | 5 ++ 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 crypto/asymmetric_keys/mscode.asn1 create mode 100644 crypto/asymmetric_keys/mscode_parser.c (limited to 'crypto/asymmetric_keys/Makefile') diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile index 3e4de529..e47fcd9a 100644 --- a/crypto/asymmetric_keys/Makefile +++ b/crypto/asymmetric_keys/Makefile @@ -54,4 +54,11 @@ pkcs7_test_key-y := \ obj-$(CONFIG_SIGNED_PE_FILE_VERIFICATION) += verify_signed_pefile.o verify_signed_pefile-y := \ - verify_pefile.o + 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 + +clean-files += mscode-asn1.c mscode-asn1.h diff --git a/crypto/asymmetric_keys/mscode.asn1 b/crypto/asymmetric_keys/mscode.asn1 new file mode 100644 index 00000000..6d09ba48 --- /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 00000000..09336c32 --- /dev/null +++ b/crypto/asymmetric_keys/mscode_parser.c @@ -0,0 +1,120 @@ +/* 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 +#include +#include +#include +#include +#include "verify_pefile.h" +#include "mscode-asn1.h" + +/* + * Parse a Microsoft Individual Code Signing blob + */ +int mscode_parse(struct pefile_context *ctx) +{ + const void *content_data; + size_t data_len; + int ret; + + ret = pkcs7_get_content_data(ctx->pkcs7, &content_data, &data_len, 1); + + if (ret) { + pr_debug("PKCS#7 message does not contain data\n"); + return ret; + } + + 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; + } + + if (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 = HASH_ALGO_MD4; + break; + case OID_md5: + ctx->digest_algo = HASH_ALGO_MD5; + break; + case OID_sha1: + ctx->digest_algo = HASH_ALGO_SHA1; + break; + case OID_sha256: + ctx->digest_algo = HASH_ALGO_SHA256; + 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 = value; + ctx->digest_len = vlen; + return 0; +} diff --git a/crypto/asymmetric_keys/verify_pefile.c b/crypto/asymmetric_keys/verify_pefile.c index 13f3b44b..b975918e 100644 --- a/crypto/asymmetric_keys/verify_pefile.c +++ b/crypto/asymmetric_keys/verify_pefile.c @@ -245,6 +245,13 @@ int verify_pefile_signature(const void *pebuf, unsigned pelen, goto error; } + ret = mscode_parse(&ctx); + if (ret < 0) + goto error; + + pr_debug("Digest: %u [%*ph]\n", + ctx.digest_len, ctx.digest_len, ctx.digest); + ret = -ENOANO; // Not yet complete error: diff --git a/crypto/asymmetric_keys/verify_pefile.h b/crypto/asymmetric_keys/verify_pefile.h index e165d234..55d5f7eb 100644 --- a/crypto/asymmetric_keys/verify_pefile.h +++ b/crypto/asymmetric_keys/verify_pefile.h @@ -35,3 +35,8 @@ struct pefile_context { 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(struct pefile_context *ctx); -- cgit v1.2.3