Re: [PATCH 16/21] KEYS: PGP-based public key signature verification[ver #3]

From: Kasatkin, Dmitry
Date: Wed Jan 18 2012 - 06:36:26 EST


On Fri, Dec 2, 2011 at 8:45 PM, David Howells <dhowells@xxxxxxxxxx> wrote:
> Provide handlers for PGP-based public-key algorithm signature verification.
> This does most of the work involved in signature verification as most of it is
> public-key algorithm agnostic. ÂThe public-key verification algorithm itself
> is just the last little bit and is supplied the complete hash data to process.
>
> This requires glue logic putting on top to make use of it - something the next
> patch provides.
>
> Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
> ---
>
> Âsecurity/keys/Makefile     |  Â3
> Âsecurity/keys/pgp_parser.h   |  Â6 +
> Âsecurity/keys/pgp_pubkey_sig.c | Â323 ++++++++++++++++++++++++++++++++++++++++
> Â3 files changed, 331 insertions(+), 1 deletions(-)
> Âcreate mode 100644 security/keys/pgp_pubkey_sig.c
>
>
> diff --git a/security/keys/Makefile b/security/keys/Makefile
> index 242a087..fc1968e 100644
> --- a/security/keys/Makefile
> +++ b/security/keys/Makefile
> @@ -34,4 +34,5 @@ obj-$(CONFIG_CRYPTO_KEY_PGP_PARSER) += pgp_parser.o
> Âcrypto_keys-y := crypto_type.o crypto_verify.o
>
> Âpgp_parser-y := \
> - Â Â Â pgp_key_parser.o
> + Â Â Â pgp_key_parser.o \
> + Â Â Â pgp_pubkey_sig.o
> diff --git a/security/keys/pgp_parser.h b/security/keys/pgp_parser.h
> index 1cda231..a6192ce 100644
> --- a/security/keys/pgp_parser.h
> +++ b/security/keys/pgp_parser.h
> @@ -21,3 +21,9 @@
> Â*/
> Âextern const
> Âstruct public_key_algorithm *pgp_public_key_algorithms[PGP_PUBKEY__LAST];
> +
> +/*
> + * pgp_pubkey_sig.c
> + */
> +extern struct crypto_key_verify_context *pgp_pkey_verify_sig_begin(
> + Â Â Â struct key *crypto_key, const u8 *sigdata, size_t siglen);
> diff --git a/security/keys/pgp_pubkey_sig.c b/security/keys/pgp_pubkey_sig.c
> new file mode 100644
> index 0000000..b4b7cb0
> --- /dev/null
> +++ b/security/keys/pgp_pubkey_sig.c
> @@ -0,0 +1,323 @@
> +/* Handling for PGP public key signature data [RFC 4880]
> + *
> + * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
> + * Written by David Howells (dhowells@xxxxxxxxxx)
> + *
> + * 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) "PGPSIG: "fmt
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/pgp.h>
> +#include "public_key.h"
> +#include "pgp_parser.h"
> +
> +const struct {
> + Â Â Â enum pkey_hash_algo algo : 8;
> +} pgp_pubkey_hash[PGP_HASH__LAST] = {
> +    [PGP_HASH_MD5].algo       = PKEY_HASH_MD5,
> +    [PGP_HASH_SHA1].algo      Â= PKEY_HASH_SHA1,
> +    [PGP_HASH_RIPE_MD_160].algo   = PKEY_HASH_RIPE_MD_160,
> +    [PGP_HASH_SHA256].algo     Â= PKEY_HASH_SHA256,
> +    [PGP_HASH_SHA384].algo     Â= PKEY_HASH_SHA384,
> +    [PGP_HASH_SHA512].algo     Â= PKEY_HASH_SHA512,
> +    [PGP_HASH_SHA224].algo     Â= PKEY_HASH_SHA224,
> +};
> +
> +static int pgp_pkey_verify_sig_add_data(struct crypto_key_verify_context *ctx,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â const void *data, size_t datalen);
> +static int pgp_pkey_verify_sig_end(struct crypto_key_verify_context *ctx,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âconst u8 *sig, size_t siglen);
> +static void pgp_pkey_verify_sig_cancel(struct crypto_key_verify_context *ctx);
> +
> +struct pgp_pkey_sig_parse_context {
> + Â Â Â struct pgp_parse_context pgp;
> + Â Â Â struct pgp_sig_parameters params;
> +};
> +
> +static int pgp_pkey_parse_signature(struct pgp_parse_context *context,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â enum pgp_packet_tag type,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â u8 headerlen,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â const u8 *data,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â size_t datalen)
> +{
> + Â Â Â struct pgp_pkey_sig_parse_context *ctx =
> + Â Â Â Â Â Â Â container_of(context, struct pgp_pkey_sig_parse_context, pgp);
> +
> + Â Â Â return pgp_parse_sig_params(&data, &datalen, &ctx->params);
> +}
> +
> +/*
> + * Begin the process of verifying a DSA signature.
> + *
> + * This involves allocating the hash into which first the data and then the
> + * metadata will be put, and parsing the signature to check that it matches the
> + * key.
> + */
> +struct crypto_key_verify_context *pgp_pkey_verify_sig_begin(
> + Â Â Â struct key *crypto_key, const u8 *sigdata, size_t siglen)
> +{
> + Â Â Â struct pgp_pkey_sig_parse_context p;
> + Â Â Â struct public_key_signature *sig;
> + Â Â Â struct crypto_shash *tfm;
> + Â Â Â const struct public_key *key = crypto_key->payload.data;
> + Â Â Â size_t digest_size, desc_size;
> + Â Â Â int ret;
> +
> + Â Â Â kenter("{%d},,%zu", key_serial(crypto_key), siglen);
> +
> + Â Â Â if (!key) {
> + Â Â Â Â Â Â Â kleave(" = -ENOKEY [no public key]");
> + Â Â Â Â Â Â Â return ERR_PTR(-ENOKEY);
> + Â Â Â }
> +
> + Â Â Â p.pgp.types_of_interest = (1 << PGP_PKT_SIGNATURE);
> + Â Â Â p.pgp.process_packet = pgp_pkey_parse_signature;
> + Â Â Â ret = pgp_parse_packets(sigdata, siglen, &p.pgp);
> + Â Â Â if (ret < 0)
> + Â Â Â Â Â Â Â return ERR_PTR(ret);
> +
> + Â Â Â if (p.params.pubkey_algo >= PGP_PUBKEY__LAST ||
> + Â Â Â Â Â !pgp_public_key_algorithms[p.params.pubkey_algo]) {
> + Â Â Â Â Â Â Â pr_debug("Unsupported public key algorithm %u\n",
> + Â Â Â Â Â Â Â Â Â Â Â Âp.params.pubkey_algo);
> + Â Â Â Â Â Â Â return ERR_PTR(-ENOKEY);
> + Â Â Â }
> +
> + Â Â Â if (pgp_public_key_algorithms[p.params.pubkey_algo] != key->algo) {
> + Â Â Â Â Â Â Â kleave(" = -ENOKEY [wrong pk algo]");
> + Â Â Â Â Â Â Â return ERR_PTR(-ENOKEY);
> + Â Â Â }
> +
> + Â Â Â if (!(key->capabilities & PKEY_CAN_VERIFY)) {
> + Â Â Â Â Â Â Â kleave(" = -EKEYREJECTED [key can't verify]");
> + Â Â Â Â Â Â Â return ERR_PTR(-EKEYREJECTED);
> + Â Â Â }
> +
> + Â Â Â if (p.params.hash_algo >= PGP_HASH__LAST ||
> + Â Â Â Â Â !pgp_hash_algorithms[p.params.hash_algo]) {
> + Â Â Â Â Â Â Â kleave(" = -ENOPKG [hash]");
> + Â Â Â Â Â Â Â return ERR_PTR(-ENOPKG);
> + Â Â Â }
> +
> + Â Â Â pr_debug("Signature generated with %s hash\n",
> + Â Â Â Â Â Â Â Âpgp_hash_algorithms[p.params.hash_algo]);
> +
> + Â Â Â if (memcmp(&p.params.issuer, key->key_id, 8) != 0) {
> + Â Â Â Â Â Â Â kleave(" = -ENOKEY [wrong key ID]");
> + Â Â Â Â Â Â Â return ERR_PTR(-ENOKEY);
> + Â Â Â }
> +
> + Â Â Â if (p.params.signature_type != PGP_SIG_BINARY_DOCUMENT_SIG &&
> + Â Â Â Â Â p.params.signature_type != PGP_SIG_STANDALONE_SIG) {
> + Â Â Â Â Â Â Â /* We don't want to canonicalise */
> + Â Â Â Â Â Â Â kleave(" = -EOPNOTSUPP [canon]");
> + Â Â Â Â Â Â Â return ERR_PTR(-EOPNOTSUPP);
> + Â Â Â }
> +
> + Â Â Â /* Allocate the hashing algorithm we're going to need and find out how
> + Â Â Â Â* big the hash operational data will be.
> + Â Â Â Â*/
> + Â Â Â tfm = crypto_alloc_shash(pgp_hash_algorithms[p.params.hash_algo], 0, 0);
> + Â Â Â if (IS_ERR(tfm))
> + Â Â Â Â Â Â Â return PTR_ERR(tfm) == -ENOENT ?
> + Â Â Â Â Â Â Â Â Â Â Â ERR_PTR(-ENOPKG) : ERR_CAST(tfm);
> +
> + Â Â Â desc_size = crypto_shash_descsize(tfm);
> + Â Â Â digest_size = crypto_shash_digestsize(tfm);
> +
> + Â Â Â /* We allocate the hash operational data storage on the end of our
> + Â Â Â Â* context data.
> + Â Â Â Â*/
> + Â Â Â sig = kzalloc(sizeof(*sig) + desc_size + digest_size, GFP_KERNEL);
> + Â Â Â if (!sig) {
> + Â Â Â Â Â Â Â crypto_free_shash(tfm);
> + Â Â Â Â Â Â Â return ERR_PTR(-ENOMEM);
> + Â Â Â }
> +
> +    sig->base.key      = crypto_key;
> +    sig->base.add_data   Â= pgp_pkey_verify_sig_add_data;
> +    sig->base.end      = pgp_pkey_verify_sig_end;
> +    sig->base.cancel    Â= pgp_pkey_verify_sig_cancel;
> +    sig->pkey_hash_algo   = pgp_pubkey_hash[p.params.hash_algo].algo;
> +    sig->digest       = (u8 *)sig + sizeof(*sig) + desc_size;
> +    sig->digest_size    Â= digest_size;
> +    sig->hash.tfm      = tfm;
> +    sig->hash.flags     = CRYPTO_TFM_REQ_MAY_SLEEP;
> +
> + Â Â Â ret = crypto_shash_init(&sig->hash);
> + Â Â Â if (ret < 0) {
> + Â Â Â Â Â Â Â crypto_free_shash(sig->hash.tfm);
> + Â Â Â Â Â Â Â kfree(sig);
> + Â Â Â Â Â Â Â return ERR_PTR(ret);
> + Â Â Â }
> +
> + Â Â Â key_get(sig->base.key);
> + Â Â Â kleave(" = %p", sig);
> + Â Â Â return &sig->base;
> +}
> +
> +/*
> + * Load data into the hash
> + */
> +static int pgp_pkey_verify_sig_add_data(struct crypto_key_verify_context *ctx,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â const void *data, size_t datalen)
> +{
> + Â Â Â struct public_key_signature *sig =
> + Â Â Â Â Â Â Â container_of(ctx, struct public_key_signature, base);
> +
> + Â Â Â return crypto_shash_update(&sig->hash, data, datalen);
> +}

Hello,

Synchronous hash SHASH is used only for software hash implementation...
HW acceleration is not supported by this hash.
It is good for short data.
But when calculating a hash over long data as files can be,
async hash AHASH is a preferred choice as enables HW acceleration.

As in my response to
[PATCH 08/21] KEYS: Add signature verification facility [ver #3]
It would be nice to have API to pass pre-computed hash,
then client might tackle async peculiarities by itself...

- Dmitry

> +
> +struct pgp_pkey_sig_digest_context {
> + Â Â Â struct pgp_parse_context pgp;
> + Â Â Â const struct public_key *key;
> + Â Â Â struct public_key_signature *sig;
> +};
> +
> +/*
> + * Extract required metadata from the signature packet and add what we need to
> + * to the hash.
> + */
> +static int pgp_pkey_digest_signature(struct pgp_parse_context *context,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âenum pgp_packet_tag type,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âu8 headerlen,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âconst u8 *data,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âsize_t datalen)
> +{
> + Â Â Â struct pgp_pkey_sig_digest_context *ctx =
> + Â Â Â Â Â Â Â container_of(context, struct pgp_pkey_sig_digest_context, pgp);
> + Â Â Â enum pgp_signature_version version;
> + Â Â Â int i;
> +
> + Â Â Â kenter(",%u,%u,,%zu", type, headerlen, datalen);
> +
> + Â Â Â version = *data;
> + Â Â Â if (version == PGP_SIG_VERSION_3) {
> + Â Â Â Â Â Â Â /* We just include an excerpt of the metadata from a V3
> + Â Â Â Â Â Â Â Â* signature.
> + Â Â Â Â Â Â Â Â*/
> + Â Â Â Â Â Â Â crypto_shash_update(&ctx->sig->hash, data + 1, 5);
> + Â Â Â Â Â Â Â data += sizeof(struct pgp_signature_v3_packet);
> + Â Â Â Â Â Â Â datalen -= sizeof(struct pgp_signature_v3_packet);
> + Â Â Â } else if (version == PGP_SIG_VERSION_4) {
> + Â Â Â Â Â Â Â /* We add the whole metadata header and some of the hashed data
> + Â Â Â Â Â Â Â Â* for a V4 signature, plus a trailer.
> + Â Â Â Â Â Â Â Â*/
> + Â Â Â Â Â Â Â size_t hashedsz, unhashedsz;
> + Â Â Â Â Â Â Â u8 trailer[6];
> +
> + Â Â Â Â Â Â Â hashedsz = 4 + 2 + (data[4] << 8) + data[5];
> + Â Â Â Â Â Â Â crypto_shash_update(&ctx->sig->hash, data, hashedsz);
> +
> + Â Â Â Â Â Â Â trailer[0] = version;
> + Â Â Â Â Â Â Â trailer[1] = 0xffU;
> + Â Â Â Â Â Â Â trailer[2] = hashedsz >> 24;
> + Â Â Â Â Â Â Â trailer[3] = hashedsz >> 16;
> + Â Â Â Â Â Â Â trailer[4] = hashedsz >> 8;
> + Â Â Â Â Â Â Â trailer[5] = hashedsz;
> +
> + Â Â Â Â Â Â Â crypto_shash_update(&ctx->sig->hash, trailer, 6);
> + Â Â Â Â Â Â Â data += hashedsz;
> + Â Â Â Â Â Â Â datalen -= hashedsz;
> +
> + Â Â Â Â Â Â Â unhashedsz = 2 + (data[0] << 8) + data[1];
> + Â Â Â Â Â Â Â data += unhashedsz;
> + Â Â Â Â Â Â Â datalen -= unhashedsz;
> + Â Â Â }
> +
> + Â Â Â if (datalen <= 2) {
> + Â Â Â Â Â Â Â kleave(" = -EBADMSG");
> + Â Â Â Â Â Â Â return -EBADMSG;
> + Â Â Â }
> +
> + Â Â Â /* There's a quick check on the hash available. */
> + Â Â Â ctx->sig->signed_hash_msw[0] = *data++;
> + Â Â Â ctx->sig->signed_hash_msw[1] = *data++;
> + Â Â Â datalen -= 2;
> +
> + Â Â Â /* And then the cryptographic data, which we'll need for the
> + Â Â Â Â* algorithm.
> + Â Â Â Â*/
> + Â Â Â for (i = 0; i < ctx->key->algo->n_sig_mpi; i++) {
> + Â Â Â Â Â Â Â unsigned int remaining = datalen;
> + Â Â Â Â Â Â Â if (remaining == 0) {
> + Â Â Â Â Â Â Â Â Â Â Â pr_debug("short %zu mpi %d\n", datalen, i);
> + Â Â Â Â Â Â Â Â Â Â Â return -EBADMSG;
> + Â Â Â Â Â Â Â }
> + Â Â Â Â Â Â Â ctx->sig->mpi[i] = mpi_read_from_buffer(data, &remaining);
> + Â Â Â Â Â Â Â if (!ctx->sig->mpi[i])
> + Â Â Â Â Â Â Â Â Â Â Â return -ENOMEM;
> + Â Â Â Â Â Â Â data += remaining;
> + Â Â Â Â Â Â Â datalen -= remaining;
> + Â Â Â }
> +
> + Â Â Â if (datalen != 0) {
> + Â Â Â Â Â Â Â kleave(" = -EBADMSG [trailer %zu]", datalen);
> + Â Â Â Â Â Â Â return -EBADMSG;
> + Â Â Â }
> +
> + Â Â Â kleave(" = 0");
> + Â Â Â return 0;
> +}
> +
> +/*
> + * The data is now all loaded into the hash; load the metadata, finalise the
> + * hash and perform the verification step.
> + */
> +static int pgp_pkey_verify_sig_end(struct crypto_key_verify_context *ctx,
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Âconst u8 *sigdata, size_t siglen)
> +{
> + Â Â Â struct public_key_signature *sig =
> + Â Â Â Â Â Â Â container_of(ctx, struct public_key_signature, base);
> + Â Â Â const struct public_key *key = sig->base.key->payload.data;
> + Â Â Â struct pgp_pkey_sig_digest_context p;
> + Â Â Â int ret;
> +
> + Â Â Â kenter("");
> +
> + Â Â Â /* Firstly we add metadata, starting with some of the data from the
> + Â Â Â Â* signature packet */
> + Â Â Â p.pgp.types_of_interest = (1 << PGP_PKT_SIGNATURE);
> + Â Â Â p.pgp.process_packet = pgp_pkey_digest_signature;
> + Â Â Â p.key = key;
> + Â Â Â p.sig = sig;
> + Â Â Â ret = pgp_parse_packets(sigdata, siglen, &p.pgp);
> + Â Â Â if (ret < 0)
> + Â Â Â Â Â Â Â goto error_free_ctx;
> +
> + Â Â Â crypto_shash_final(&sig->hash, sig->digest);
> +
> + Â Â Â ret = key->algo->verify(key, sig);
> +
> +error_free_ctx:
> + Â Â Â pgp_pkey_verify_sig_cancel(ctx);
> + Â Â Â kleave(" = %d", ret);
> + Â Â Â return ret;
> +}
> +
> +/*
> + * Cancel an in-progress data loading
> + */
> +static void pgp_pkey_verify_sig_cancel(struct crypto_key_verify_context *ctx)
> +{
> + Â Â Â struct public_key_signature *sig =
> + Â Â Â Â Â Â Â container_of(ctx, struct public_key_signature, base);
> + Â Â Â int i;
> +
> + Â Â Â kenter("");
> +
> + Â Â Â /* !!! Do we need to tell the crypto layer to cancel too? */
> + Â Â Â crypto_free_shash(sig->hash.tfm);
> + Â Â Â key_put(sig->base.key);
> + Â Â Â for (i = 0; i < ARRAY_SIZE(sig->mpi); i++)
> + Â Â Â Â Â Â Â mpi_free(sig->mpi[i]);
> + Â Â Â kfree(sig);
> +
> + Â Â Â kleave("");
> +}
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/