Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932458AbXBNTKz (ORCPT ); Wed, 14 Feb 2007 14:10:55 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S932476AbXBNTKo (ORCPT ); Wed, 14 Feb 2007 14:10:44 -0500 Received: from mx1.redhat.com ([66.187.233.31]:45604 "EHLO mx1.redhat.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932469AbXBNTKb (ORCPT ); Wed, 14 Feb 2007 14:10:31 -0500 From: David Howells Subject: [PATCH 5/6] MODSIGN: Module signature checker and key manager 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 Date: Wed, 14 Feb 2007 19:10:04 +0000 Message-ID: <20070214191004.6438.19957.stgit@warthog.cambridge.redhat.com> In-Reply-To: <20070214190938.6438.15091.stgit@warthog.cambridge.redhat.com> References: <20070214190938.6438.15091.stgit@warthog.cambridge.redhat.com> User-Agent: StGIT/0.12 MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 33962 Lines: 1323 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 --- 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 +#include +#include +#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 +#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 +#include +#include +#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 +#include +#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 +#include +#include +#include +#include + +#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 + +#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 */ - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/