Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ca72c96 authored by David Howells's avatar David Howells
Browse files

Merge tag 'keys-pkcs7-20140708' into keys-next



Here's a set of changes that implement a PKCS#7 message parser in the kernel.

The PKCS#7 message parsing will then be used to limit kexec to authenticated
kernels only if so configured.

The changes provide the following facilities:

 (1) Parse an ASN.1 PKCS#7 message and pick out useful bits such as the data
     content and the X.509 certificates used to sign it and all the data
     signatures.

 (2) Verify all the data signatures against the set of X.509 certificates
     available in the message.

 (3) Follow the certificate chains and verify that:

     (a) for every self-signed X.509 certificate, check that it validly signed
     	 itself, and:

     (b) for every non-self-signed certificate, if we have a 'parent'
     	 certificate, the former is validly signed by the latter.

 (4) Look for intersections between the certificate chains and the trusted
     keyring, if any intersections are found, verify that the trusted
     certificates signed the intersection point in the chain.

 (5) For testing purposes, a key type can be made available that will take a
     PKCS#7 message, check that the message is trustworthy, and if so, add its
     data content into the key.

Note that (5) has to be altered to take account of the preparsing patches
already committed to this branch.

Signed-off-by: default avatarDavid Howells <dhowells@redhat.com>
parents a19e3c22 22d01afb
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -37,4 +37,26 @@ config X509_CERTIFICATE_PARSER
	  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 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 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
+22 −0
Original line number Diff line number Diff line
@@ -25,3 +25,25 @@ $(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 \
	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

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
+127 −0
Original line number Diff line number Diff line
PKCS7ContentInfo ::= SEQUENCE {
	contentType	ContentType,
	content		[0] EXPLICIT SignedData OPTIONAL
}

ContentType ::= OBJECT IDENTIFIER ({ pkcs7_note_OID })

SignedData ::= SEQUENCE {
	version			INTEGER,
	digestAlgorithms	DigestAlgorithmIdentifiers,
	contentInfo		ContentInfo,
	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,
	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,
	issuerAndSerialNumber	IssuerAndSerialNumber,
	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 })

IssuerAndSerialNumber ::= SEQUENCE {
	issuer			Name ({ pkcs7_sig_note_issuer }),
	serialNumber		CertificateSerialNumber ({ pkcs7_sig_note_serial })
}

CertificateSerialNumber ::= INTEGER

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 ({ pkcs7_note_OID }),
	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
}
+99 −0
Original line number Diff line number Diff line
/* 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/key-type.h>
#include <crypto/pkcs7.h>
#include <keys/user-type.h>
#include <keys/system_keyring.h>
#include "pkcs7_parser.h"

/*
 * Preparse a PKCS#7 wrapped and validated data blob.
 */
static int pkcs7_preparse(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_preparse(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,
	.preparse		= pkcs7_preparse,
	.free_preparse		= user_free_preparse,
	.instantiate		= generic_key_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);
+396 −0
Original line number Diff line number Diff line
/* 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/export.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/oid_registry.h>
#include "public_key.h"
#include "pkcs7_parser.h"
#include "pkcs7-asn1.h"

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

/**
 * 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;
			mpi_free(sinfo->sig.mpi[0]);
			kfree(sinfo->sig.digest);
			kfree(sinfo);
		}
		kfree(pkcs7);
	}
}
EXPORT_SYMBOL_GPL(pkcs7_free_message);

/**
 * 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;
	long ret;

	ret = -ENOMEM;
	msg = kzalloc(sizeof(struct pkcs7_message), GFP_KERNEL);
	if (!msg)
		goto error_no_sig;
	ctx = kzalloc(sizeof(struct pkcs7_parse_context), GFP_KERNEL);
	if (!ctx)
		goto error_no_ctx;
	ctx->sinfo = kzalloc(sizeof(struct pkcs7_signed_info), GFP_KERNEL);
	if (!ctx->sinfo)
		goto error_no_sinfo;

	ctx->msg = msg;
	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)
		goto error_decode;

	while (ctx->certs) {
		struct x509_certificate *cert = ctx->certs;
		ctx->certs = cert->next;
		x509_free_certificate(cert);
	}
	mpi_free(ctx->sinfo->sig.mpi[0]);
	kfree(ctx->sinfo->sig.digest);
	kfree(ctx->sinfo);
	kfree(ctx);
	return msg;

error_decode:
	mpi_free(ctx->sinfo->sig.mpi[0]);
	kfree(ctx->sinfo->sig.digest);
	kfree(ctx->sinfo);
error_no_sinfo:
	kfree(ctx);
error_no_ctx:
	pkcs7_free_message(msg);
error_no_sig:
	return ERR_PTR(ret);
}
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
 * @want_wrapper: True if the ASN.1 object header should be included in the data
 *
 * Get access to the data content of the PKCS#7 message, including, optionally,
 * the header of the ASN.1 object that contains it.  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,
			   bool want_wrapper)
{
	size_t wrapper;

	if (!pkcs7->data)
		return -ENODATA;

	wrapper = want_wrapper ? pkcs7->data_hdrlen : 0;
	*_data = pkcs7->data - wrapper;
	*_data_len = pkcs7->data_len + wrapper;
	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.pkey_hash_algo = HASH_ALGO_MD4;
		break;
	case OID_md5:
		ctx->sinfo->sig.pkey_hash_algo = HASH_ALGO_MD5;
		break;
	case OID_sha1:
		ctx->sinfo->sig.pkey_hash_algo = HASH_ALGO_SHA1;
		break;
	case OID_sha256:
		ctx->sinfo->sig.pkey_hash_algo = HASH_ALGO_SHA256;
		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 = PKEY_ALGO_RSA;
		break;
	default:
		printk("Unsupported pkey algo: %u\n", ctx->last_oid);
		return -ENOPKG;
	}
	return 0;
}

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

	pr_debug("Got cert for %s\n", x509->subject);
	pr_debug("- fingerprint %s\n", x509->fingerprint);

	x509->index = ++ctx->x509_index;
	*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;
}

/*
 * 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;
	ctx->msg->data_type = ctx->last_oid;
	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;

	pr_devel("AuthAttr: %02x %zu [%*ph]\n", tag, vlen, (unsigned)vlen, value);

	switch (ctx->last_oid) {
	case OID_messageDigest:
		if (tag != ASN1_OTS)
			return -EBADMSG;
		ctx->sinfo->msgdigest = value;
		ctx->sinfo->msgdigest_len = vlen;
		return 0;
	default:
		return 0;
	}
}

/*
 * Note the set of auth attributes for digestion purposes [RFC2315 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;

	/* We need to switch the 'CONT 0' to a 'SET OF' when we digest */
	ctx->sinfo->authattrs = value - (hdrlen - 1);
	ctx->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->sinfo->raw_serial = value;
	ctx->sinfo->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->sinfo->raw_issuer = value;
	ctx->sinfo->raw_issuer_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;
	MPI mpi;

	BUG_ON(ctx->sinfo->sig.pkey_algo != PKEY_ALGO_RSA);

	mpi = mpi_read_raw_data(value, vlen);
	if (!mpi)
		return -ENOMEM;

	ctx->sinfo->sig.mpi[0] = mpi;
	ctx->sinfo->sig.nr_mpi = 1;
	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;

	ctx->sinfo->index = ++ctx->sinfo_index;
	*ctx->ppsinfo = ctx->sinfo;
	ctx->ppsinfo = &ctx->sinfo->next;
	ctx->sinfo = kzalloc(sizeof(struct pkcs7_signed_info), GFP_KERNEL);
	if (!ctx->sinfo)
		return -ENOMEM;
	return 0;
}
Loading