LKML Archive on lore.kernel.org
help / color / mirror / Atom feed
From: David Howells <dhowells@redhat.com>
To: torvalds@osdl.org, akpm@osdl.org, herbert.xu@redhat.com
Cc: linux-kernel@vger.kernel.org, davej@redhat.com,
	arjan@infradead.org, linux-crypto@vger.kernel.org,
	dhowells@redhat.com
Subject: [PATCH 5/6] MODSIGN: Module signature checker and key manager
Date: Wed, 14 Feb 2007 19:10:04 +0000	[thread overview]
Message-ID: <20070214191004.6438.19957.stgit@warthog.cambridge.redhat.com> (raw)
In-Reply-To: <20070214190938.6438.15091.stgit@warthog.cambridge.redhat.com>

Add a facility to retain public keys and to verify signatures made with those
public keys, given a signature and crypto_hash of the data that was signed.

Signed-Off-By: David Howells <dhowells@redhat.com>
---

 crypto/Kconfig                     |   13 +
 crypto/Makefile                    |    1 
 crypto/signature/Makefile          |   10 +
 crypto/signature/dsa.c             |   96 ++++++
 crypto/signature/key.h             |    7 
 crypto/signature/ksign-keyring.c   |  116 +++++++
 crypto/signature/ksign-parse.c     |  603 ++++++++++++++++++++++++++++++++++++
 crypto/signature/ksign-publickey.c |   18 +
 crypto/signature/ksign.c           |  180 +++++++++++
 crypto/signature/local.h           |  160 ++++++++++
 include/linux/crypto/ksign.h       |   22 +
 11 files changed, 1226 insertions(+), 0 deletions(-)

diff --git a/crypto/Kconfig b/crypto/Kconfig
index aab5b8f..e764509 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -453,6 +453,19 @@ config CRYPTO_MPILIB
 	help
 	  Multiprecision maths library from GnuPG
 
+config CRYPTO_SIGNATURE
+	bool "In-kernel signature checker (EXPERIMENTAL)"
+	depends on CRYPTO
+	help
+	  Signature checker (used for module sig checking).
+
+config CRYPTO_SIGNATURE_DSA
+	bool "Handle DSA signatures (EXPERIMENTAL)"
+	depends on CRYPTO_SIGNATURE
+	select CRYPTO_MPILIB
+	help
+	  DSA Signature checker.
+
 source "drivers/crypto/Kconfig"
 
 endif	# if CRYPTO
diff --git a/crypto/Makefile b/crypto/Makefile
index 49fc857..fe33414 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -50,3 +50,4 @@ obj-$(CONFIG_CRYPTO_CRC32C) += crc32c.o
 obj-$(CONFIG_CRYPTO_TEST) += tcrypt.o
 
 obj-$(CONFIG_CRYPTO_MPILIB) += mpi/
+obj-$(CONFIG_CRYPTO_SIGNATURE) += signature/
diff --git a/crypto/signature/Makefile b/crypto/signature/Makefile
new file mode 100644
index 0000000..4d1042e
--- /dev/null
+++ b/crypto/signature/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the signature checker
+#
+
+obj-y := \
+	ksign.o \
+	ksign-parse.o \
+	ksign-keyring.o \
+	ksign-publickey.o \
+	dsa.o
diff --git a/crypto/signature/dsa.c b/crypto/signature/dsa.c
new file mode 100644
index 0000000..469539c
--- /dev/null
+++ b/crypto/signature/dsa.c
@@ -0,0 +1,96 @@
+/* dsa.c  -  DSA signature algorithm
+ *	Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/crypto/mpi.h>
+#include <asm/errno.h>
+#include "local.h"
+
+/*
+ * perform DSA algorithm signature verification
+ */
+int DSA_verify(const MPI datahash, const MPI sig[], const MPI pkey[])
+{
+	MPI p, q, g, y, r, s;
+	MPI w = NULL, u1 = NULL, u2 = NULL, v = NULL;
+	MPI base[3];
+	MPI exp[3];
+	int rc;
+
+	if (!datahash ||
+	    !sig[0] || !sig[1] ||
+	    !pkey[0] || !pkey[1] || !pkey[2] || !pkey[3])
+		return -EINVAL;
+
+	p = pkey[0];	/* prime */
+	q = pkey[1];	/* group order */
+	g = pkey[2];	/* group generator */
+	y = pkey[3];	/* g^x mod p */
+	r = sig[0];
+	s = sig[1];
+
+	if (!(mpi_cmp_ui(r, 0) > 0 && mpi_cmp(r, q) < 0)) {
+		printk("DSA_verify assertion failed [0 < r < q]\n");
+		return -EKEYREJECTED;
+	}
+
+	if (!(mpi_cmp_ui(s, 0) > 0 && mpi_cmp(s, q) < 0)) {
+		printk("DSA_verify assertion failed [0 < s < q]\n");
+		return -EKEYREJECTED;
+	}
+
+	rc = -ENOMEM;
+	w  = mpi_alloc(mpi_get_nlimbs(q)); if (!w ) goto cleanup;
+	u1 = mpi_alloc(mpi_get_nlimbs(q)); if (!u1) goto cleanup;
+	u2 = mpi_alloc(mpi_get_nlimbs(q)); if (!u2) goto cleanup;
+	v  = mpi_alloc(mpi_get_nlimbs(p)); if (!v ) goto cleanup;
+
+	/* w = s^(-1) mod q */
+	if (mpi_invm(w, s, q) < 0)
+		goto cleanup;
+
+	/* u1 = (datahash * w) mod q */
+	if (mpi_mulm(u1, datahash, w, q) < 0)
+		goto cleanup;
+
+	/* u2 = r * w mod q  */
+	if (mpi_mulm(u2, r, w, q) < 0)
+		goto cleanup;
+
+	/* v =  g^u1 * y^u2 mod p mod q */
+	base[0] = g;	exp[0] = u1;
+	base[1] = y;	exp[1] = u2;
+	base[2] = NULL;	exp[2] = NULL;
+
+	if (mpi_mulpowm(v, base, exp, p) < 0)
+		goto cleanup;
+
+	if (mpi_fdiv_r(v, v, q) < 0)
+		goto cleanup;
+
+	rc = (mpi_cmp(v, r) == 0) ? 0 : -EKEYREJECTED;
+
+cleanup:
+	mpi_free(w);
+	mpi_free(u1);
+	mpi_free(u2);
+	mpi_free(v);
+	return rc;
+}
diff --git a/crypto/signature/key.h b/crypto/signature/key.h
new file mode 100644
index 0000000..7297968
--- /dev/null
+++ b/crypto/signature/key.h
@@ -0,0 +1,7 @@
+const int ksign_def_public_key_size = 0;
+/* automatically generated by bin2hex */
+static unsigned char ksign_def_public_key[] __initdata =
+{
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
diff --git a/crypto/signature/ksign-keyring.c b/crypto/signature/ksign-keyring.c
new file mode 100644
index 0000000..a839261
--- /dev/null
+++ b/crypto/signature/ksign-keyring.c
@@ -0,0 +1,116 @@
+/* ksign-keyring.c: public key cache
+ *
+ * Copyright (C) 2001 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ *
+ * This file is derived from part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <linux/rwsem.h>
+#include "local.h"
+
+static LIST_HEAD(keyring);
+static DECLARE_RWSEM(keyring_sem);
+
+/*
+ * handle a public key element parsed from the keyring blob
+ */
+static int add_keyblock_key(struct ksign_public_key *pk, void *data)
+{
+	printk("- Added public key %X%X\n", pk->keyid[0], pk->keyid[1]);
+
+	if (pk->expiredate && pk->expiredate < xtime.tv_sec)
+		printk("  - public key has expired\n");
+
+	if (pk->timestamp > xtime.tv_sec )
+		printk("  - key was been created %lu seconds in future\n",
+		       pk->timestamp - xtime.tv_sec);
+
+	atomic_inc(&pk->count);
+
+	down_write(&keyring_sem);
+	list_add_tail(&pk->link, &keyring);
+	up_write(&keyring_sem);
+
+	return 0;
+}
+
+/*
+ * handle a user ID element parsed from the keyring blob
+ */
+static int add_keyblock_uid(struct ksign_user_id *uid, void *data)
+{
+	printk("- User ID: %s\n", uid->name);
+	return 1;
+}
+
+/*
+ * add the keys from a ASN.1 encoded blob into the keyring
+ */
+int ksign_load_keyring_from_buffer(const void *buffer, size_t size)
+{
+    printk("Loading keyring\n");
+
+    return ksign_parse_packets((const uint8_t *) buffer,
+			       size,
+			       NULL,
+			       add_keyblock_key,
+			       add_keyblock_uid,
+			       NULL);
+}
+
+/*
+ * find a public key by ID
+ */
+struct ksign_public_key *ksign_get_public_key(const uint32_t *keyid)
+{
+	struct ksign_public_key *pk;
+
+	down_read(&keyring_sem);
+
+	list_for_each_entry(pk, &keyring, link) {
+		if (memcmp(pk->keyid, keyid, sizeof(pk->keyid)) == 0) {
+			atomic_inc(&pk->count);
+			goto found;
+		}
+	}
+
+	pk = NULL;
+
+found:
+	up_read(&keyring_sem);
+	return pk;
+}
+
+/*
+ * clear the public-key keyring
+ */
+void ksign_clear_keyring(void)
+{
+	struct ksign_public_key *pk;
+
+	down_write(&keyring_sem);
+
+	while (!list_empty(&keyring)) {
+		pk = list_entry(keyring.next, struct ksign_public_key, link);
+		list_del(&pk->link);
+
+		ksign_put_public_key(pk);
+	}
+
+	up_write(&keyring_sem);
+}
diff --git a/crypto/signature/ksign-parse.c b/crypto/signature/ksign-parse.c
new file mode 100644
index 0000000..96e2ff5
--- /dev/null
+++ b/crypto/signature/ksign-parse.c
@@ -0,0 +1,603 @@
+/* parse packet data
+ * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <asm/errno.h>
+#include "local.h"
+
+static inline uint32_t buffer_to_u32(const uint8_t *buffer)
+{
+	uint32_t a;
+	a =  *buffer << 24;
+	a |= buffer[1] << 16;
+	a |= buffer[2] << 8;
+	a |= buffer[3];
+	return a;
+}
+
+static inline uint16_t read_16(const uint8_t **datap)
+{
+	uint16_t a;
+	a = *(*datap)++ << 8;
+	a |= *(*datap)++;
+	return a;
+}
+
+static inline uint32_t read_32(const uint8_t **datap)
+{
+	uint32_t a;
+	a =  *(*datap)++ << 24;
+	a |= *(*datap)++ << 16;
+	a |= *(*datap)++ << 8;
+	a |= *(*datap)++;
+	return a;
+}
+
+void ksign_free_signature(struct ksign_signature *sig)
+{
+	int i;
+
+	if (sig) {
+		for (i = 0; i < DSA_NSIG; i++)
+			mpi_free(sig->data[i]);
+		kfree(sig->hashed_data);
+		kfree(sig->unhashed_data);
+		kfree(sig);
+	}
+}
+
+void ksign_free_public_key(struct ksign_public_key *pk)
+{
+	int i;
+
+	if (pk) {
+		for (i = 0; i < DSA_NPKEY; i++)
+			mpi_free(pk->pkey[i]);
+		kfree(pk);
+	}
+}
+
+void ksign_free_user_id(struct ksign_user_id *uid)
+{
+	kfree(uid);
+}
+
+/*
+ *
+ */
+static void ksign_calc_pk_keyid(struct hash_desc *sha1,
+				struct ksign_public_key *pk)
+{
+	unsigned n;
+	unsigned nb[DSA_NPKEY];
+	unsigned nn[DSA_NPKEY];
+	uint8_t *pp[DSA_NPKEY];
+	uint32_t a32;
+	int i;
+	int npkey = DSA_NPKEY;
+
+	crypto_hash_init(sha1);
+
+	n = pk->version < 4 ? 8 : 6;
+	for (i = 0; i < npkey; i++) {
+		nb[i] = mpi_get_nbits(pk->pkey[i]);
+		pp[i] = mpi_get_buffer( pk->pkey[i], nn + i, NULL);
+		n += 2 + nn[i];
+	}
+
+	SHA1_putc(sha1, 0x99);     /* ctb */
+	SHA1_putc(sha1, n >> 8);   /* 2 uint8_t length header */
+	SHA1_putc(sha1, n);
+
+	if (pk->version < 4)
+		SHA1_putc(sha1, 3);
+	else
+		SHA1_putc(sha1, 4);
+
+	a32 = pk->timestamp;
+	SHA1_putc(sha1, a32 >> 24 );
+	SHA1_putc(sha1, a32 >> 16 );
+	SHA1_putc(sha1, a32 >>  8 );
+	SHA1_putc(sha1, a32 >>  0 );
+
+	if (pk->version < 4) {
+		uint16_t a16;
+
+		if( pk->expiredate )
+			a16 = (uint16_t)
+				((pk->expiredate - pk->timestamp) / 86400L);
+		else
+			a16 = 0;
+		SHA1_putc(sha1, a16 >> 8);
+		SHA1_putc(sha1, a16 >> 0);
+	}
+
+	SHA1_putc(sha1, PUBKEY_ALGO_DSA);
+
+	for (i = 0; i < npkey; i++) {
+		SHA1_putc(sha1, nb[i] >> 8);
+		SHA1_putc(sha1, nb[i]);
+		SHA1_write(sha1, pp[i], nn[i]);
+		kfree(pp[i]);
+	}
+}
+
+/*
+ * parse a user ID embedded in a signature
+ */
+static int ksign_parse_user_id(const uint8_t *datap, const uint8_t *endp,
+			       ksign_user_id_actor_t uidfnx, void *fnxdata)
+{
+	struct ksign_user_id *uid;
+	int rc = 0;
+	int n;
+
+	if (!uidfnx)
+		return 0;
+
+	n = endp - datap;
+	uid = kmalloc(sizeof(*uid) + n + 1, GFP_KERNEL);
+	if (!uid)
+		return -ENOMEM;
+	uid->len = n;
+
+	memcpy(uid->name, datap, n);
+	uid->name[n] = 0;
+
+	rc = uidfnx(uid, fnxdata);
+	if (rc == 0)
+		return rc; /* uidfnx keeps the record */
+	if (rc == 1)
+		rc = 0;
+
+	ksign_free_user_id(uid);
+	return rc;
+}
+
+/*
+ * extract a public key embedded in a signature
+ */
+static int ksign_parse_key(const uint8_t *datap, const uint8_t *endp,
+			   uint8_t *hdr, int hdrlen,
+			   ksign_public_key_actor_t pkfnx, void *fnxdata)
+{
+	struct ksign_public_key *pk;
+	struct hash_desc sha1;
+	unsigned long timestamp, expiredate;
+	uint8_t hash[SHA1_DIGEST_SIZE];
+	int i, version;
+	int is_v4 = 0;
+	int rc = 0;
+
+	if (endp - datap < 12) {
+		printk("ksign: public key packet too short\n");
+		return -EBADMSG;
+	}
+
+	version = *datap++;
+	switch (version) {
+	case 4:
+		is_v4 = 1;
+	case 2:
+	case 3:
+		break;
+	default:
+		printk("ksign: public key packet with unknown version %d\n",
+		       version);
+		return -EBADMSG;
+	}
+
+	timestamp = read_32(&datap);
+	if (is_v4) {
+		expiredate = 0; /* have to get it from the selfsignature */
+	} else {
+		unsigned short ndays;
+		ndays = read_16(&datap);
+		if (ndays)
+			expiredate = timestamp + ndays * 86400L;
+		else
+			expiredate = 0;
+	}
+
+	if (*datap++ != PUBKEY_ALGO_DSA) {
+		printk("ksign: public key packet with unknown version %d\n",
+		       version);
+		return 0;
+	}
+
+	/* extract the stuff from the DSA public key */
+	pk = kzalloc(sizeof(struct ksign_public_key), GFP_KERNEL);
+	if (!pk)
+		return -ENOMEM;
+
+	atomic_set(&pk->count, 1);
+	pk->timestamp	= timestamp;
+	pk->expiredate	= expiredate;
+	pk->hdrbytes	= hdrlen;
+	pk->version	= version;
+
+	for (i = 0; i < DSA_NPKEY; i++) {
+		unsigned int remaining = endp - datap;
+		pk->pkey[i] = mpi_read_from_buffer(datap, &remaining);
+		datap += remaining;
+	}
+
+	rc = -ENOMEM;
+
+	sha1.tfm = crypto_hash_cast(crypto_alloc_tfm2("sha1", 0, 1));
+	if (!sha1.tfm)
+		goto cleanup;
+	sha1.flags = 0;
+
+	ksign_calc_pk_keyid(&sha1, pk);
+	crypto_hash_final(&sha1, hash);
+	crypto_free_hash(sha1.tfm);
+
+	pk->keyid[0] = hash[12] << 24 | hash[13] << 16 | hash[14] << 8 | hash[15];
+	pk->keyid[1] = hash[16] << 24 | hash[17] << 16 | hash[18] << 8 | hash[19];
+
+	rc = 0;
+	if (pkfnx)
+		rc = pkfnx(pk, fnxdata);
+
+cleanup:
+	ksign_put_public_key(pk);
+	return rc;
+}
+
+/*
+ * find an element representing the issuer
+ */
+static const uint8_t *ksign_find_sig_issuer(const uint8_t *buffer)
+{
+	size_t buflen;
+	size_t n;
+	int type;
+	int seq = 0;
+
+	if (!buffer)
+		return NULL;
+
+	buflen = read_16(&buffer);
+	while (buflen) {
+		n = *buffer++; buflen--;
+		if (n == 255) {
+			if (buflen < 4)
+				goto too_short;
+			n = read_32(&buffer);
+			buflen -= 4;
+		} else if (n >= 192) {
+			if(buflen < 2)
+				goto too_short;
+			n = ((n - 192) << 8) + *buffer + 192;
+			buffer++;
+			buflen--;
+		}
+
+		if (buflen < n)
+			goto too_short;
+
+		type = *buffer & 0x7f;
+		if (!(++seq > 0)) {
+			;
+		} else if (type == SIGSUBPKT_ISSUER) {
+			/* found */
+			buffer++;
+			n--;
+			if (n > buflen || n < 8)
+				goto too_short;
+			return buffer;
+		}
+
+		buffer += n;
+		buflen -= n;
+	}
+
+too_short:
+	return NULL; /* end of subpackets; not found */
+}
+
+/*
+ * extract signature data embedded in a signature
+ */
+static int ksign_parse_signature(const uint8_t *datap, const uint8_t *endp,
+				 ksign_signature_actor_t sigfnx, void *fnxdata)
+{
+	struct ksign_signature *sig;
+	size_t n;
+	int version, is_v4 = 0;
+	int rc;
+	int i;
+
+	if (endp - datap < 16) {
+		printk("ksign: signature packet too short\n");
+		return -EBADMSG;
+	}
+
+	version = *datap++;
+	switch (version) {
+	case 4:
+		is_v4 = 1;
+	case 3:
+	case 2:
+		break;
+	default:
+		printk("ksign: signature packet with unknown version %d\n",
+		       version);
+		return 0;
+	}
+
+	/* store information */
+	sig = kzalloc(sizeof(*sig), GFP_KERNEL);
+	if (!sig)
+		return -ENOMEM;
+
+	sig->version = version;
+
+	if (!is_v4)
+		datap++; /* ignore md5 length */
+
+	sig->sig_class = *datap++;
+	if (!is_v4) {
+		sig->timestamp = read_32(&datap);
+		sig->keyid[0] = read_32(&datap);
+		sig->keyid[1] = read_32(&datap);
+	}
+
+	rc = 0;
+	if (*datap++ != PUBKEY_ALGO_DSA) {
+		printk("ksign: ignoring non-DSA signature\n");
+		goto leave;
+	}
+	if (*datap++ != DIGEST_ALGO_SHA1) {
+		printk("ksign: ignoring non-SHA1 signature\n");
+		goto leave;
+	}
+
+	rc = -EBADMSG;
+	if (is_v4) {
+		/* read subpackets */
+		n = read_16(&datap); /* length of hashed data */
+		if (n > 10000) {
+			printk("ksign: signature packet:"
+			       " hashed data too long\n");
+			goto leave;
+		}
+		if (n) {
+			if ((size_t)(endp - datap) < n) {
+				printk("ksign: signature packet:"
+				       " available data too short\n");
+				goto leave;
+			}
+			sig->hashed_data = kmalloc(n + 2, GFP_KERNEL);
+			if (!sig->hashed_data) {
+				rc = -ENOMEM;
+				goto leave;
+			}
+			sig->hashed_data[0] = n >> 8;
+			sig->hashed_data[1] = n;
+			memcpy(sig->hashed_data + 2, datap, n);
+			datap += n;
+		}
+
+		n = read_16(&datap); /* length of unhashed data */
+		if (n > 10000) {
+			printk("ksign: signature packet:"
+			       " unhashed data too long\n");
+			goto leave;
+		}
+		if (n) {
+			if ((size_t) (endp - datap) < n) {
+				printk("ksign: signature packet:"
+				       " available data too short\n");
+				goto leave;
+			}
+			sig->unhashed_data = kmalloc(n + 2, GFP_KERNEL);
+			if (!sig->unhashed_data) {
+				rc = -ENOMEM;
+				goto leave;
+			}
+			sig->unhashed_data[0] = n >> 8;
+			sig->unhashed_data[1] = n;
+			memcpy(sig->unhashed_data + 2, datap, n);
+			datap += n;
+		}
+	}
+
+	if (endp - datap < 5) { /* sanity check */
+		printk("ksign: signature packet too short\n");
+		goto leave;
+	}
+
+	sig->digest_start[0] = *datap++;
+	sig->digest_start[1] = *datap++;
+
+	if (is_v4) {
+		const uint8_t *p;
+
+		p = ksign_find_sig_issuer(sig->hashed_data);
+		if (!p)
+			p = ksign_find_sig_issuer(sig->unhashed_data);
+		if (!p) {
+			printk("ksign: signature packet without issuer\n");
+		} else {
+			sig->keyid[0] = buffer_to_u32(p);
+			sig->keyid[1] = buffer_to_u32(p + 4);
+		}
+	}
+
+	for (i = 0; i < DSA_NSIG; i++) {
+		unsigned remaining = endp - datap;
+		sig->data[i] = mpi_read_from_buffer(datap, &remaining);
+		datap += remaining;
+	}
+
+	rc = 0;
+	if (sigfnx) {
+		rc = sigfnx(sig, fnxdata);
+		if (rc == 0)
+			return rc; /* sigfnx keeps the signature */
+		if (rc == 1)
+			rc = 0;
+	}
+
+leave:
+	ksign_free_signature(sig);
+	return rc;
+}
+
+/*
+ * parse the next packet and call appropriate handler function for known types
+ * - returns:
+ *     0 on EOF
+ *     1 if there might be more packets
+ *     -EBADMSG if the packet is in an invalid format
+ *     -ve on other error
+ */
+static int ksign_parse_one_packet(const uint8_t **datap,
+				  const uint8_t *endp,
+				  ksign_signature_actor_t sigfnx,
+				  ksign_public_key_actor_t pkfnx,
+				  ksign_user_id_actor_t uidfnx,
+				  void *data)
+{
+	int rc, c, ctb, pkttype, lenuint8_ts;
+	unsigned long pktlen;
+	uint8_t hdr[8];
+	int hdrlen;
+
+	/* extract the next packet and dispatch it */
+	rc = 0;
+	if (*datap >= endp)
+		goto leave;
+	ctb = *(*datap)++;
+
+	rc = -EBADMSG;
+
+	hdrlen = 0;
+	hdr[hdrlen++] = ctb;
+	if (!(ctb & 0x80)) {
+		printk("ksign: invalid packet (ctb=%02x)\n", ctb);
+		goto leave;
+	}
+
+	pktlen = 0;
+	if (ctb & 0x40) {
+		pkttype = ctb & 0x3f;
+		if (*datap >= endp) {
+			printk("ksign: 1st length byte missing\n");
+			goto leave;
+		}
+		c = *(*datap)++;
+		hdr[hdrlen++] = c;
+
+		if (c < 192) {
+			pktlen = c;
+		} else if (c < 224) {
+			pktlen = (c - 192) * 256;
+			if (*datap >= endp) {
+				printk("ksign: 2nd length uint8_t missing\n");
+				goto leave;
+			}
+			c = *(*datap)++;
+			hdr[hdrlen++] = c;
+			pktlen += c + 192;
+		} else if (c == 255) {
+			if (*datap + 3 >= endp) {
+				printk("ksign: 4 uint8_t length invalid\n");
+				goto leave;
+			}
+			pktlen  = (hdr[hdrlen++] = *(*datap)++ << 24);
+			pktlen |= (hdr[hdrlen++] = *(*datap)++ << 16);
+			pktlen |= (hdr[hdrlen++] = *(*datap)++ <<  8);
+			pktlen |= (hdr[hdrlen++] = *(*datap)++ <<  0);
+		} else {
+			pktlen = 0;/* to indicate partial length */
+		}
+	} else {
+		pkttype = (ctb >> 2) & 0xf;
+		lenuint8_ts = ((ctb & 3) == 3) ? 0 : (1 << (ctb & 3));
+		if( !lenuint8_ts ) {
+			pktlen = 0; /* don't know the value */
+		} else {
+			if (*datap + lenuint8_ts > endp) {
+				printk("ksign: length uint8_ts missing\n");
+				goto leave;
+			}
+			for( ; lenuint8_ts; lenuint8_ts-- ) {
+				pktlen <<= 8;
+				pktlen |= hdr[hdrlen++] = *(*datap)++;
+			}
+		}
+	}
+
+	if (*datap + pktlen > endp) {
+		printk("ksign: packet length longer than available data\n");
+		goto leave;
+	}
+
+	/* deal with the next packet appropriately */
+	switch (pkttype) {
+	case PKT_PUBLIC_KEY:
+		rc = ksign_parse_key(*datap, *datap + pktlen, hdr, hdrlen,
+				     pkfnx, data);
+		break;
+	case PKT_SIGNATURE:
+		rc = ksign_parse_signature(*datap, *datap + pktlen,
+					   sigfnx, data);
+		break;
+	case PKT_USER_ID:
+		rc = ksign_parse_user_id(*datap, *datap + pktlen,
+					 uidfnx, data);
+		break;
+	default:
+		rc = 0; /* unknown packet */
+		break;
+	}
+
+	*datap += pktlen;
+leave:
+	return rc;
+}
+
+/*
+ * parse the contents of a packet buffer, passing the signature, public key and
+ * user ID to the caller's callback functions
+ */
+int ksign_parse_packets(const uint8_t *buf,
+			size_t size,
+			ksign_signature_actor_t sigfnx,
+			ksign_public_key_actor_t pkfnx,
+			ksign_user_id_actor_t uidfnx,
+			void *data)
+{
+	const uint8_t *datap, *endp;
+	int rc;
+
+	datap = buf;
+	endp = buf + size;
+	do {
+		rc = ksign_parse_one_packet(&datap, endp,
+					    sigfnx, pkfnx, uidfnx, data);
+	} while (rc == 0 && datap < endp);
+
+	return rc;
+}
diff --git a/crypto/signature/ksign-publickey.c b/crypto/signature/ksign-publickey.c
new file mode 100644
index 0000000..832a419
--- /dev/null
+++ b/crypto/signature/ksign-publickey.c
@@ -0,0 +1,18 @@
+#include "local.h"
+#include "key.h"
+
+static int __init ksign_init(void)
+{
+	int rc;
+
+	printk("ksign: Installing public key data\n");
+
+	rc = ksign_load_keyring_from_buffer(ksign_def_public_key,
+					    ksign_def_public_key_size);
+	if (rc < 0)
+		printk("Unable to load default keyring: error=%d\n", -rc);
+
+	return rc;
+}
+
+module_init(ksign_init)
diff --git a/crypto/signature/ksign.c b/crypto/signature/ksign.c
new file mode 100644
index 0000000..b62eb38
--- /dev/null
+++ b/crypto/signature/ksign.c
@@ -0,0 +1,180 @@
+/* ksign.c: signature checker
+ *
+ * Copyright (C) 2004 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 License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <asm/errno.h>
+#include "local.h"
+
+#if 0
+#define _debug(FMT, ...) printk(KERN_DEBUG FMT, ##__VA_ARGS__)
+#else
+#define _debug(FMT, ...) do { ; } while (0)
+#endif
+
+/*
+ * check the signature which is contained in SIG.
+ */
+static int ksign_signature_check(const struct ksign_signature *sig,
+				 struct crypto_hash *sha1_tfm)
+{
+	struct ksign_public_key *pk;
+	struct hash_desc sha1_d;
+	uint8_t sha1[SHA1_DIGEST_SIZE];
+	MPI result = NULL;
+	int rc = 0;
+
+	pk = ksign_get_public_key(sig->keyid);
+	if (!pk) {
+		printk("ksign: module signed with unknown public key\n");
+		printk("- signature keyid: %08x%08x ver=%u\n",
+		       sig->keyid[0], sig->keyid[1], sig->version);
+		return -ENOKEY;
+	}
+
+	if (pk->timestamp > sig->timestamp)
+		printk("ksign:"
+		       " public key is %lu seconds newer than the signature\n",
+		       pk->timestamp - sig->timestamp);
+
+	sha1_d.tfm = sha1_tfm;
+	sha1_d.flags = 0;
+
+	/* complete the digest */
+	if (sig->version >= 4)
+		SHA1_putc(&sha1_d, sig->version);
+	SHA1_putc(&sha1_d, sig->sig_class);
+
+	if (sig->version < 4) {
+		u32 a = sig->timestamp;
+		SHA1_putc(&sha1_d, (a >> 24) & 0xff);
+		SHA1_putc(&sha1_d, (a >> 16) & 0xff);
+		SHA1_putc(&sha1_d, (a >>  8) & 0xff);
+		SHA1_putc(&sha1_d, (a >>  0) & 0xff);
+	}
+	else {
+		uint8_t buf[6];
+		size_t n;
+		SHA1_putc(&sha1_d, PUBKEY_ALGO_DSA);
+		SHA1_putc(&sha1_d, DIGEST_ALGO_SHA1);
+		if (sig->hashed_data) {
+			n = (sig->hashed_data[0] << 8) | sig->hashed_data[1];
+			SHA1_write(&sha1_d, sig->hashed_data, n + 2);
+			n += 6;
+		}
+		else {
+			n = 6;
+		}
+
+		/* add some magic */
+		buf[0] = sig->version;
+		buf[1] = 0xff;
+		buf[2] = n >> 24;
+		buf[3] = n >> 16;
+		buf[4] = n >>  8;
+		buf[5] = n;
+		SHA1_write(&sha1_d, buf, 6);
+	}
+
+	crypto_hash_final(&sha1_d, sha1);
+	crypto_free_hash(sha1_tfm);
+
+	rc = -ENOMEM;
+	result = mpi_alloc((SHA1_DIGEST_SIZE + BYTES_PER_MPI_LIMB - 1) /
+			   BYTES_PER_MPI_LIMB);
+	if (!result)
+		goto cleanup;
+
+	rc = mpi_set_buffer(result, sha1, SHA1_DIGEST_SIZE, 0);
+	if (rc < 0)
+		goto cleanup;
+
+	rc = DSA_verify(result, sig->data, pk->pkey);
+
+ cleanup:
+	mpi_free(result);
+	ksign_put_public_key(pk);
+
+	return rc;
+}
+
+/*
+ * examine the signatures that are parsed out of the signature data - we keep
+ * the first one that's appropriate and ignore the rest
+ * - return 0 if signature of interest (sig not freed by caller)
+ * - return 1 if no interest (caller frees)
+ */
+static int ksign_grab_signature(struct ksign_signature *sig, void *fnxdata)
+{
+	struct ksign_signature **_sig = fnxdata;
+
+	if (sig->sig_class != 0x00) {
+		_debug("ksign: standalone signature of class 0x%02x\n",
+		       sig->sig_class);
+		return 1;
+	}
+
+	if (*_sig)
+		return 1;
+
+	*_sig = sig;
+	return 0;
+}
+
+/*
+ * verify the signature of some data with one of the kernel's known public keys
+ * - the SHA1 context should be currently open with the signed data digested
+ *   into it so that more data can be appended
+ * - the SHA1 context is finalised and freed before returning
+ */
+int ksign_verify_signature(const char *sigdata, unsigned sig_size,
+			   struct crypto_hash *sha1)
+{
+	struct ksign_signature *sig = NULL;
+	int retval;
+
+	/* parse the signature data to get the actual signature */
+	retval = ksign_parse_packets(sigdata, sig_size,
+				     &ksign_grab_signature, NULL, NULL,
+				     &sig);
+	if (retval < 0)
+		goto cleanup;
+
+	if (!sig) {
+		printk(KERN_NOTICE
+		       "Couldn't find valid DSA signature in module\n");
+		return -ENOENT;
+	}
+
+	_debug("signature keyid: %08x%08x ver=%u\n",
+	       sig->keyid[0], sig->keyid[1], sig->version);
+
+	/* check the data SHA1 transformation against the public key */
+	retval = ksign_signature_check(sig, sha1);
+	switch (retval) {
+	case 0:
+		_debug("ksign: Signature check succeeded\n");
+		break;
+	case -ENOMEM:
+		_debug("ksign: Signature check ENOMEM\n");
+		break;
+	default:
+		_debug("ksign: Signature check failed\n");
+		if (retval != -ENOKEY)
+			retval = -EKEYREJECTED;
+		break;
+	}
+
+ cleanup:
+	if (sig)
+		ksign_free_signature(sig);
+
+	return retval;
+}
diff --git a/crypto/signature/local.h b/crypto/signature/local.h
new file mode 100644
index 0000000..aa18cc4
--- /dev/null
+++ b/crypto/signature/local.h
@@ -0,0 +1,160 @@
+/* local.h: kernel signature checker internal defs
+ *
+ * Copyright (C) 2004 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ * - Derived from GnuPG packet.h - packet definitions
+ *   - Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <linux/list.h>
+#include <linux/crypto.h>
+#include <linux/crypto/ksign.h>
+#include <linux/crypto/mpi.h>
+#include <asm/atomic.h>
+
+#define SHA1_DIGEST_SIZE	20
+
+#define PUBKEY_USAGE_SIG	1	    /* key is good for signatures */
+#define PUBKEY_USAGE_ENC	2	    /* key is good for encryption */
+
+#define PUBKEY_ALGO_DSA		17
+#define DSA_NPKEY		4	/* number of MPI's in DSA public key */
+#define DSA_NSIG		2	/* number of MPI's in DSA signature */
+
+#define DIGEST_ALGO_SHA1	2
+
+typedef enum {
+	PKT_NONE			= 0,
+	PKT_SIGNATURE			= 2,	/* secret key encrypted packet */
+	PKT_PUBLIC_KEY			= 6,	/* public key */
+	PKT_USER_ID			= 13,	/* user id packet */
+} pkttype_t;
+
+typedef enum {
+	SIGSUBPKT_TEST_CRITICAL		= -3,
+	SIGSUBPKT_NONE			= 0,
+	SIGSUBPKT_SIG_CREATED		= 2,	/* signature creation time */
+	SIGSUBPKT_SIG_EXPIRE		= 3,	/* signature expiration time */
+	SIGSUBPKT_EXPORTABLE		= 4,	/* exportable */
+	SIGSUBPKT_TRUST			= 5,	/* trust signature */
+	SIGSUBPKT_REGEXP		= 6,	/* regular expression */
+	SIGSUBPKT_REVOCABLE		= 7,	/* revocable */
+	SIGSUBPKT_KEY_EXPIRE		= 9,	/* key expiration time */
+	SIGSUBPKT_ARR			= 10,	/* additional recipient request */
+	SIGSUBPKT_PREF_SYM		= 11,	/* preferred symmetric algorithms */
+	SIGSUBPKT_REV_KEY		= 12,	/* revocation key */
+	SIGSUBPKT_ISSUER		= 16,	/* issuer key ID */
+	SIGSUBPKT_NOTATION		= 20,	/* notation data */
+	SIGSUBPKT_PREF_HASH		= 21,	/* preferred hash algorithms */
+	SIGSUBPKT_PREF_COMPR		= 22,	/* preferred compression algorithms */
+	SIGSUBPKT_KS_FLAGS		= 23,	/* key server preferences */
+	SIGSUBPKT_PREF_KS		= 24,	/* preferred key server */
+	SIGSUBPKT_PRIMARY_UID		= 25,	/* primary user id */
+	SIGSUBPKT_POLICY		= 26,	/* policy URL */
+	SIGSUBPKT_KEY_FLAGS		= 27,	/* key flags */
+	SIGSUBPKT_SIGNERS_UID		= 28,	/* signer's user id */
+	SIGSUBPKT_REVOC_REASON		= 29,	/* reason for revocation */
+	SIGSUBPKT_PRIV_VERIFY_CACHE	= 101,	/* cache verification result */
+
+	SIGSUBPKT_FLAG_CRITICAL		= 128
+} sigsubpkttype_t;
+
+/*
+ * signature record
+ */
+struct ksign_signature {
+	uint32_t	keyid[2];		/* 64 bit keyid */
+	time_t		timestamp;		/* signature made */
+	uint8_t		version;
+	uint8_t		sig_class;		/* sig classification, append for MD calculation*/
+	uint8_t		*hashed_data;		/* all subpackets with hashed  data (v4 only) */
+	uint8_t		*unhashed_data;		/* ditto for unhashed data */
+	uint8_t		digest_start[2];	/* first 2 uint8_ts of the digest */
+	MPI		data[DSA_NSIG];
+};
+
+extern void ksign_free_signature(struct ksign_signature *sig);
+
+/*
+ * public key record
+ */
+struct ksign_public_key {
+	struct list_head link;
+	atomic_t	count;			/* ref count */
+	time_t		timestamp;		/* key made */
+	time_t		expiredate;		/* expires at this date or 0 if not at all */
+	uint8_t		hdrbytes;		/* number of header bytes */
+	uint8_t		version;
+	int		is_valid;		/* key (especially subkey) is valid */
+	unsigned long	local_id;		/* internal use, valid if > 0 */
+	uint32_t	main_keyid[2];		/* keyid of the primary key */
+	uint32_t	keyid[2];		/* calculated by keyid_from_pk() */
+	MPI		pkey[DSA_NPKEY];
+};
+
+extern void ksign_free_public_key(struct ksign_public_key *pk);
+
+static inline void ksign_put_public_key(struct ksign_public_key *pk)
+{
+	if (atomic_dec_and_test(&pk->count))
+		ksign_free_public_key(pk);
+}
+
+extern int ksign_load_keyring_from_buffer(const void *buffer, size_t size);
+
+extern struct ksign_public_key *ksign_get_public_key(const uint32_t *keyid);
+
+/*
+ * user ID record
+ */
+struct ksign_user_id {
+	int		len;			/* length of the name */
+	char		name[0];
+};
+
+extern void ksign_free_user_id(struct ksign_user_id *uid);
+
+/*
+ *
+ */
+typedef int (*ksign_signature_actor_t)(struct ksign_signature *, void *fnxdata);
+typedef int (*ksign_public_key_actor_t)(struct ksign_public_key *, void *fnxdata);
+typedef int (*ksign_user_id_actor_t)(struct ksign_user_id *, void *fnxdata);
+
+extern int ksign_parse_packets(const uint8_t *buf,
+			       size_t size,
+			       ksign_signature_actor_t sigfnx,
+			       ksign_public_key_actor_t pkfnx,
+			       ksign_user_id_actor_t uidfnx,
+			       void *data);
+
+extern int DSA_verify(const MPI datahash, const MPI sig[], const MPI pkey[]);
+
+/*
+ * fast access to the digest
+ * - we _know_ the data is locked into kernel memory, so we don't want to have
+ *   to kmap() it
+ */
+static inline void SHA1_putc(struct hash_desc *sha1, uint8_t ch)
+{
+	crypto_hash_update_kernel(sha1, &ch, 1);
+}
+
+static inline void SHA1_write(struct hash_desc *sha1, const void *s, size_t n)
+{
+	crypto_hash_update_kernel(sha1, s, n);
+}
diff --git a/include/linux/crypto/ksign.h b/include/linux/crypto/ksign.h
new file mode 100644
index 0000000..27c9e4a
--- /dev/null
+++ b/include/linux/crypto/ksign.h
@@ -0,0 +1,22 @@
+/* ksign.h: in-kernel signature checker
+ *
+ * Copyright (C) 2004 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 License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LINUX_CRYPTO_KSIGN_H
+#define _LINUX_CRYPTO_KSIGN_H
+
+#include <linux/types.h>
+
+#ifdef CONFIG_CRYPTO_SIGNATURE
+extern int ksign_verify_signature(const char *sig, unsigned sig_size,
+				  struct crypto_hash *sha1);
+#endif
+
+#endif /* _LINUX_CRYPTO_KSIGN_H */

  parent reply	other threads:[~2007-02-14 19:10 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2007-02-14 19:09 [PATCH 0/6] MODSIGN: Kernel module signing David Howells
2007-02-14 19:09 ` [PATCH 2/6] MODSIGN: In-kernel crypto extensions David Howells
2007-02-14 19:09 ` [PATCH 3/6] MODSIGN: Add indications of module ELF types David Howells
2007-02-14 19:09 ` [PATCH 4/6] MODSIGN: Module ELF verifier David Howells
2007-02-14 19:10 ` David Howells [this message]
2007-02-14 19:10 ` [PATCH 6/6] MODSIGN: Apply signature checking to modules on module load David Howells
2007-02-14 19:26 ` [PATCH 0/6] MODSIGN: Kernel module signing Linus Torvalds
2007-02-14 19:40 ` David Howells
2007-02-14 21:32   ` Michael Halcrow
2007-02-14 21:59   ` David Howells
2007-02-14 22:21     ` Michael Halcrow
2007-02-15 21:31   ` Indan Zupancic
2007-02-15  3:41 ` Andrew Morton
2007-02-15  4:13   ` Dave Jones
2007-02-15  5:35     ` Andreas Gruenbacher
2007-02-15  5:45       ` Dave Jones
2007-02-15  6:14         ` Andreas Gruenbacher
2007-02-15  6:22           ` Dave Jones
2007-02-15 20:34           ` Valdis.Kletnieks
2007-02-15 22:12             ` Andreas Gruenbacher
2007-02-16  0:15               ` Olaf Kirch
2007-02-15 22:10           ` Pavel Machek
2007-02-15 20:55     ` Valdis.Kletnieks
2007-02-15 21:32       ` Adrian Bunk
2007-02-15 22:12         ` Valdis.Kletnieks
2007-02-15 14:35 ` Roman Zippel
2007-02-15 17:32 ` David Howells
2007-02-15 18:33   ` Roman Zippel
2007-02-15 20:01     ` David Lang
2007-02-15 21:01       ` Roman Zippel
2007-02-15 21:03 ` Adrian Bunk
2007-02-15 22:13 ` Pavel Machek
2007-02-16 20:21   ` Dave Jones
2007-02-16 20:27     ` Arjan van de Ven

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20070214191004.6438.19957.stgit@warthog.cambridge.redhat.com \
    --to=dhowells@redhat.com \
    --cc=akpm@osdl.org \
    --cc=arjan@infradead.org \
    --cc=davej@redhat.com \
    --cc=herbert.xu@redhat.com \
    --cc=linux-crypto@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=torvalds@osdl.org \
    --subject='Re: [PATCH 5/6] MODSIGN: Module signature checker and key manager' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).