2022-01-11 18:04:23

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

Support for PGP keys and signatures was proposed by David long time ago,
before the decision of using PKCS#7 for kernel modules signatures
verification was made. After that, there has been not enough interest to
support PGP too.

Lately, when discussing a proposal of introducing fsverity signatures in
Fedora [1], developers expressed their preference on not having a separate
key for signing, which would complicate the management of the distribution.
They would be more in favor of using the same PGP key, currently used for
signing RPM headers, also for file-based signatures (not only fsverity, but
also IMA ones).

Another envisioned use case would be to add the ability to appraise RPM
headers with their existing PGP signature, so that they can be used as an
authenticated source of reference values for appraising remaining
files [2].

To make these use cases possible, introduce support for PGP keys and
signatures in the kernel, and load provided PGP keys in the built-in
keyring, so that PGP signatures of RPM headers, fsverity digests, and IMA
digests can be verified from this trust anchor.

In addition to the original version of the patch set, also introduce
support for signature verification of PGP keys, so that those keys can be
added to keyrings with a signature-based restriction (e.g. .ima). PGP keys
are searched with partial IDs, provided with signature subtype 16 (Issuer).
Search with full IDs could be supported with
draft-ietf-openpgp-rfc4880bis-10, by retrieving the information from
signature subtype 33 (Issuer Fingerprint). Due to the possibility of ID
collisions, the key_or_keyring restriction is not supported.

The patch set includes two preliminary patches: patch 1 introduces
mpi_key_length(), to get the number of bits and bytes of an MPI; patch 2
introduces rsa_parse_priv_key_raw() and rsa_parse_pub_key_raw(), to parse
an RSA key in RAW format if the ASN.1 parser returns an error.

Patches 3-5 introduce the library necessary to parse PGP keys and
signatures, whose support is added with patches 6-10. Patch 11 introduces
verify_pgp_signature() to be used by kernel subsystems (e.g. fsverity and
IMA). Patch 12 is for testing of PGP signatures. Finally, patches 13-14
allow loading a set of PGP keys from a supplied blob at boot time.

I generated the diff from [3] (rebased). It is available at:

https://github.com/robertosassu/linux/compare/pgp-signatures-v1-orig..pgp-signatures-v1

Changelog

v0 [3]:
- style fixes
- move include/linux/pgp.h and pgplib.h to crypto/asymmetric_keys
- introduce verify_pgp_signature()
- replace KEY_ALLOC_TRUSTED flag with KEY_ALLOC_BUILT_IN
- don't fetch PGP subkeys
- drop support for DSA
- store number of MPIs in pgp_key_algo_p_num_mpi array
- replace dynamic memory allocations with static ones in
pgp_generate_fingerprint()
- store only keys with capability of verifying signatures
- remember selection of PGP signature packet and don't repeat parsing
- move search of the PGP key to verify the signature from the beginning
to the end of the verification process (to be similar with PKCS#7)
- don't retry key search in the session keyring from the signature
verification code, let the caller pass the desired keyring
- for the PGP signature test key type, retry the key search in the session
keyring
- retry key search in restrict_link_by_signature() with a partial ID
(provided in the PGP signature)

[1] https://fedoraproject.org/wiki/Changes/FsVerityRPM
[2] https://fedoraproject.org/wiki/Changes/DIGLIM
[3] https://git.kernel.org/pub/scm/linux/kernel/git/dhowells/linux-modsign.git/log/?h=pgp-parser

David Howells (8):
PGPLIB: PGP definitions (RFC 4880)
PGPLIB: Basic packet parser
PGPLIB: Signature parser
KEYS: PGP data parser
KEYS: Provide PGP key description autogeneration
KEYS: PGP-based public key signature verification
PGP: Provide a key type for testing PGP signatures
KEYS: Provide a function to load keys from a PGP keyring blob

Roberto Sassu (6):
mpi: Introduce mpi_key_length()
rsa: add parser of raw format
KEYS: Retry asym key search with partial ID in
restrict_link_by_signature()
KEYS: Calculate key digest and get signature of the key
verification: introduce verify_pgp_signature()
KEYS: Introduce load_pgp_public_keyring()

MAINTAINERS | 1 +
certs/Kconfig | 11 +
certs/Makefile | 7 +
certs/system_certificates.S | 18 +
certs/system_keyring.c | 91 ++++
crypto/asymmetric_keys/Kconfig | 38 ++
crypto/asymmetric_keys/Makefile | 13 +
crypto/asymmetric_keys/pgp.h | 206 ++++++++
crypto/asymmetric_keys/pgp_library.c | 620 ++++++++++++++++++++++++
crypto/asymmetric_keys/pgp_parser.h | 18 +
crypto/asymmetric_keys/pgp_preload.c | 110 +++++
crypto/asymmetric_keys/pgp_public_key.c | 484 ++++++++++++++++++
crypto/asymmetric_keys/pgp_signature.c | 507 +++++++++++++++++++
crypto/asymmetric_keys/pgp_test_key.c | 129 +++++
crypto/asymmetric_keys/pgplib.h | 74 +++
crypto/asymmetric_keys/restrict.c | 10 +-
crypto/rsa.c | 14 +-
crypto/rsa_helper.c | 69 +++
include/crypto/internal/rsa.h | 6 +
include/crypto/pgp.h | 35 ++
include/linux/mpi.h | 2 +
include/linux/verification.h | 23 +
lib/mpi/mpicoder.c | 33 +-
23 files changed, 2506 insertions(+), 13 deletions(-)
create mode 100644 crypto/asymmetric_keys/pgp.h
create mode 100644 crypto/asymmetric_keys/pgp_library.c
create mode 100644 crypto/asymmetric_keys/pgp_parser.h
create mode 100644 crypto/asymmetric_keys/pgp_preload.c
create mode 100644 crypto/asymmetric_keys/pgp_public_key.c
create mode 100644 crypto/asymmetric_keys/pgp_signature.c
create mode 100644 crypto/asymmetric_keys/pgp_test_key.c
create mode 100644 crypto/asymmetric_keys/pgplib.h
create mode 100644 include/crypto/pgp.h

--
2.32.0



2022-01-11 18:04:26

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 02/14] rsa: add parser of raw format

Parse the RSA key with RAW format if the ASN.1 parser returns an error.

Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/rsa.c | 14 +++++--
crypto/rsa_helper.c | 69 +++++++++++++++++++++++++++++++++++
include/crypto/internal/rsa.h | 6 +++
3 files changed, 85 insertions(+), 4 deletions(-)

diff --git a/crypto/rsa.c b/crypto/rsa.c
index 4cdbec95d077..ece7bafd6984 100644
--- a/crypto/rsa.c
+++ b/crypto/rsa.c
@@ -164,8 +164,11 @@ static int rsa_set_pub_key(struct crypto_akcipher *tfm, const void *key,
rsa_free_mpi_key(mpi_key);

ret = rsa_parse_pub_key(&raw_key, key, keylen);
- if (ret)
- return ret;
+ if (ret) {
+ ret = rsa_parse_pub_key_raw(&raw_key, key, keylen);
+ if (ret)
+ return ret;
+ }

mpi_key->e = mpi_read_raw_data(raw_key.e, raw_key.e_sz);
if (!mpi_key->e)
@@ -198,8 +201,11 @@ static int rsa_set_priv_key(struct crypto_akcipher *tfm, const void *key,
rsa_free_mpi_key(mpi_key);

ret = rsa_parse_priv_key(&raw_key, key, keylen);
- if (ret)
- return ret;
+ if (ret) {
+ ret = rsa_parse_priv_key_raw(&raw_key, key, keylen);
+ if (ret)
+ return ret;
+ }

mpi_key->d = mpi_read_raw_data(raw_key.d, raw_key.d_sz);
if (!mpi_key->d)
diff --git a/crypto/rsa_helper.c b/crypto/rsa_helper.c
index 94266f29049c..fb9443df8f0b 100644
--- a/crypto/rsa_helper.c
+++ b/crypto/rsa_helper.c
@@ -9,6 +9,7 @@
#include <linux/export.h>
#include <linux/err.h>
#include <linux/fips.h>
+#include <linux/mpi.h>
#include <crypto/internal/rsa.h>
#include "rsapubkey.asn1.h"
#include "rsaprivkey.asn1.h"
@@ -148,6 +149,32 @@ int rsa_get_qinv(void *context, size_t hdrlen, unsigned char tag,
return 0;
}

+typedef int (*rsa_get_func)(void *, size_t, unsigned char,
+ const void *, size_t);
+
+static int rsa_parse_key_raw(struct rsa_key *rsa_key,
+ const void *key, unsigned int key_len,
+ rsa_get_func *func, int n_func)
+{
+ unsigned int nbytes, len = key_len;
+ const void *key_ptr = key;
+ int ret, i;
+
+ for (i = 0; i < n_func; i++) {
+ ret = mpi_key_length(key_ptr, len, NULL, &nbytes);
+ if (ret < 0)
+ return ret;
+
+ ret = func[i](rsa_key, 0, 0, key_ptr + 2, nbytes);
+ if (ret < 0)
+ return ret;
+
+ key_ptr += nbytes + 2;
+ }
+
+ return (key_ptr == key + key_len) ? 0 : -EINVAL;
+}
+
/**
* rsa_parse_pub_key() - decodes the BER encoded buffer and stores in the
* provided struct rsa_key, pointers to the raw key as is,
@@ -166,6 +193,27 @@ int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key,
}
EXPORT_SYMBOL_GPL(rsa_parse_pub_key);

+/**
+ * rsa_parse_pub_key_raw() - parse the RAW key and store in the provided struct
+ * rsa_key, pointers to the raw key as is, so that
+ * the caller can copy it or MPI parse it, etc.
+ *
+ * @rsa_key: struct rsa_key key representation
+ * @key: key in RAW format
+ * @key_len: length of key
+ *
+ * Return: 0 on success or error code in case of error
+ */
+int rsa_parse_pub_key_raw(struct rsa_key *rsa_key, const void *key,
+ unsigned int key_len)
+{
+ rsa_get_func pub_func[] = {rsa_get_n, rsa_get_e};
+
+ return rsa_parse_key_raw(rsa_key, key, key_len,
+ pub_func, ARRAY_SIZE(pub_func));
+}
+EXPORT_SYMBOL_GPL(rsa_parse_pub_key_raw);
+
/**
* rsa_parse_priv_key() - decodes the BER encoded buffer and stores in the
* provided struct rsa_key, pointers to the raw key
@@ -184,3 +232,24 @@ int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key,
return asn1_ber_decoder(&rsaprivkey_decoder, rsa_key, key, key_len);
}
EXPORT_SYMBOL_GPL(rsa_parse_priv_key);
+
+/**
+ * rsa_parse_priv_key_raw() - parse the RAW key and store in the provided struct
+ * rsa_key, pointers to the raw key as is, so that
+ * the caller can copy it or MPI parse it, etc.
+ *
+ * @rsa_key: struct rsa_key key representation
+ * @key: key in RAW format
+ * @key_len: length of key
+ *
+ * Return: 0 on success or error code in case of error
+ */
+int rsa_parse_priv_key_raw(struct rsa_key *rsa_key, const void *key,
+ unsigned int key_len)
+{
+ rsa_get_func priv_func[] = {rsa_get_n, rsa_get_e, rsa_get_d};
+
+ return rsa_parse_key_raw(rsa_key, key, key_len,
+ priv_func, ARRAY_SIZE(priv_func));
+}
+EXPORT_SYMBOL_GPL(rsa_parse_priv_key_raw);
diff --git a/include/crypto/internal/rsa.h b/include/crypto/internal/rsa.h
index e870133f4b77..7141e806ceea 100644
--- a/include/crypto/internal/rsa.h
+++ b/include/crypto/internal/rsa.h
@@ -50,8 +50,14 @@ struct rsa_key {
int rsa_parse_pub_key(struct rsa_key *rsa_key, const void *key,
unsigned int key_len);

+int rsa_parse_pub_key_raw(struct rsa_key *rsa_key, const void *key,
+ unsigned int key_len);
+
int rsa_parse_priv_key(struct rsa_key *rsa_key, const void *key,
unsigned int key_len);

+int rsa_parse_priv_key_raw(struct rsa_key *rsa_key, const void *key,
+ unsigned int key_len);
+
extern struct crypto_template rsa_pkcs1pad_tmpl;
#endif
--
2.32.0


2022-01-11 18:04:50

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 09/14] KEYS: Retry asym key search with partial ID in restrict_link_by_signature()

Retry asymmetric key search in restrict_link_by_signature() to support the
case of partial IDs, provided by PGP signatures (only the last 8 bytes).

Although recently draft-ietf-openpgp-rfc4880bis-10 supports the signature
subpacket type 33, which contains the full issuer fingerprint, we cannot
rely on existing signatures to support it.

Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/restrict.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/crypto/asymmetric_keys/restrict.c b/crypto/asymmetric_keys/restrict.c
index 84cefe3b3585..f1ad1862ee9d 100644
--- a/crypto/asymmetric_keys/restrict.c
+++ b/crypto/asymmetric_keys/restrict.c
@@ -97,8 +97,14 @@ int restrict_link_by_signature(struct key *dest_keyring,
key = find_asymmetric_key(trust_keyring,
sig->auth_ids[0], sig->auth_ids[1],
false);
- if (IS_ERR(key))
- return -ENOKEY;
+ if (IS_ERR(key)) {
+ /* Retry with a partial ID. */
+ key = find_asymmetric_key(trust_keyring,
+ sig->auth_ids[0], sig->auth_ids[1],
+ true);
+ if (IS_ERR(key))
+ return -ENOKEY;
+ }

if (use_builtin_keys && !test_bit(KEY_FLAG_BUILTIN, &key->flags))
ret = -ENOKEY;
--
2.32.0


2022-01-11 18:04:50

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 10/14] KEYS: Calculate key digest and get signature of the key

Calculate the digest of the signature, according to the RFC4880 section
5.2.4, get the last suitable signature with type 0x13 (Positive
certification of a User ID and Public Key packet), and store it in the
asym_auth field of the key payload, so that it is available for validating
a restriction on a keyring.

The rationale of taking the last signature is that, if there are multiple
signatures, that would be of a different issuer (not a self-signature),
that likely has more chances to be useful for the restriction verification.
If there is one (the self-signature), that will be used.

Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/pgp_public_key.c | 81 +++++++++++++++++++++++++
1 file changed, 81 insertions(+)

diff --git a/crypto/asymmetric_keys/pgp_public_key.c b/crypto/asymmetric_keys/pgp_public_key.c
index 378ed5ff0a3a..cecd67bab4e6 100644
--- a/crypto/asymmetric_keys/pgp_public_key.c
+++ b/crypto/asymmetric_keys/pgp_public_key.c
@@ -14,6 +14,7 @@
#include <keys/asymmetric-parser.h>
#include <crypto/hash.h>
#include <crypto/public_key.h>
+#include <crypto/pgp.h>

#include "pgp_parser.h"

@@ -61,6 +62,8 @@ struct pgp_key_data_parse_context {
size_t raw_fingerprint_len;
const char *user_id;
size_t user_id_len;
+ const char *key_pkt;
+ size_t key_pkt_len;
};

/*
@@ -226,6 +229,12 @@ static int pgp_process_public_key(struct pgp_parse_context *context,
return -EBADMSG;
}

+ /* Pointer refers to data being processed. */
+ if (type == PGP_PKT_PUBLIC_KEY) {
+ ctx->key_pkt = data;
+ ctx->key_pkt_len = datalen;
+ }
+
pub = kzalloc(sizeof(struct public_key), GFP_KERNEL);
if (!pub)
return -ENOMEM;
@@ -314,6 +323,77 @@ static struct asymmetric_key_ids *pgp_key_generate_id(
return NULL;
}

+/*
+ * Calculate the digest of the signature according to the RFC4880, section
+ * 5.2.4 (packet type 0x13).
+ */
+static int pgp_key_add_sig_data(struct pgp_key_data_parse_context *ctx,
+ struct pgp_sig_verify *sig_ctx)
+{
+ loff_t offset = 0;
+ u8 *data;
+
+ if (!ctx->key_pkt_len || !ctx->user_id_len)
+ return 0;
+
+ /* 0x99 + key pkt len + key pkt + 0xb4 + user ID len + user ID */
+ data = kmalloc(1 + sizeof(u16) + ctx->key_pkt_len +
+ 1 + sizeof(u32) + ctx->user_id_len, GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data[offset++] = 0x99;
+ data[offset++] = ctx->key_pkt_len >> 8;
+ data[offset++] = ctx->key_pkt_len;
+
+ memcpy(data + offset, ctx->key_pkt, ctx->key_pkt_len);
+ offset += ctx->key_pkt_len;
+
+ if (pgp_sig_get_version(sig_ctx) == PGP_SIG_VERSION_4) {
+ data[offset++] = 0xb4;
+ data[offset++] = ctx->user_id_len >> 24;
+ data[offset++] = ctx->user_id_len >> 16;
+ data[offset++] = ctx->user_id_len >> 8;
+ data[offset++] = ctx->user_id_len;
+ }
+
+ memcpy(data + offset, ctx->user_id, ctx->user_id_len);
+ offset += ctx->user_id_len;
+
+ pgp_sig_add_data(sig_ctx, data, offset);
+ kfree(data);
+ return 0;
+}
+
+static struct public_key_signature *pgp_key_get_sig(
+ struct key_preparsed_payload *prep,
+ struct pgp_key_data_parse_context *ctx)
+{
+ struct public_key_signature *sig = NULL;
+ struct pgp_sig_verify *sig_ctx;
+ bool keep_sig = false;
+ int ret;
+
+ sig_ctx = pgp_sig_parse(prep->data, prep->datalen);
+ if (IS_ERR(sig_ctx))
+ return NULL;
+
+ ret = pgp_key_add_sig_data(ctx, sig_ctx);
+ if (ret < 0)
+ goto out;
+
+ sig = pgp_sig_get_sig(sig_ctx);
+ if (IS_ERR(sig)) {
+ sig = NULL;
+ goto out;
+ }
+
+ keep_sig = true;
+out:
+ pgp_sig_verify_cancel(sig_ctx, keep_sig);
+ return sig;
+}
+
/*
* Attempt to parse the instantiation data blob for a key as a PGP packet
* message holding a key.
@@ -372,6 +452,7 @@ static int pgp_key_parse(struct key_preparsed_payload *prep)
prep->payload.data[asym_subtype] = &public_key_subtype;
prep->payload.data[asym_key_ids] = pgp_key_generate_id(&ctx);
prep->payload.data[asym_crypto] = ctx.pub;
+ prep->payload.data[asym_auth] = pgp_key_get_sig(prep, &ctx);
prep->quotalen = 100;
return 0;

--
2.32.0


2022-01-11 18:05:08

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 13/14] KEYS: Provide a function to load keys from a PGP keyring blob

From: David Howells <[email protected]>

Provide a function to load keys from a PGP keyring blob to the built-in
keyring:

int preload_pgp_keys(const u8 *pgpdata, size_t pgpdatalen,
struct key *keyring);

Descriptions are generated from user ID notes and key fingerprints. The
keys will actually be identified by the ID calculated from the PGP data
rather than by the description, so this shouldn't be a problem.

The keys are attached to the keyring supplied.

Looking as root in /proc/keys after the built-in keyring has been loaded:

383a00c1 I------ 1 perm 1f030000 0 0 asymmetri \
Red Hat, Inc. dbeca166: PGP.DSA dbeca166 []

Thanks to Tetsuo Handa <[email protected]> for some
pointing out some errors.

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/Kconfig | 8 ++
crypto/asymmetric_keys/Makefile | 1 +
crypto/asymmetric_keys/pgp_preload.c | 110 +++++++++++++++++++++++++++
include/crypto/pgp.h | 8 +-
4 files changed, 126 insertions(+), 1 deletion(-)
create mode 100644 crypto/asymmetric_keys/pgp_preload.c

diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig
index 5f328f57dd0b..79a95bf8919a 100644
--- a/crypto/asymmetric_keys/Kconfig
+++ b/crypto/asymmetric_keys/Kconfig
@@ -126,4 +126,12 @@ config PGP_TEST_KEY

This is intended for testing the PGP parser.

+config PGP_PRELOAD
+ bool "PGP public key preloading facility"
+ depends on SYSTEM_TRUSTED_KEYRING
+ select PGP_KEY_PARSER
+ help
+ This option provides a facility for the kernel to preload PGP-wrapped
+ bundles of keys during boot to the built-in keyring.
+
endif # ASYMMETRIC_KEY_TYPE
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index 99240d11f591..daaed990376f 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -91,6 +91,7 @@ $(obj)/tpm.asn1.o: $(obj)/tpm.asn1.c $(obj)/tpm.asn1.h
# PGP handling
#
obj-$(CONFIG_PGP_LIBRARY) += pgp_library.o
+obj-$(CONFIG_PGP_PRELOAD) += pgp_preload.o

obj-$(CONFIG_PGP_KEY_PARSER) += pgp_key_parser.o
pgp_key_parser-y := \
diff --git a/crypto/asymmetric_keys/pgp_preload.c b/crypto/asymmetric_keys/pgp_preload.c
new file mode 100644
index 000000000000..0eb9ba0db3df
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp_preload.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Asymmetric key request handling
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#include <linux/module.h>
+#include <linux/key.h>
+#include <linux/err.h>
+#include <keys/asymmetric-type.h>
+
+#include "pgp_parser.h"
+
+struct preload_pgp_keys_context {
+ struct pgp_parse_context pgp;
+ key_ref_t keyring;
+ const u8 *key_start;
+ const u8 *key_end;
+ bool found_key;
+};
+
+/*
+ * Create a key.
+ */
+static void __init create_pgp_key(struct preload_pgp_keys_context *ctx)
+{
+ key_ref_t key;
+
+ key = key_create_or_update(ctx->keyring,
+ "asymmetric",
+ NULL,
+ ctx->key_start,
+ ctx->key_end - ctx->key_start,
+ ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
+ KEY_USR_VIEW | KEY_USR_READ),
+ KEY_ALLOC_NOT_IN_QUOTA |
+ KEY_ALLOC_BUILT_IN |
+ KEY_ALLOC_BYPASS_RESTRICTION);
+ if (IS_ERR(key)) {
+ pr_notice("Ignoring PGP key, error: %ld\n", PTR_ERR(key));
+ return;
+ }
+
+ pr_notice("Loaded PGP key '%s'\n",
+ key_ref_to_ptr(key)->description);
+
+ key_ref_put(key);
+}
+
+/*
+ * Extract a public key or subkey from the PGP stream.
+ */
+static int __init found_pgp_key(struct pgp_parse_context *context,
+ enum pgp_packet_tag type, u8 headerlen,
+ const u8 *data, size_t datalen)
+{
+ struct preload_pgp_keys_context *ctx =
+ container_of(context, struct preload_pgp_keys_context, pgp);
+
+ if (ctx->found_key) {
+ ctx->key_end = data - headerlen;
+ create_pgp_key(ctx);
+ }
+
+ ctx->key_start = data - headerlen;
+ ctx->found_key = true;
+ return 0;
+}
+
+/**
+ * preload_pgp_keys - Load keys from a PGP keyring blob
+ * @pgpdata: The PGP keyring blob containing the keys.
+ * @pgpdatalen: The size of the @pgpdata blob.
+ * @keyring: The keyring to add the new keys to.
+ *
+ * Preload a pack of keys from a PGP keyring blob.
+ *
+ * The keys have their descriptions generated from the user ID and fingerprint
+ * in the PGP stream. Since keys can be matched on their key IDs independently
+ * of the key description, the description is mostly irrelevant apart from the
+ * fact that keys of the same description displace one another from a keyring.
+ *
+ * The caller should override the current creds if they want the keys to be
+ * owned by someone other than the current process's owner. Keys will not be
+ * accounted towards the owner's quota.
+ *
+ * This function may only be called whilst the kernel is booting.
+ */
+int __init preload_pgp_keys(const u8 *pgpdata, size_t pgpdatalen,
+ struct key *keyring)
+{
+ struct preload_pgp_keys_context ctx;
+ int ret;
+
+ ctx.pgp.types_of_interest = (1 << PGP_PKT_PUBLIC_KEY);
+ ctx.pgp.process_packet = found_pgp_key;
+ ctx.keyring = make_key_ref(keyring, 1);
+ ctx.found_key = false;
+
+ ret = pgp_parse_packets(pgpdata, pgpdatalen, &ctx.pgp);
+ if (ret < 0)
+ return ret;
+
+ if (ctx.found_key) {
+ ctx.key_end = pgpdata + pgpdatalen;
+ create_pgp_key(&ctx);
+ }
+ return 0;
+}
diff --git a/include/crypto/pgp.h b/include/crypto/pgp.h
index a58453843dc8..fcb1c4c38a9d 100644
--- a/include/crypto/pgp.h
+++ b/include/crypto/pgp.h
@@ -1,5 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0+ */
-/* PGP signature processing
+/* PGP key and signature processing
*
* Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
* Written by David Howells ([email protected])
@@ -26,4 +26,10 @@ extern void pgp_sig_verify_cancel(struct pgp_sig_verify *ctx, bool keep_sig);
extern struct public_key_signature *pgp_sig_get_sig(struct pgp_sig_verify *ctx);
extern u8 pgp_sig_get_version(struct pgp_sig_verify *ctx);

+/*
+ * pgp_preload.c
+ */
+extern int __init preload_pgp_keys(const u8 *pgpdata, size_t pgpdatalen,
+ struct key *keyring);
+
#endif /* _CRYPTO_PGP_H */
--
2.32.0


2022-01-11 18:05:15

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 11/14] verification: introduce verify_pgp_signature()

Introduce verify_pgp_signature() to verify PGP signatures from detached
data. It will be used by fsverity and by IMA.

Signed-off-by: Roberto Sassu <[email protected]>
---
certs/system_keyring.c | 70 ++++++++++++++++++++++++++++++++++++
include/linux/verification.h | 23 ++++++++++++
2 files changed, 93 insertions(+)

diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 692365dee2bd..26a11b1dcd59 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -16,6 +16,7 @@
#include <keys/asymmetric-type.h>
#include <keys/system_keyring.h>
#include <crypto/pkcs7.h>
+#include <crypto/pgp.h>
#include "common.h"

static struct key *builtin_trusted_keys;
@@ -289,6 +290,75 @@ int verify_pkcs7_signature(const void *data, size_t len,
}
EXPORT_SYMBOL_GPL(verify_pkcs7_signature);

+#ifdef CONFIG_PGP_KEY_PARSER
+/**
+ * verify_pgp_signature - Verify a PGP-based signature on system data.
+ * @data: The data to be verified (must be provided).
+ * @len: Size of @data.
+ * @raw_pgp: The PGP message that is the signature.
+ * @pgp_len: The size of @raw_pgp.
+ * @trusted_keys: Trusted keys to use (NULL for builtin trusted keys only,
+ * (void *)1UL for all trusted keys).
+ * @usage: The use to which the key is being put.
+ * @view_content: Callback to gain access to content.
+ * @ctx: Context for callback.
+ */
+int verify_pgp_signature(const void *data, size_t len,
+ const void *raw_pgp, size_t pgp_len,
+ struct key *trusted_keys,
+ enum key_being_used_for usage,
+ int (*view_content)(void *ctx,
+ const void *data, size_t len,
+ size_t asn1hdrlen),
+ void *ctx)
+{
+ struct pgp_sig_verify *pgp_ctx;
+ int ret;
+
+ if (!data || !len)
+ return -EINVAL;
+
+ pgp_ctx = pgp_sig_parse(raw_pgp, pgp_len);
+ if (IS_ERR(pgp_ctx))
+ return PTR_ERR(pgp_ctx);
+
+ if (!trusted_keys) {
+ trusted_keys = builtin_trusted_keys;
+ } else if (trusted_keys == VERIFY_USE_SECONDARY_KEYRING) {
+#ifdef CONFIG_SECONDARY_TRUSTED_KEYRING
+ trusted_keys = secondary_trusted_keys;
+#else
+ trusted_keys = builtin_trusted_keys;
+#endif
+ } else if (trusted_keys == VERIFY_USE_PLATFORM_KEYRING) {
+#ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING
+ trusted_keys = platform_trusted_keys;
+#else
+ trusted_keys = NULL;
+#endif
+ if (!trusted_keys) {
+ ret = -ENOKEY;
+ pr_devel("PGP platform keyring is not available\n");
+ goto error;
+ }
+ }
+
+ /* The data should be detached - so we need to supply it. */
+ if (pgp_sig_add_data(pgp_ctx, data, len)) {
+ pr_err("Failed to supply data for PGP signature\n");
+ ret = -EBADMSG;
+ goto error;
+ }
+
+ ret = pgp_sig_verify(pgp_ctx, trusted_keys);
+error:
+ pgp_sig_verify_cancel(pgp_ctx, false);
+ pr_devel("<==%s() = %d\n", __func__, ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(verify_pgp_signature);
+
+#endif /* CONFIG_PGP_KEY_PARSER */
#endif /* CONFIG_SYSTEM_DATA_VERIFICATION */

#ifdef CONFIG_INTEGRITY_PLATFORM_KEYRING
diff --git a/include/linux/verification.h b/include/linux/verification.h
index a655923335ae..c27516d68962 100644
--- a/include/linux/verification.h
+++ b/include/linux/verification.h
@@ -54,6 +54,29 @@ extern int verify_pkcs7_message_sig(const void *data, size_t len,
size_t asn1hdrlen),
void *ctx);

+#ifdef CONFIG_PGP_KEY_PARSER
+extern int verify_pgp_signature(const void *data, size_t len,
+ const void *raw_pgp, size_t pgp_len,
+ struct key *trusted_keys,
+ enum key_being_used_for usage,
+ int (*view_content)(void *ctx,
+ const void *data, size_t len,
+ size_t asn1hdrlen),
+ void *ctx);
+#else
+static inline int verify_pgp_signature(const void *data, size_t len,
+ const void *raw_pgp, size_t pgp_len,
+ struct key *trusted_keys,
+ enum key_being_used_for usage,
+ int (*view_content)(void *ctx,
+ const void *data, size_t len,
+ size_t asn1hdrlen),
+ void *ctx)
+{
+ return -EOPNOTSUPP;
+}
+#endif /* CONFIG_PGP_KEY_PARSER */
+
#ifdef CONFIG_SIGNED_PE_FILE_VERIFICATION
extern int verify_pefile_signature(const void *pebuf, unsigned pelen,
struct key *trusted_keys,
--
2.32.0


2022-01-11 18:05:17

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 12/14] PGP: Provide a key type for testing PGP signatures

From: David Howells <[email protected]>

Provide a key type for testing the PGP signature parser. It is given a
non-detached PGP message as payload:

keyctl padd pgp_test a @s <content.txt.gpg

A suitable message can be generated like this:

echo "This is a test attached-signed content" >content.txt
gpg --compress-algo=none -s content.txt

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/Kconfig | 13 +++
crypto/asymmetric_keys/Makefile | 2 +
crypto/asymmetric_keys/pgp_library.c | 64 +++++++++++++
crypto/asymmetric_keys/pgp_test_key.c | 129 ++++++++++++++++++++++++++
crypto/asymmetric_keys/pgplib.h | 16 ++++
5 files changed, 224 insertions(+)
create mode 100644 crypto/asymmetric_keys/pgp_test_key.c

diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig
index 7be60ef07ac0..5f328f57dd0b 100644
--- a/crypto/asymmetric_keys/Kconfig
+++ b/crypto/asymmetric_keys/Kconfig
@@ -113,4 +113,17 @@ config PGP_KEY_PARSER
for key data and provides the ability to instantiate a crypto key
from a public key packet found inside the blob.

+config PGP_TEST_KEY
+ tristate "PGP testing key type"
+ depends on SYSTEM_DATA_VERIFICATION
+ depends on PGP_KEY_PARSER=y
+ help
+ This option provides a type of key that can be loaded up from a
+ PGP message - provided the message is signed by a trusted key. If
+ it is, the PGP 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 PGP parser.
+
endif # ASYMMETRIC_KEY_TYPE
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index e2aeb2a4b6a6..99240d11f591 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -96,3 +96,5 @@ obj-$(CONFIG_PGP_KEY_PARSER) += pgp_key_parser.o
pgp_key_parser-y := \
pgp_public_key.o \
pgp_signature.o
+
+obj-$(CONFIG_PGP_TEST_KEY) += pgp_test_key.o
diff --git a/crypto/asymmetric_keys/pgp_library.c b/crypto/asymmetric_keys/pgp_library.c
index de4a748db9be..5e5d75e99194 100644
--- a/crypto/asymmetric_keys/pgp_library.c
+++ b/crypto/asymmetric_keys/pgp_library.c
@@ -554,3 +554,67 @@ int pgp_parse_sig_params(const u8 **_data, size_t *_datalen,
return 0;
}
EXPORT_SYMBOL_GPL(pgp_parse_sig_params);
+
+#if IS_ENABLED(CONFIG_PGP_TEST_KEY)
+
+/**
+ * pgp_parse_literal_data - Parse basic params from a PGP literal data packet
+ * @data: Content of packet
+ * @datalen: Length of packet remaining
+ * @p: The basic parameters
+ *
+ * Parse the basic parameters from a PGP literal data packet [RFC 4880: 5.9]
+ * that are needed to work out what form the data is in and where it is.
+ *
+ * Returns 0 if successful or a negative error code.
+ */
+int pgp_parse_literal_data(const u8 *data, size_t datalen,
+ struct pgp_literal_data_parameters *p)
+{
+ unsigned int tmp;
+
+ pr_devel("-->%s(,%zu,,)\n", __func__, datalen);
+
+ if (datalen < 6)
+ goto too_short;
+ datalen -= 6;
+
+ p->format = *data++;
+ switch (p->format) {
+ case PGP_LIT_FORMAT_BINARY:
+ case PGP_LIT_FORMAT_TEXT:
+ case PGP_LIT_FORMAT_TEXT_UTF8:
+ break;
+ default:
+ pr_debug("Literal data packet with unhandled format %02x\n",
+ p->format);
+ return -EBADMSG;
+ }
+
+ p->filename_len = *data++;
+ p->filename_offset = 2;
+ if (datalen < p->filename_len)
+ goto too_short;
+ data += p->filename_len;
+ datalen -= p->filename_len;
+
+ tmp = *data++ << 24;
+ tmp |= *data++ << 16;
+ tmp |= *data++ << 8;
+ tmp |= *data++;
+ p->time = tmp;
+
+ p->content_offset = 6 + p->filename_len;
+ p->content_len = datalen;
+
+ pr_devel("%x,%u,%x,%u\n",
+ p->format, p->filename_len, p->time, p->content_len);
+ return 0;
+
+too_short:
+ pr_debug("Literal data packet too short\n");
+ return -EBADMSG;
+}
+EXPORT_SYMBOL_GPL(pgp_parse_literal_data);
+
+#endif /* CONFIG_PGP_TEST_KEY */
diff --git a/crypto/asymmetric_keys/pgp_test_key.c b/crypto/asymmetric_keys/pgp_test_key.c
new file mode 100644
index 000000000000..e067dedf6ca0
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp_test_key.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Testing module to load key from trusted PGP message
+ *
+ * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#define pr_fmt(fmt) "PGPtest: "fmt
+#include <linux/key.h>
+#include <linux/key-type.h>
+#include <linux/cred.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/verification.h>
+#include <keys/user-type.h>
+#include <keys/system_keyring.h>
+#include <crypto/pgp.h>
+
+#include "pgp_parser.h"
+
+MODULE_LICENSE("GPL");
+
+struct pgp_test_parse_context {
+ struct pgp_parse_context pgp;
+ struct pgp_literal_data_parameters params;
+ const void *content;
+};
+
+static int pgp_test_parse_data(struct pgp_parse_context *context,
+ enum pgp_packet_tag type,
+ u8 headerlen,
+ const u8 *data,
+ size_t datalen)
+{
+ struct pgp_test_parse_context *ctx =
+ container_of(context, struct pgp_test_parse_context, pgp);
+ int ret;
+
+ kenter("");
+
+ ret = pgp_parse_literal_data(data, datalen, &ctx->params);
+ if (ret == 0)
+ ctx->content = data + ctx->params.content_offset;
+ return ret;
+}
+
+/*
+ * Instantiate a PGP wrapped and validated key.
+ */
+static int pgp_test_instantiate(struct key *key,
+ struct key_preparsed_payload *prep)
+{
+ struct pgp_test_parse_context p;
+ const void *saved_prep_data;
+ size_t saved_prep_datalen;
+ const struct cred *cred = current_cred();
+ int ret;
+
+ kenter("");
+
+ memset(&p, 0, sizeof(p));
+ p.pgp.types_of_interest = (1 << PGP_PKT_LITERAL_DATA);
+ p.pgp.process_packet = pgp_test_parse_data;
+ ret = pgp_parse_packets(prep->data, prep->datalen, &p.pgp);
+ if (ret < 0) {
+ kleave(" = %d [parse]", ret);
+ return ret;
+ }
+
+ if (!p.params.content_len) {
+ kleave(" = -ENODATA [no literal data");
+ return -ENODATA;
+ }
+
+ ret = verify_pgp_signature(p.content, p.params.content_len,
+ prep->data, prep->datalen, NULL,
+ VERIFYING_UNSPECIFIED_SIGNATURE, NULL, NULL);
+ if (ret < 0 && cred->session_keyring) {
+ ret = verify_pgp_signature(p.content, p.params.content_len,
+ prep->data, prep->datalen,
+ cred->session_keyring,
+ VERIFYING_UNSPECIFIED_SIGNATURE,
+ NULL, NULL);
+ if (ret < 0)
+ goto error;
+
+ pr_warn("PGP message doesn't chain back to a trusted key\n");
+ }
+
+ saved_prep_data = prep->data;
+ saved_prep_datalen = prep->datalen;
+ prep->data = p.content;
+ prep->datalen = p.params.content_len;
+ ret = generic_key_instantiate(key, prep);
+ prep->data = saved_prep_data;
+ prep->datalen = saved_prep_datalen;
+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
+ */
+static struct key_type key_type_pgp_test = {
+ .name = "pgp_test",
+ .instantiate = pgp_test_instantiate,
+ .revoke = user_revoke,
+ .destroy = user_destroy,
+ .describe = user_describe,
+ .read = user_read,
+};
+
+/*
+ * Module stuff
+ */
+static int __init pgp_key_init(void)
+{
+ return register_key_type(&key_type_pgp_test);
+}
+
+static void __exit pgp_key_cleanup(void)
+{
+ unregister_key_type(&key_type_pgp_test);
+}
+
+module_init(pgp_key_init);
+module_exit(pgp_key_cleanup);
diff --git a/crypto/asymmetric_keys/pgplib.h b/crypto/asymmetric_keys/pgplib.h
index 967e2853186d..0dc3cd142469 100644
--- a/crypto/asymmetric_keys/pgplib.h
+++ b/crypto/asymmetric_keys/pgplib.h
@@ -56,3 +56,19 @@ struct pgp_sig_parameters {

extern int pgp_parse_sig_params(const u8 **_data, size_t *_datalen,
struct pgp_sig_parameters *p);
+
+#if IS_ENABLED(CONFIG_PGP_TEST_KEY)
+
+struct pgp_literal_data_parameters {
+ enum pgp_literal_data_format format : 8;
+ u8 filename_len;
+ u8 filename_offset;
+ u8 content_offset;
+ u32 content_len;
+ u32 time;
+};
+
+extern int pgp_parse_literal_data(const u8 *data, size_t datalen,
+ struct pgp_literal_data_parameters *p);
+
+#endif /* CONFIG_PGP_TEST_KEY */
--
2.32.0


2022-01-11 18:05:24

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 07/14] KEYS: Provide PGP key description autogeneration

From: David Howells <[email protected]>

Provide a facility to autogenerate the name of PGP keys from the contents
of the payload. If add_key() is given a blank description, a description
is constructed from the last user ID packet in the payload data plus the
last 8 hex digits of the key ID. For instance:

keyctl padd asymmetric "" @s </tmp/key.pub

might create a key with a constructed description that can be seen in
/proc/keys:

2f674b96 I--Q--- 1 perm 39390000 0 0 crypto \
Sample kernel key 31f0ae93: PGP.RSA 31f0ae93 []

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/pgp_public_key.c | 47 ++++++++++++++++++++++++-
1 file changed, 46 insertions(+), 1 deletion(-)

diff --git a/crypto/asymmetric_keys/pgp_public_key.c b/crypto/asymmetric_keys/pgp_public_key.c
index 2763a53ea378..378ed5ff0a3a 100644
--- a/crypto/asymmetric_keys/pgp_public_key.c
+++ b/crypto/asymmetric_keys/pgp_public_key.c
@@ -59,6 +59,8 @@ struct pgp_key_data_parse_context {
struct public_key *pub;
u8 raw_fingerprint[HASH_MAX_DIGESTSIZE];
size_t raw_fingerprint_len;
+ const char *user_id;
+ size_t user_id_len;
};

/*
@@ -210,6 +212,15 @@ static int pgp_process_public_key(struct pgp_parse_context *context,

kenter(",%u,%u,,%zu", type, headerlen, datalen);

+ if (type == PGP_PKT_USER_ID) {
+ if (!ctx->user_id_len) {
+ ctx->user_id = data;
+ ctx->user_id_len = datalen;
+ }
+ kleave(" = 0 [user ID]");
+ return 0;
+ }
+
if (ctx->raw_fingerprint_len) {
kleave(" = -ENOKEY [already]");
return -EBADMSG;
@@ -315,13 +326,47 @@ static int pgp_key_parse(struct key_preparsed_payload *prep)
kenter("");

memset(&ctx, 0, sizeof(ctx));
- ctx.pgp.types_of_interest = (1 << PGP_PKT_PUBLIC_KEY);
+ ctx.pgp.types_of_interest = (1 << PGP_PKT_PUBLIC_KEY) |
+ (1 << PGP_PKT_USER_ID);
ctx.pgp.process_packet = pgp_process_public_key;

ret = pgp_parse_packets(prep->data, prep->datalen, &ctx.pgp);
if (ret < 0)
goto error;

+ if (ctx.user_id && ctx.user_id_len > 0) {
+ /*
+ * Propose a description for the key (user ID without the
+ * comment).
+ */
+ size_t ulen = ctx.user_id_len;
+ const char *p;
+
+ p = memchr(ctx.user_id, '(', ulen);
+ if (p) {
+ /* Remove the comment */
+ do {
+ p--;
+ } while (*p == ' ' && p > ctx.user_id);
+ if (*p != ' ')
+ p++;
+ ulen = p - ctx.user_id;
+ }
+
+ if (ulen > 255 - 9)
+ ulen = 255 - 9;
+ prep->description = kmalloc(ulen + 1 + 8 + 1, GFP_KERNEL);
+ ret = -ENOMEM;
+ if (!prep->description)
+ goto error;
+ memcpy(prep->description, ctx.user_id, ulen);
+ prep->description[ulen] = ' ';
+ bin2hex(prep->description + ulen + 1,
+ ctx.raw_fingerprint + ctx.raw_fingerprint_len - 4, 4);
+ prep->description[ulen + 9] = 0;
+ pr_debug("desc '%s'\n", prep->description);
+ }
+
/* We're pinning the module by being linked against it */
__module_get(public_key_subtype.owner);
prep->payload.data[asym_subtype] = &public_key_subtype;
--
2.32.0


2022-01-11 18:05:26

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 06/14] KEYS: PGP data parser

From: David Howells <[email protected]>

Implement a PGP data parser for the crypto key type to use when
instantiating a key.

This parser attempts to parse the instantiation data as a PGP packet
sequence (RFC 4880) and if it parses okay, attempts to extract a public-key
algorithm key or subkey from it.

If it finds such a key, it will set up a public_key subtype payload with
appropriate handler routines (RSA) and attach it to the key.

Thanks to Tetsuo Handa <[email protected]> for pointing
out some errors.

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/Kconfig | 11 +
crypto/asymmetric_keys/Makefile | 4 +
crypto/asymmetric_keys/pgp_parser.h | 18 ++
crypto/asymmetric_keys/pgp_public_key.c | 358 ++++++++++++++++++++++++
4 files changed, 391 insertions(+)
create mode 100644 crypto/asymmetric_keys/pgp_parser.h
create mode 100644 crypto/asymmetric_keys/pgp_public_key.c

diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig
index 0678ede9d17e..7be60ef07ac0 100644
--- a/crypto/asymmetric_keys/Kconfig
+++ b/crypto/asymmetric_keys/Kconfig
@@ -102,4 +102,15 @@ config PGP_LIBRARY
This option enables a library that provides a number of simple
utility functions for parsing PGP (RFC 4880) packet-based messages.

+config PGP_KEY_PARSER
+ tristate "PGP key parser"
+ depends on ASYMMETRIC_PUBLIC_KEY_SUBTYPE
+ select PGP_LIBRARY
+ select MD5 # V3 fingerprint generation
+ select SHA1 # V4 fingerprint generation
+ help
+ This option provides support for parsing PGP (RFC 4880) format blobs
+ for key data and provides the ability to instantiate a crypto key
+ from a public key packet found inside the blob.
+
endif # ASYMMETRIC_KEY_TYPE
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index 55a67ebfe8e1..a68f9a5d1746 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -91,3 +91,7 @@ $(obj)/tpm.asn1.o: $(obj)/tpm.asn1.c $(obj)/tpm.asn1.h
# PGP handling
#
obj-$(CONFIG_PGP_LIBRARY) += pgp_library.o
+
+obj-$(CONFIG_PGP_KEY_PARSER) += pgp_key_parser.o
+pgp_key_parser-y := \
+ pgp_public_key.o
diff --git a/crypto/asymmetric_keys/pgp_parser.h b/crypto/asymmetric_keys/pgp_parser.h
new file mode 100644
index 000000000000..1a560ce32415
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp_parser.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* PGP crypto data parser internal definitions
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#include "pgplib.h"
+
+#define kenter(FMT, ...) \
+ pr_devel("==> %s("FMT")\n", __func__, ##__VA_ARGS__)
+#define kleave(FMT, ...) \
+ pr_devel("<== %s()"FMT"\n", __func__, ##__VA_ARGS__)
+
+/*
+ * pgp_public_key.c
+ */
+extern const char *pgp_to_public_key_algo[PGP_PUBKEY__LAST];
diff --git a/crypto/asymmetric_keys/pgp_public_key.c b/crypto/asymmetric_keys/pgp_public_key.c
new file mode 100644
index 000000000000..2763a53ea378
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp_public_key.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Instantiate a public key crypto key from PGP format data [RFC 4880]
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#define pr_fmt(fmt) "PGP: "fmt
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mpi.h>
+#include <keys/asymmetric-subtype.h>
+#include <keys/asymmetric-parser.h>
+#include <crypto/hash.h>
+#include <crypto/public_key.h>
+
+#include "pgp_parser.h"
+
+#define MAX_MPI 5
+#define KEYCTL_SUPPORTS_ENCDEC \
+ (KEYCTL_SUPPORTS_ENCRYPT | KEYCTL_SUPPORTS_DECRYPT)
+#define KEYCTL_SUPPORTS_SIGVER (KEYCTL_SUPPORTS_SIGN | KEYCTL_SUPPORTS_VERIFY)
+
+MODULE_LICENSE("GPL");
+
+const char *pgp_to_public_key_algo[PGP_PUBKEY__LAST] = {
+ [PGP_PUBKEY_RSA_ENC_OR_SIG] = "rsa",
+ [PGP_PUBKEY_RSA_ENC_ONLY] = "rsa",
+ [PGP_PUBKEY_RSA_SIG_ONLY] = "rsa",
+ [PGP_PUBKEY_ELGAMAL] = NULL,
+ [PGP_PUBKEY_DSA] = NULL,
+};
+
+static const int pgp_key_algo_p_num_mpi[PGP_PUBKEY__LAST] = {
+ [PGP_PUBKEY_RSA_ENC_OR_SIG] = 2,
+ [PGP_PUBKEY_RSA_ENC_ONLY] = 2,
+ [PGP_PUBKEY_RSA_SIG_ONLY] = 2,
+ [PGP_PUBKEY_ELGAMAL] = 3,
+ [PGP_PUBKEY_DSA] = 4,
+};
+
+static const u8 pgp_public_key_capabilities[PGP_PUBKEY__LAST] = {
+ [PGP_PUBKEY_RSA_ENC_OR_SIG] = KEYCTL_SUPPORTS_ENCDEC |
+ KEYCTL_SUPPORTS_SIGVER,
+ [PGP_PUBKEY_RSA_ENC_ONLY] = KEYCTL_SUPPORTS_ENCDEC,
+ [PGP_PUBKEY_RSA_SIG_ONLY] = KEYCTL_SUPPORTS_SIGVER,
+ [PGP_PUBKEY_ELGAMAL] = 0,
+ [PGP_PUBKEY_DSA] = 0,
+};
+
+static inline void digest_putc(struct shash_desc *digest, uint8_t ch)
+{
+ crypto_shash_update(digest, &ch, 1);
+}
+
+struct pgp_key_data_parse_context {
+ struct pgp_parse_context pgp;
+ struct public_key *pub;
+ u8 raw_fingerprint[HASH_MAX_DIGESTSIZE];
+ size_t raw_fingerprint_len;
+};
+
+/*
+ * Calculate the public key ID (RFC4880 12.2)
+ */
+static int pgp_calc_pkey_keyid(struct shash_desc *digest,
+ struct pgp_parse_pubkey *pgp,
+ struct public_key *pub)
+{
+ unsigned int nb[MAX_MPI];
+ unsigned int nn[MAX_MPI];
+ unsigned int n;
+ size_t keylen = pub->keylen;
+ u8 *key_ptr = pub->key;
+ u8 *pp[MAX_MPI];
+ u32 a32;
+ int npkey = pgp_key_algo_p_num_mpi[pgp->pubkey_algo];
+ int i, ret;
+
+ kenter("");
+
+ n = (pgp->version < PGP_KEY_VERSION_4) ? 8 : 6;
+ for (i = 0; i < npkey; i++) {
+ ret = mpi_key_length(key_ptr, keylen, nb + i, nn + i);
+ if (ret < 0)
+ return ret;
+
+ if (keylen < 2 + nn[i])
+ break;
+
+ pp[i] = key_ptr + 2;
+ key_ptr += 2 + nn[i];
+ keylen -= 2 + nn[i];
+ n += 2 + nn[i];
+ }
+
+ if (keylen != 0) {
+ pr_debug("excess %zu\n", keylen);
+ return -EBADMSG;
+ }
+
+ digest_putc(digest, 0x99); /* ctb */
+ digest_putc(digest, n >> 8); /* 16-bit header length */
+ digest_putc(digest, n);
+ digest_putc(digest, pgp->version);
+
+ a32 = pgp->creation_time;
+ digest_putc(digest, a32 >> 24);
+ digest_putc(digest, a32 >> 16);
+ digest_putc(digest, a32 >> 8);
+ digest_putc(digest, a32 >> 0);
+
+ if (pgp->version < PGP_KEY_VERSION_4) {
+ u16 a16;
+
+ if (pgp->expires_at)
+ a16 = (pgp->expires_at - pgp->creation_time) / 86400UL;
+ else
+ a16 = 0;
+ digest_putc(digest, a16 >> 8);
+ digest_putc(digest, a16 >> 0);
+ }
+
+ digest_putc(digest, pgp->pubkey_algo);
+
+ for (i = 0; i < npkey; i++) {
+ digest_putc(digest, nb[i] >> 8);
+ digest_putc(digest, nb[i]);
+ crypto_shash_update(digest, pp[i], nn[i]);
+ }
+ ret = 0;
+
+ kleave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * Calculate the public key ID fingerprint
+ */
+static int pgp_generate_fingerprint(struct pgp_key_data_parse_context *ctx,
+ struct pgp_parse_pubkey *pgp,
+ struct public_key *pub)
+{
+ struct crypto_shash *tfm;
+ struct shash_desc *digest;
+ char fingerprint[HASH_MAX_DIGESTSIZE * 2 + 1] = { 0 };
+ size_t offset;
+ int ret;
+
+ ret = -ENOMEM;
+ tfm = crypto_alloc_shash(pgp->version < PGP_KEY_VERSION_4 ?
+ "md5" : "sha1", 0, 0);
+ if (!tfm)
+ goto cleanup;
+
+ digest = kmalloc(sizeof(*digest) + crypto_shash_descsize(tfm),
+ GFP_KERNEL);
+ if (!digest)
+ goto cleanup_tfm;
+
+ digest->tfm = tfm;
+ crypto_shash_set_flags(digest->tfm, CRYPTO_TFM_REQ_MAY_SLEEP);
+ ret = crypto_shash_init(digest);
+ if (ret < 0)
+ goto cleanup_hash;
+
+ ret = pgp_calc_pkey_keyid(digest, pgp, pub);
+ if (ret < 0)
+ goto cleanup_hash;
+
+ ctx->raw_fingerprint_len = crypto_shash_digestsize(tfm);
+
+ ret = crypto_shash_final(digest, ctx->raw_fingerprint);
+ if (ret < 0)
+ goto cleanup_hash;
+
+ offset = ctx->raw_fingerprint_len - 8;
+ pr_debug("offset %lu/%lu\n", offset, ctx->raw_fingerprint_len);
+
+ bin2hex(fingerprint, ctx->raw_fingerprint, ctx->raw_fingerprint_len);
+ pr_debug("fingerprint %s\n", fingerprint);
+
+ ret = 0;
+cleanup_hash:
+ kfree(digest);
+cleanup_tfm:
+ crypto_free_shash(tfm);
+cleanup:
+ kleave(" = %d", ret);
+ return ret;
+}
+
+/*
+ * Extract a public key or public subkey from the PGP stream.
+ */
+static int pgp_process_public_key(struct pgp_parse_context *context,
+ enum pgp_packet_tag type,
+ u8 headerlen,
+ const u8 *data,
+ size_t datalen)
+{
+ const char *algo;
+ struct pgp_key_data_parse_context *ctx =
+ container_of(context, struct pgp_key_data_parse_context, pgp);
+ struct pgp_parse_pubkey pgp;
+ struct public_key *pub;
+ u8 capabilities;
+ int ret;
+
+ kenter(",%u,%u,,%zu", type, headerlen, datalen);
+
+ if (ctx->raw_fingerprint_len) {
+ kleave(" = -ENOKEY [already]");
+ return -EBADMSG;
+ }
+
+ pub = kzalloc(sizeof(struct public_key), GFP_KERNEL);
+ if (!pub)
+ return -ENOMEM;
+ pub->id_type = "PGP";
+
+ ret = pgp_parse_public_key(&data, &datalen, &pgp);
+ if (ret < 0)
+ goto cleanup;
+
+ if (pgp.pubkey_algo >= PGP_PUBKEY__LAST)
+ goto cleanup_unsupported_pkey_algo;
+ algo = pgp_to_public_key_algo[pgp.pubkey_algo];
+ if (!algo)
+ goto cleanup_unsupported_pkey_algo;
+
+ pub->pkey_algo = algo;
+
+ /*
+ * It's the public half of a key, so that only gives us encrypt and
+ * verify capabilities.
+ */
+ capabilities = pgp_public_key_capabilities[pgp.pubkey_algo] &
+ (KEYCTL_SUPPORTS_ENCRYPT | KEYCTL_SUPPORTS_VERIFY);
+ /*
+ * Capabilities are not stored anymore in the public key, store only
+ * those that allow signature verification.
+ */
+ if (!(capabilities & KEYCTL_SUPPORTS_VERIFY))
+ goto cleanup_unsupported_pkey_algo;
+
+ pub->key = kmemdup(data, datalen, GFP_KERNEL);
+ if (!pub->key)
+ goto cleanup_nomem;
+
+ pub->keylen = datalen;
+
+ ret = pgp_generate_fingerprint(ctx, &pgp, pub);
+ if (ret < 0)
+ goto cleanup;
+
+ ctx->pub = pub;
+ kleave(" = 0 [use]");
+ return 0;
+
+cleanup_unsupported_pkey_algo:
+ pr_debug("Unsupported public key algorithm %u\n",
+ pgp.pubkey_algo);
+ ret = -ENOPKG;
+ goto cleanup;
+cleanup_nomem:
+ ret = -ENOMEM;
+ goto cleanup;
+cleanup:
+ pr_devel("cleanup");
+ public_key_free(pub);
+ kleave(" = %d", ret);
+ return ret;
+}
+
+static struct asymmetric_key_ids *pgp_key_generate_id(
+ struct pgp_key_data_parse_context *ctx)
+{
+ struct asymmetric_key_ids *kids;
+ struct asymmetric_key_id *kid;
+
+ kids = kzalloc(sizeof(struct asymmetric_key_ids), GFP_KERNEL);
+ if (!kids)
+ return kids;
+
+ kid = asymmetric_key_generate_id(ctx->raw_fingerprint,
+ ctx->raw_fingerprint_len, NULL, 0);
+ if (IS_ERR(kid))
+ goto error;
+
+ kids->id[0] = kid;
+ kids->id[1] = kmemdup(kid, sizeof(*kid) + ctx->raw_fingerprint_len,
+ GFP_KERNEL);
+ if (!kids->id[1])
+ goto error;
+
+ return kids;
+error:
+ kfree(kids->id[0]);
+ kfree(kids);
+
+ return NULL;
+}
+
+/*
+ * Attempt to parse the instantiation data blob for a key as a PGP packet
+ * message holding a key.
+ */
+static int pgp_key_parse(struct key_preparsed_payload *prep)
+{
+ struct pgp_key_data_parse_context ctx;
+ int ret;
+
+ kenter("");
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.pgp.types_of_interest = (1 << PGP_PKT_PUBLIC_KEY);
+ ctx.pgp.process_packet = pgp_process_public_key;
+
+ ret = pgp_parse_packets(prep->data, prep->datalen, &ctx.pgp);
+ if (ret < 0)
+ goto error;
+
+ /* We're pinning the module by being linked against it */
+ __module_get(public_key_subtype.owner);
+ prep->payload.data[asym_subtype] = &public_key_subtype;
+ prep->payload.data[asym_key_ids] = pgp_key_generate_id(&ctx);
+ prep->payload.data[asym_crypto] = ctx.pub;
+ prep->quotalen = 100;
+ return 0;
+
+error:
+ public_key_free(ctx.pub);
+ return ret;
+}
+
+static struct asymmetric_key_parser pgp_key_parser = {
+ .owner = THIS_MODULE,
+ .name = "pgp",
+ .parse = pgp_key_parse,
+};
+
+/*
+ * Module stuff
+ */
+static int __init pgp_key_init(void)
+{
+ return register_asymmetric_key_parser(&pgp_key_parser);
+}
+
+static void __exit pgp_key_exit(void)
+{
+ unregister_asymmetric_key_parser(&pgp_key_parser);
+}
+
+module_init(pgp_key_init);
+module_exit(pgp_key_exit);
--
2.32.0


2022-01-11 18:05:34

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 03/14] PGPLIB: PGP definitions (RFC 4880)

From: David Howells <[email protected]>

Provide some useful PGP definitions from RFC 4880. These describe details
of public key crypto as used by crypto keys for things like signature
verification.

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/pgp.h | 206 +++++++++++++++++++++++++++++++++++
1 file changed, 206 insertions(+)
create mode 100644 crypto/asymmetric_keys/pgp.h

diff --git a/crypto/asymmetric_keys/pgp.h b/crypto/asymmetric_keys/pgp.h
new file mode 100644
index 000000000000..5eb4f4222090
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp.h
@@ -0,0 +1,206 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* PGP definitions (RFC 4880)
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#include <linux/types.h>
+
+struct pgp_key_ID {
+ u8 id[8];
+} __packed;
+
+struct pgp_time {
+ u8 time[4];
+} __packed;
+
+/*
+ * PGP public-key algorithm identifiers [RFC4880: 9.1]
+ */
+enum pgp_pubkey_algo {
+ PGP_PUBKEY_RSA_ENC_OR_SIG = 1,
+ PGP_PUBKEY_RSA_ENC_ONLY = 2,
+ PGP_PUBKEY_RSA_SIG_ONLY = 3,
+ PGP_PUBKEY_ELGAMAL = 16,
+ PGP_PUBKEY_DSA = 17,
+ PGP_PUBKEY__LAST
+};
+
+/*
+ * PGP symmetric-key algorithm identifiers [RFC4880: 9.2]
+ */
+enum pgp_symkey_algo {
+ PGP_SYMKEY_PLAINTEXT = 0,
+ PGP_SYMKEY_IDEA = 1,
+ PGP_SYMKEY_3DES = 2,
+ PGP_SYMKEY_CAST5 = 3,
+ PGP_SYMKEY_BLOWFISH = 4,
+ PGP_SYMKEY_AES_128KEY = 7,
+ PGP_SYMKEY_AES_192KEY = 8,
+ PGP_SYMKEY_AES_256KEY = 9,
+ PGP_SYMKEY_TWOFISH_256KEY = 10,
+};
+
+/*
+ * PGP compression algorithm identifiers [RFC4880: 9.3]
+ */
+enum pgp_compr_algo {
+ PGP_COMPR_UNCOMPRESSED = 0,
+ PGP_COMPR_ZIP = 1,
+ PGP_COMPR_ZLIB = 2,
+ PGP_COMPR_BZIP2 = 3,
+};
+
+/*
+ * PGP hash algorithm identifiers [RFC4880: 9.4]
+ */
+enum pgp_hash_algo {
+ PGP_HASH_MD5 = 1,
+ PGP_HASH_SHA1 = 2,
+ PGP_HASH_RIPE_MD_160 = 3,
+ PGP_HASH_SHA256 = 8,
+ PGP_HASH_SHA384 = 9,
+ PGP_HASH_SHA512 = 10,
+ PGP_HASH_SHA224 = 11,
+ PGP_HASH__LAST
+};
+
+extern const char *const pgp_hash_algorithms[PGP_HASH__LAST];
+
+/*
+ * PGP packet type tags [RFC4880: 4.3].
+ */
+enum pgp_packet_tag {
+ PGP_PKT_RESERVED = 0,
+ PGP_PKT_PUBKEY_ENC_SESSION_KEY = 1,
+ PGP_PKT_SIGNATURE = 2,
+ PGP_PKT_SYMKEY_ENC_SESSION_KEY = 3,
+ PGP_PKT_ONEPASS_SIGNATURE = 4,
+ PGP_PKT_SECRET_KEY = 5,
+ PGP_PKT_PUBLIC_KEY = 6,
+ PGP_PKT_SECRET_SUBKEY = 7,
+ PGP_PKT_COMPRESSED_DATA = 8,
+ PGP_PKT_SYM_ENC_DATA = 9,
+ PGP_PKT_MARKER = 10,
+ PGP_PKT_LITERAL_DATA = 11,
+ PGP_PKT_TRUST = 12,
+ PGP_PKT_USER_ID = 13,
+ PGP_PKT_PUBLIC_SUBKEY = 14,
+ PGP_PKT_USER_ATTRIBUTE = 17,
+ PGP_PKT_SYM_ENC_AND_INTEG_DATA = 18,
+ PGP_PKT_MODIFY_DETECT_CODE = 19,
+ PGP_PKT_PRIVATE_0 = 60,
+ PGP_PKT_PRIVATE_3 = 63,
+ PGP_PKT__HIGHEST = 63
+};
+
+/*
+ * Signature (tag 2) packet [RFC4880: 5.2].
+ */
+enum pgp_signature_version {
+ PGP_SIG_VERSION_3 = 3,
+ PGP_SIG_VERSION_4 = 4,
+};
+
+enum pgp_signature_type {
+ PGP_SIG_BINARY_DOCUMENT_SIG = 0x00,
+ PGP_SIG_CANONICAL_TEXT_DOCUMENT_SIG = 0x01,
+ PGP_SIG_STANDALONE_SIG = 0x02,
+ PGP_SIG_GENERAL_CERT_OF_UID_PUBKEY = 0x10,
+ PGP_SIG_PERSONAL_CERT_OF_UID_PUBKEY = 0x11,
+ PGP_SIG_CASUAL_CERT_OF_UID_PUBKEY = 0x12,
+ PGP_SIG_POSTITIVE_CERT_OF_UID_PUBKEY = 0x13,
+ PGP_SIG_SUBKEY_BINDING_SIG = 0x18,
+ PGP_SIG_PRIMARY_KEY_BINDING_SIG = 0x19,
+ PGP_SIG_DIRECTLY_ON_KEY = 0x1F,
+ PGP_SIG_KEY_REVOCATION_SIG = 0x20,
+ PGP_SIG_SUBKEY_REVOCATION_SIG = 0x28,
+ PGP_SIG_CERT_REVOCATION_SIG = 0x30,
+ PGP_SIG_TIMESTAMP_SIG = 0x40,
+ PGP_SIG_THIRD_PARTY_CONFIRM_SIG = 0x50,
+};
+
+struct pgp_signature_v3_packet {
+ enum pgp_signature_version version : 8; /* == PGP_SIG_VERSION_3 */
+ u8 length_of_hashed; /* == 5 */
+ struct {
+ enum pgp_signature_type signature_type : 8;
+ struct pgp_time creation_time;
+ } __packed hashed;
+ struct pgp_key_ID issuer;
+ enum pgp_pubkey_algo pubkey_algo : 8;
+ enum pgp_hash_algo hash_algo : 8;
+} __packed;
+
+struct pgp_signature_v4_packet {
+ enum pgp_signature_version version : 8; /* == PGP_SIG_VERSION_4 */
+ enum pgp_signature_type signature_type : 8;
+ enum pgp_pubkey_algo pubkey_algo : 8;
+ enum pgp_hash_algo hash_algo : 8;
+} __packed;
+
+/*
+ * V4 signature subpacket types [RFC4880: 5.2.3.1].
+ */
+enum pgp_sig_subpkt_type {
+ PGP_SIG_CREATION_TIME = 2,
+ PGP_SIG_EXPIRATION_TIME = 3,
+ PGP_SIG_EXPORTABLE_CERT = 4,
+ PGP_SIG_TRUST_SIG = 5,
+ PGP_SIG_REGEXP = 6,
+ PGP_SIG_REVOCABLE = 7,
+ PGP_SIG_KEY_EXPIRATION_TIME = 9,
+ PGP_SIG_PREF_SYM_ALGO = 11,
+ PGP_SIG_REVOCATION_KEY = 12,
+ PGP_SIG_ISSUER = 16,
+ PGP_SIG_NOTATION_DATA = 20,
+ PGP_SIG_PREF_HASH_ALGO = 21,
+ PGP_SIG_PREF_COMPR_ALGO = 22,
+ PGP_SIG_KEY_SERVER_PREFS = 23,
+ PGP_SIG_PREF_KEY_SERVER = 24,
+ PGP_SIG_PRIMARY_USER_ID = 25,
+ PGP_SIG_POLICY_URI = 26,
+ PGP_SIG_KEY_FLAGS = 27,
+ PGP_SIG_SIGNERS_USER_ID = 28,
+ PGP_SIG_REASON_FOR_REVOCATION = 29,
+ PGP_SIG_FEATURES = 30,
+ PGP_SIG_TARGET = 31,
+ PGP_SIG_EMBEDDED_SIG = 32,
+ PGP_SIG__LAST
+};
+
+#define PGP_SIG_SUBPKT_TYPE_CRITICAL_MASK 0x80
+
+/*
+ * Key (tag 5, 6, 7 and 14) packet
+ */
+enum pgp_key_version {
+ PGP_KEY_VERSION_2 = 2,
+ PGP_KEY_VERSION_3 = 3,
+ PGP_KEY_VERSION_4 = 4,
+};
+
+struct pgp_key_v3_packet {
+ enum pgp_key_version version : 8;
+ struct pgp_time creation_time;
+ u8 expiry[2]; /* 0 or time in days till expiry */
+ enum pgp_pubkey_algo pubkey_algo : 8;
+ u8 key_material[0];
+} __packed;
+
+struct pgp_key_v4_packet {
+ enum pgp_key_version version : 8;
+ struct pgp_time creation_time;
+ enum pgp_pubkey_algo pubkey_algo : 8;
+ u8 key_material[0];
+} __packed;
+
+/*
+ * Literal Data (tag 11) packet
+ */
+enum pgp_literal_data_format {
+ PGP_LIT_FORMAT_BINARY = 0x62,
+ PGP_LIT_FORMAT_TEXT = 0x74,
+ PGP_LIT_FORMAT_TEXT_UTF8 = 0x75,
+};
--
2.32.0


2022-01-11 18:05:38

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 04/14] PGPLIB: Basic packet parser

From: David Howells <[email protected]>

Provide a simple parser that extracts the packets from a PGP packet blob
and passes the desirous ones to the given processor function:

struct pgp_parse_context {
u64 types_of_interest;
int (*process_packet)(struct pgp_parse_context *context,
enum pgp_packet_tag type,
u8 headerlen,
const u8 *data,
size_t datalen);
};

int pgp_parse_packets(const u8 *data, size_t datalen,
struct pgp_parse_context *ctx);

This is configured on with CONFIG_PGP_LIBRARY.

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/Kconfig | 6 +
crypto/asymmetric_keys/Makefile | 5 +
crypto/asymmetric_keys/pgp_library.c | 272 +++++++++++++++++++++++++++
crypto/asymmetric_keys/pgplib.h | 33 ++++
4 files changed, 316 insertions(+)
create mode 100644 crypto/asymmetric_keys/pgp_library.c
create mode 100644 crypto/asymmetric_keys/pgplib.h

diff --git a/crypto/asymmetric_keys/Kconfig b/crypto/asymmetric_keys/Kconfig
index 1f1f004dc757..0678ede9d17e 100644
--- a/crypto/asymmetric_keys/Kconfig
+++ b/crypto/asymmetric_keys/Kconfig
@@ -96,4 +96,10 @@ config SIGNED_PE_FILE_VERIFICATION
This option provides support for verifying the signature(s) on a
signed PE binary.

+config PGP_LIBRARY
+ tristate "PGP parsing library"
+ help
+ This option enables a library that provides a number of simple
+ utility functions for parsing PGP (RFC 4880) packet-based messages.
+
endif # ASYMMETRIC_KEY_TYPE
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index 28b91adba2ae..55a67ebfe8e1 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -86,3 +86,8 @@ tpm_key_parser-y := \

$(obj)/tpm_parser.o: $(obj)/tpm.asn1.h
$(obj)/tpm.asn1.o: $(obj)/tpm.asn1.c $(obj)/tpm.asn1.h
+
+#
+# PGP handling
+#
+obj-$(CONFIG_PGP_LIBRARY) += pgp_library.o
diff --git a/crypto/asymmetric_keys/pgp_library.c b/crypto/asymmetric_keys/pgp_library.c
new file mode 100644
index 000000000000..d2c3149983d5
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp_library.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+/* PGP packet parser (RFC 4880)
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#define pr_fmt(fmt) "PGPL: "fmt
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "pgplib.h"
+
+MODULE_LICENSE("GPL");
+
+const char *const pgp_hash_algorithms[PGP_HASH__LAST] = {
+ [PGP_HASH_MD5] = "md5",
+ [PGP_HASH_SHA1] = "sha1",
+ [PGP_HASH_RIPE_MD_160] = "rmd160",
+ [PGP_HASH_SHA256] = "sha256",
+ [PGP_HASH_SHA384] = "sha384",
+ [PGP_HASH_SHA512] = "sha512",
+ [PGP_HASH_SHA224] = "sha224",
+};
+EXPORT_SYMBOL_GPL(pgp_hash_algorithms);
+
+/**
+ * pgp_parse_packet_header - Parse a PGP packet header
+ * @_data: Start of the PGP packet (updated to PGP packet data)
+ * @_datalen: Amount of data remaining in buffer (decreased)
+ * @_type: Where the packet type will be returned
+ * @_headerlen: Where the header length will be returned
+ *
+ * Parse a set of PGP packet header [RFC 4880: 4.2].
+ *
+ * Return: packet data size on success; non-zero on error. If successful,
+ * *_data and *_datalen will have been updated and *_headerlen will be set to
+ * hold the length of the packet header.
+ */
+static ssize_t pgp_parse_packet_header(const u8 **_data, size_t *_datalen,
+ enum pgp_packet_tag *_type,
+ u8 *_headerlen)
+{
+ enum pgp_packet_tag type;
+ const u8 *data = *_data;
+ size_t size, datalen = *_datalen;
+
+ pr_devel("-->%s(,%zu,,)\n", __func__, datalen);
+
+ if (datalen < 2)
+ goto short_packet;
+
+ pr_devel("pkthdr %02x, %02x\n", data[0], data[1]);
+
+ type = *data++;
+ datalen--;
+ if (!(type & 0x80)) {
+ pr_debug("Packet type does not have MSB set\n");
+ return -EBADMSG;
+ }
+ type &= ~0x80;
+
+ if (type & 0x40) {
+ /* New packet length format */
+ type &= ~0x40;
+ pr_devel("new format: t=%u\n", type);
+ switch (data[0]) {
+ case 0x00 ... 0xbf:
+ /* One-byte length */
+ size = data[0];
+ data++;
+ datalen--;
+ *_headerlen = 2;
+ break;
+ case 0xc0 ... 0xdf:
+ /* Two-byte length */
+ if (datalen < 2)
+ goto short_packet;
+ size = (data[0] - 192) * 256;
+ size += data[1] + 192;
+ data += 2;
+ datalen -= 2;
+ *_headerlen = 3;
+ break;
+ case 0xff:
+ /* Five-byte length */
+ if (datalen < 5)
+ goto short_packet;
+ size = data[1] << 24;
+ size |= data[2] << 16;
+ size |= data[3] << 8;
+ size |= data[4];
+ data += 5;
+ datalen -= 5;
+ *_headerlen = 6;
+ break;
+ default:
+ pr_debug("Partial body length packet not supported\n");
+ return -EBADMSG;
+ }
+ } else {
+ /* Old packet length format */
+ u8 length_type = type & 0x03;
+
+ type >>= 2;
+ pr_devel("old format: t=%u lt=%u\n", type, length_type);
+
+ switch (length_type) {
+ case 0:
+ /* One-byte length */
+ size = data[0];
+ data++;
+ datalen--;
+ *_headerlen = 2;
+ break;
+ case 1:
+ /* Two-byte length */
+ if (datalen < 2)
+ goto short_packet;
+ size = data[0] << 8;
+ size |= data[1];
+ data += 2;
+ datalen -= 2;
+ *_headerlen = 3;
+ break;
+ case 2:
+ /* Four-byte length */
+ if (datalen < 4)
+ goto short_packet;
+ size = data[0] << 24;
+ size |= data[1] << 16;
+ size |= data[2] << 8;
+ size |= data[3];
+ data += 4;
+ datalen -= 4;
+ *_headerlen = 5;
+ break;
+ default:
+ pr_debug("Indefinite length packet not supported\n");
+ return -EBADMSG;
+ }
+ }
+
+ pr_devel("datalen=%zu size=%zu\n", datalen, size);
+ if (datalen < size)
+ goto short_packet;
+ if (size > INT_MAX)
+ goto too_big;
+
+ *_data = data;
+ *_datalen = datalen;
+ *_type = type;
+ pr_devel("Found packet type=%u size=%zd\n", type, size);
+ return size;
+
+short_packet:
+ pr_debug("Attempt to parse short packet\n");
+ return -EBADMSG;
+too_big:
+ pr_debug("Signature subpacket size >2G\n");
+ return -EMSGSIZE;
+}
+
+/**
+ * pgp_parse_packets - Parse a set of PGP packets
+ * @data: Data to be parsed (updated)
+ * @datalen: Amount of data (updated)
+ * @ctx: Parsing context
+ *
+ * Parse a set of PGP packets [RFC 4880: 4].
+ *
+ * Return: 0 on successful parsing, a negative value otherwise
+ */
+int pgp_parse_packets(const u8 *data, size_t datalen,
+ struct pgp_parse_context *ctx)
+{
+ enum pgp_packet_tag type;
+ ssize_t pktlen;
+ u8 headerlen;
+ int ret;
+
+ while (datalen > 2) {
+ pktlen = pgp_parse_packet_header(&data, &datalen, &type,
+ &headerlen);
+ if (pktlen < 0)
+ return pktlen;
+
+ if ((ctx->types_of_interest >> type) & 1) {
+ ret = ctx->process_packet(ctx, type, headerlen,
+ data, pktlen);
+ if (ret < 0)
+ return ret;
+ }
+ data += pktlen;
+ datalen -= pktlen;
+ }
+
+ if (datalen != 0) {
+ pr_debug("Excess octets in packet stream\n");
+ return -EBADMSG;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pgp_parse_packets);
+
+/**
+ * pgp_parse_public_key - Parse the common part of a PGP pubkey packet
+ * @_data: Content of packet (updated)
+ * @_datalen: Length of packet remaining (updated)
+ * @pk: Public key data
+ *
+ * Parse the common data struct for a PGP pubkey packet [RFC 4880: 5.5.2].
+ *
+ * Return: 0 on successful parsing, a negative value otherwise
+ */
+int pgp_parse_public_key(const u8 **_data, size_t *_datalen,
+ struct pgp_parse_pubkey *pk)
+{
+ const u8 *data = *_data;
+ size_t datalen = *_datalen;
+ unsigned int tmp;
+
+ if (datalen < 12) {
+ pr_debug("Public key packet too short\n");
+ return -EBADMSG;
+ }
+
+ pk->version = *data++;
+ switch (pk->version) {
+ case PGP_KEY_VERSION_2:
+ case PGP_KEY_VERSION_3:
+ case PGP_KEY_VERSION_4:
+ break;
+ default:
+ pr_debug("Public key packet with unhandled version %d\n",
+ pk->version);
+ return -EBADMSG;
+ }
+
+ tmp = *data++ << 24;
+ tmp |= *data++ << 16;
+ tmp |= *data++ << 8;
+ tmp |= *data++;
+ pk->creation_time = tmp;
+ if (pk->version == PGP_KEY_VERSION_4) {
+ pk->expires_at = 0; /* Have to get it from the selfsignature */
+ } else {
+ unsigned short ndays;
+
+ ndays = *data++ << 8;
+ ndays |= *data++;
+ if (ndays)
+ pk->expires_at = pk->creation_time + ndays * 86400UL;
+ else
+ pk->expires_at = 0;
+ datalen -= 2;
+ }
+
+ pk->pubkey_algo = *data++;
+ datalen -= 6;
+
+ pr_devel("%x,%x,%lx,%lx\n",
+ pk->version, pk->pubkey_algo, pk->creation_time,
+ pk->expires_at);
+
+ *_data = data;
+ *_datalen = datalen;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pgp_parse_public_key);
diff --git a/crypto/asymmetric_keys/pgplib.h b/crypto/asymmetric_keys/pgplib.h
new file mode 100644
index 000000000000..d82b84179433
--- /dev/null
+++ b/crypto/asymmetric_keys/pgplib.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* PGP library definitions (RFC 4880)
+ *
+ * Copyright (C) 2012 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#include "pgp.h"
+
+/*
+ * PGP library packet parser
+ */
+struct pgp_parse_context {
+ u64 types_of_interest;
+ int (*process_packet)(struct pgp_parse_context *context,
+ enum pgp_packet_tag type,
+ u8 headerlen,
+ const u8 *data,
+ size_t datalen);
+};
+
+extern int pgp_parse_packets(const u8 *data, size_t datalen,
+ struct pgp_parse_context *ctx);
+
+struct pgp_parse_pubkey {
+ enum pgp_key_version version : 8;
+ enum pgp_pubkey_algo pubkey_algo : 8;
+ __kernel_old_time_t creation_time;
+ __kernel_old_time_t expires_at;
+};
+
+extern int pgp_parse_public_key(const u8 **_data, size_t *_datalen,
+ struct pgp_parse_pubkey *pk);
--
2.32.0


2022-01-11 18:06:34

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 05/14] PGPLIB: Signature parser

From: David Howells <[email protected]>

Provide some PGP signature parsing helpers:

(1) A function to parse V4 signature subpackets and pass the desired ones
to a processor function:

int pgp_parse_sig_subpkts(const u8 *data, size_t datalen,
struct pgp_parse_sig_context *ctx);

(2) A function to parse out basic signature parameters from any PGP
signature such that the algorithms and public key can be selected:

int pgp_parse_sig_params(const u8 **_data, size_t *_datalen,
struct pgp_sig_parameters *p);

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
crypto/asymmetric_keys/pgp_library.c | 284 +++++++++++++++++++++++++++
crypto/asymmetric_keys/pgplib.h | 25 +++
2 files changed, 309 insertions(+)

diff --git a/crypto/asymmetric_keys/pgp_library.c b/crypto/asymmetric_keys/pgp_library.c
index d2c3149983d5..de4a748db9be 100644
--- a/crypto/asymmetric_keys/pgp_library.c
+++ b/crypto/asymmetric_keys/pgp_library.c
@@ -270,3 +270,287 @@ int pgp_parse_public_key(const u8 **_data, size_t *_datalen,
return 0;
}
EXPORT_SYMBOL_GPL(pgp_parse_public_key);
+
+/**
+ * pgp_parse_sig_subpkt_header - Parse a PGP V4 signature subpacket header
+ * @_data: Start of the subpacket (updated to subpacket data)
+ * @_datalen: Amount of data remaining in buffer (decreased)
+ * @_type: Where the subpacket type will be returned
+ *
+ * Parse a PGP V4 signature subpacket header [RFC 4880: 5.2.3.1].
+ *
+ * Return: packet data size on success; non-zero on error. If successful,
+ * *_data and *_datalen will have been updated and *_headerlen will be set to
+ * hold the length of the packet header.
+ */
+static ssize_t pgp_parse_sig_subpkt_header(const u8 **_data, size_t *_datalen,
+ enum pgp_sig_subpkt_type *_type)
+{
+ enum pgp_sig_subpkt_type type;
+ const u8 *data = *_data;
+ size_t size, datalen = *_datalen;
+
+ pr_devel("-->%s(,%zu,,)\n", __func__, datalen);
+
+ if (datalen < 2)
+ goto short_subpacket;
+
+ pr_devel("subpkt hdr %02x, %02x\n", data[0], data[1]);
+
+ switch (data[0]) {
+ case 0x00 ... 0xbf:
+ /* One-byte length */
+ size = data[0];
+ data++;
+ datalen--;
+ break;
+ case 0xc0 ... 0xfe:
+ /* Two-byte length */
+ if (datalen < 3)
+ goto short_subpacket;
+ size = (data[0] - 192) * 256;
+ size += data[1] + 192;
+ data += 2;
+ datalen -= 2;
+ break;
+ case 0xff:
+ if (datalen < 6)
+ goto short_subpacket;
+ size = data[1] << 24;
+ size |= data[2] << 16;
+ size |= data[3] << 8;
+ size |= data[4];
+ data += 5;
+ datalen -= 5;
+ break;
+ }
+
+ /* The type octet is included in the size */
+ pr_devel("datalen=%zu size=%zu\n", datalen, size);
+ if (datalen < size)
+ goto short_subpacket;
+ if (size == 0)
+ goto very_short_subpacket;
+ if (size > INT_MAX)
+ goto too_big;
+
+ type = *data++ & ~PGP_SIG_SUBPKT_TYPE_CRITICAL_MASK;
+ datalen--;
+ size--;
+
+ *_data = data;
+ *_datalen = datalen;
+ *_type = type;
+ pr_devel("Found subpkt type=%u size=%zd\n", type, size);
+ return size;
+
+very_short_subpacket:
+ pr_debug("Signature subpacket size can't be zero\n");
+ return -EBADMSG;
+short_subpacket:
+ pr_debug("Attempt to parse short signature subpacket\n");
+ return -EBADMSG;
+too_big:
+ pr_debug("Signature subpacket size >2G\n");
+ return -EMSGSIZE;
+}
+
+/**
+ * pgp_parse_sig_subpkts - Parse a set of PGP V4 signatute subpackets
+ * @data: Data to be parsed (updated)
+ * @datalen: Amount of data (updated)
+ * @ctx: Parsing context
+ *
+ * Parse a set of PGP signature subpackets [RFC 4880: 5.2.3].
+ *
+ * Return: 0 on successful parsing, an error value otherwise
+ */
+static int pgp_parse_sig_subpkts(const u8 *data, size_t datalen,
+ struct pgp_parse_sig_context *ctx)
+{
+ enum pgp_sig_subpkt_type type;
+ ssize_t pktlen;
+ int ret;
+
+ pr_devel("-->%s(,%zu,,)\n", __func__, datalen);
+
+ while (datalen > 2) {
+ pktlen = pgp_parse_sig_subpkt_header(&data, &datalen, &type);
+ if (pktlen < 0)
+ return pktlen;
+ if (test_bit(type, ctx->types_of_interest)) {
+ ret = ctx->process_packet(ctx, type, data, pktlen);
+ if (ret < 0)
+ return ret;
+ }
+ data += pktlen;
+ datalen -= pktlen;
+ }
+
+ if (datalen != 0) {
+ pr_debug("Excess octets in signature subpacket stream\n");
+ return -EBADMSG;
+ }
+
+ return 0;
+}
+
+struct pgp_parse_sig_params_ctx {
+ struct pgp_parse_sig_context base;
+ struct pgp_sig_parameters *params;
+ bool got_the_issuer;
+};
+
+/*
+ * Process a V4 signature subpacket.
+ */
+static int pgp_process_sig_params_subpkt(struct pgp_parse_sig_context *context,
+ enum pgp_sig_subpkt_type type,
+ const u8 *data,
+ size_t datalen)
+{
+ struct pgp_parse_sig_params_ctx *ctx =
+ container_of(context, struct pgp_parse_sig_params_ctx, base);
+
+ if (ctx->got_the_issuer) {
+ pr_debug("V4 signature packet has multiple issuers\n");
+ return -EBADMSG;
+ }
+
+ if (datalen != 8) {
+ pr_debug("V4 signature issuer subpkt not 8 long (%zu)\n",
+ datalen);
+ return -EBADMSG;
+ }
+
+ memcpy(&ctx->params->issuer, data, 8);
+ ctx->got_the_issuer = true;
+ return 0;
+}
+
+/**
+ * pgp_parse_sig_params - Parse basic parameters from a PGP signature packet
+ * @_data: Content of packet (updated)
+ * @_datalen: Length of packet remaining (updated)
+ * @p: The basic parameters
+ *
+ * Parse the basic parameters from a PGP signature packet [RFC 4880: 5.2] that
+ * are needed to start off a signature verification operation. The only ones
+ * actually necessary are the signature type (which affects how the data is
+ * transformed) and the hash algorithm.
+ *
+ * We also extract the public key algorithm and the issuer's key ID as we'll
+ * need those to determine if we actually have the public key available. If
+ * not, then we can't verify the signature anyway.
+ *
+ * Return: 0 if successful or a negative error code. *_data and *_datalen are
+ * updated to point to the 16-bit subset of the hash value and the set of MPIs.
+ */
+int pgp_parse_sig_params(const u8 **_data, size_t *_datalen,
+ struct pgp_sig_parameters *p)
+{
+ const u8 *data = *_data;
+ size_t datalen = *_datalen;
+ int ret;
+
+ pr_devel("-->%s(,%zu,,)\n", __func__, datalen);
+
+ if (datalen < 1)
+ return -EBADMSG;
+ p->version = *data;
+
+ if (p->version == PGP_SIG_VERSION_3) {
+ const struct pgp_signature_v3_packet *v3 = (const void *)data;
+
+ if (datalen < sizeof(*v3)) {
+ pr_debug("Short V3 signature packet\n");
+ return -EBADMSG;
+ }
+ datalen -= sizeof(*v3);
+ data += sizeof(*v3);
+
+ /* V3 has everything we need in the header */
+ p->signature_type = v3->hashed.signature_type;
+ memcpy(&p->issuer, &v3->issuer, 8);
+ p->pubkey_algo = v3->pubkey_algo;
+ p->hash_algo = v3->hash_algo;
+
+ } else if (p->version == PGP_SIG_VERSION_4) {
+ const struct pgp_signature_v4_packet *v4 = (const void *)data;
+ struct pgp_parse_sig_params_ctx ctx = {
+ .base.process_packet = pgp_process_sig_params_subpkt,
+ .params = p,
+ .got_the_issuer = false,
+ };
+ size_t subdatalen;
+
+ if (datalen < sizeof(*v4) + 2 + 2 + 2) {
+ pr_debug("Short V4 signature packet\n");
+ return -EBADMSG;
+ }
+ datalen -= sizeof(*v4);
+ data += sizeof(*v4);
+
+ /* V4 has most things in the header... */
+ p->signature_type = v4->signature_type;
+ p->pubkey_algo = v4->pubkey_algo;
+ p->hash_algo = v4->hash_algo;
+
+ /*
+ * ... but we have to get the key ID from the subpackets, of
+ * which there are two sets.
+ */
+ __set_bit(PGP_SIG_ISSUER, ctx.base.types_of_interest);
+
+ subdatalen = *data++ << 8;
+ subdatalen |= *data++;
+ datalen -= 2;
+ if (subdatalen) {
+ /* Hashed subpackets */
+ pr_devel("hashed data: %zu (after %zu)\n",
+ subdatalen, sizeof(*v4));
+ if (subdatalen > datalen + 2 + 2) {
+ pr_debug("Short V4 signature packet [hdata]\n");
+ return -EBADMSG;
+ }
+ ret = pgp_parse_sig_subpkts(data, subdatalen,
+ &ctx.base);
+ if (ret < 0)
+ return ret;
+ data += subdatalen;
+ datalen -= subdatalen;
+ }
+
+ subdatalen = *data++ << 8;
+ subdatalen |= *data++;
+ datalen -= 2;
+ if (subdatalen) {
+ /* Unhashed subpackets */
+ pr_devel("unhashed data: %zu\n", subdatalen);
+ if (subdatalen > datalen + 2) {
+ pr_debug("Short V4 signature packet [udata]\n");
+ return -EBADMSG;
+ }
+ ret = pgp_parse_sig_subpkts(data, subdatalen,
+ &ctx.base);
+ if (ret < 0)
+ return ret;
+ data += subdatalen;
+ datalen -= subdatalen;
+ }
+
+ if (!ctx.got_the_issuer) {
+ pr_debug("V4 signature packet lacks issuer\n");
+ return -EBADMSG;
+ }
+ } else {
+ pr_debug("Signature packet with unhandled version %d\n",
+ p->version);
+ return -EBADMSG;
+ }
+
+ *_data = data;
+ *_datalen = datalen;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pgp_parse_sig_params);
diff --git a/crypto/asymmetric_keys/pgplib.h b/crypto/asymmetric_keys/pgplib.h
index d82b84179433..967e2853186d 100644
--- a/crypto/asymmetric_keys/pgplib.h
+++ b/crypto/asymmetric_keys/pgplib.h
@@ -31,3 +31,28 @@ struct pgp_parse_pubkey {

extern int pgp_parse_public_key(const u8 **_data, size_t *_datalen,
struct pgp_parse_pubkey *pk);
+
+struct pgp_parse_sig_context {
+ unsigned long types_of_interest[128 / BITS_PER_LONG];
+ int (*process_packet)(struct pgp_parse_sig_context *context,
+ enum pgp_sig_subpkt_type type,
+ const u8 *data,
+ size_t datalen);
+};
+
+extern int pgp_parse_sig_packets(const u8 *data, size_t datalen,
+ struct pgp_parse_sig_context *ctx);
+
+struct pgp_sig_parameters {
+ enum pgp_signature_version version : 8;
+ enum pgp_signature_type signature_type : 8;
+ enum pgp_pubkey_algo pubkey_algo : 8;
+ enum pgp_hash_algo hash_algo : 8;
+ union {
+ struct pgp_key_ID issuer;
+ __be32 issuer32[2];
+ };
+};
+
+extern int pgp_parse_sig_params(const u8 **_data, size_t *_datalen,
+ struct pgp_sig_parameters *p);
--
2.32.0


2022-01-11 18:06:48

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 08/14] KEYS: PGP-based public key signature verification

From: David Howells <[email protected]>

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 that
the patch introducing verify_pgp_signature() provides.

Signed-off-by: David Howells <[email protected]>
Co-developed-by: Roberto Sassu <[email protected]>
Signed-off-by: Roberto Sassu <[email protected]>
---
MAINTAINERS | 1 +
crypto/asymmetric_keys/Makefile | 3 +-
crypto/asymmetric_keys/pgp_signature.c | 507 +++++++++++++++++++++++++
include/crypto/pgp.h | 29 ++
4 files changed, 539 insertions(+), 1 deletion(-)
create mode 100644 crypto/asymmetric_keys/pgp_signature.c
create mode 100644 include/crypto/pgp.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 306de106f31b..844ed460da59 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3025,6 +3025,7 @@ L: [email protected]
S: Maintained
F: Documentation/crypto/asymmetric-keys.rst
F: crypto/asymmetric_keys/
+F: include/crypto/pgp.h
F: include/crypto/pkcs7.h
F: include/crypto/public_key.h
F: include/linux/verification.h
diff --git a/crypto/asymmetric_keys/Makefile b/crypto/asymmetric_keys/Makefile
index a68f9a5d1746..e2aeb2a4b6a6 100644
--- a/crypto/asymmetric_keys/Makefile
+++ b/crypto/asymmetric_keys/Makefile
@@ -94,4 +94,5 @@ obj-$(CONFIG_PGP_LIBRARY) += pgp_library.o

obj-$(CONFIG_PGP_KEY_PARSER) += pgp_key_parser.o
pgp_key_parser-y := \
- pgp_public_key.o
+ pgp_public_key.o \
+ pgp_signature.o
diff --git a/crypto/asymmetric_keys/pgp_signature.c b/crypto/asymmetric_keys/pgp_signature.c
new file mode 100644
index 000000000000..4aa71559bcf4
--- /dev/null
+++ b/crypto/asymmetric_keys/pgp_signature.c
@@ -0,0 +1,507 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* PGP public key signature verification [RFC 4880]
+ *
+ * Copyright (C) 2011 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#define pr_fmt(fmt) "PGPSIG: "fmt
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mpi.h>
+#include <linux/sched.h>
+#include <linux/cred.h>
+#include <linux/key.h>
+#include <linux/err.h>
+#include <keys/asymmetric-type.h>
+#include <crypto/public_key.h>
+#include <crypto/hash.h>
+#include <crypto/pgp.h>
+
+#include "pgp_parser.h"
+
+static const struct {
+ enum hash_algo algo : 8;
+} pgp_pubkey_hash[PGP_HASH__LAST] = {
+ [PGP_HASH_MD5].algo = HASH_ALGO_MD5,
+ [PGP_HASH_SHA1].algo = HASH_ALGO_SHA1,
+ [PGP_HASH_RIPE_MD_160].algo = HASH_ALGO_RIPE_MD_160,
+ [PGP_HASH_SHA256].algo = HASH_ALGO_SHA256,
+ [PGP_HASH_SHA384].algo = HASH_ALGO_SHA384,
+ [PGP_HASH_SHA512].algo = HASH_ALGO_SHA512,
+ [PGP_HASH_SHA224].algo = HASH_ALGO_SHA224,
+};
+
+struct pgp_current_pkt {
+ enum pgp_packet_tag type;
+ u8 headerlen;
+ const u8 *data;
+ size_t datalen;
+};
+
+struct pgp_sig_verify {
+ enum pgp_signature_version sig_version : 8;
+ struct public_key_signature *sig;
+ u8 signed_hash_msw[2];
+ struct shash_desc *hash;
+ struct pgp_current_pkt pkt;
+};
+
+/*
+ * Find a key in the given keyring by issuer and authority.
+ */
+static struct key *pgp_request_asymmetric_key(struct pgp_sig_verify *ctx,
+ struct key *keyring)
+{
+ struct key *key;
+ __be32 *issuer32;
+ char id[12];
+
+ issuer32 = (__be32 *)ctx->sig->auth_ids[0]->data;
+ snprintf(id, sizeof(id), "id:%08x%08x", be32_to_cpu(issuer32[0]),
+ be32_to_cpu(issuer32[1]));
+
+ kenter(",,%s", id);
+
+ pr_debug("Look up key: \"%s\"\n", id);
+
+ key = find_asymmetric_key(keyring, ctx->sig->auth_ids[0],
+ ctx->sig->auth_ids[1], true);
+ if (IS_ERR(key)) {
+ pr_debug("Request for public key '%08x%08x' err %ld\n",
+ issuer32[0], issuer32[1], PTR_ERR(key));
+
+ switch (PTR_ERR(key)) {
+ /* Hide some search errors */
+ case -EACCES:
+ case -ENOTDIR:
+ case -EAGAIN:
+ kleave(" = -ENOKEY");
+ return ERR_PTR(-ENOKEY);
+ default:
+ kleave(" = %ld", PTR_ERR(key));
+ return ERR_CAST(key);
+ }
+ }
+
+ kleave(" = 0 [%x]", key_serial(key));
+ return key;
+}
+
+struct pgp_sig_parse_context {
+ struct pgp_parse_context pgp;
+ struct pgp_sig_parameters params;
+ struct pgp_current_pkt pkt;
+};
+
+static int pgp_parse_signature(struct pgp_parse_context *context,
+ enum pgp_packet_tag type,
+ u8 headerlen,
+ const u8 *data,
+ size_t datalen)
+{
+ struct pgp_sig_parse_context *ctx =
+ container_of(context, struct pgp_sig_parse_context, pgp);
+ struct pgp_sig_parameters tmp_params;
+ struct pgp_current_pkt tmp_pkt = { type, headerlen, data, datalen};
+ int ret;
+
+ ret = pgp_parse_sig_params(&data, &datalen, &tmp_params);
+ if (ret < 0)
+ return ret;
+
+ if (tmp_params.signature_type != PGP_SIG_BINARY_DOCUMENT_SIG &&
+ tmp_params.signature_type != PGP_SIG_STANDALONE_SIG &&
+ tmp_params.signature_type != PGP_SIG_POSTITIVE_CERT_OF_UID_PUBKEY)
+ return 0;
+
+ memcpy(&ctx->params, &tmp_params, sizeof(tmp_params));
+ memcpy(&ctx->pkt, &tmp_pkt, sizeof(tmp_pkt));
+ return 0;
+}
+
+/**
+ * pgp_sig_parse - Begin the process of verifying a signature
+ * @sigdata: Signature blob
+ * @siglen: Length of signature blob
+ *
+ * This involves allocating the hash into which first the data and then the
+ * metadata will be put, and parsing the signature to get the issuer ID from
+ * which the key used to verify the signature will be searched.
+ *
+ * Return: a PGP sig context pointer on success, an error pointer on error
+ */
+struct pgp_sig_verify *pgp_sig_parse(const u8 *sigdata, size_t siglen)
+{
+ struct pgp_sig_parse_context p;
+ struct pgp_sig_verify *ctx;
+ struct crypto_shash *tfm;
+ const char *pkey_algo;
+ size_t digest_size, desc_size;
+ int ret;
+
+ kenter(",,%zu", siglen);
+
+ p.pgp.types_of_interest = (1 << PGP_PKT_SIGNATURE);
+ p.pgp.process_packet = pgp_parse_signature;
+ p.pkt.data = NULL;
+ ret = pgp_parse_packets(sigdata, siglen, &p.pgp);
+ if (ret < 0) {
+ kleave(" = ERR_PTR [bad pkt]");
+ return ERR_PTR(ret);
+ }
+
+ if (!p.pkt.data) {
+ kleave(" = ERR_PTR [no pkt]");
+ return ERR_PTR(-ENOENT);
+ }
+
+ /* Check the signature itself for usefulness */
+ if (p.params.pubkey_algo >= PGP_PUBKEY__LAST)
+ goto unsupported_pkey_algo;
+ pkey_algo = pgp_to_public_key_algo[p.params.pubkey_algo];
+ if (!pkey_algo)
+ goto unsupported_pkey_algo;
+
+ if (p.params.hash_algo >= PGP_HASH__LAST ||
+ !pgp_hash_algorithms[p.params.hash_algo]) {
+ pr_debug("Unsupported hash algorithm %u\n",
+ p.params.hash_algo);
+ kleave(" = -ENOPKG [unsupp hash algo]");
+ return ERR_PTR(-ENOPKG);
+ }
+
+ pr_debug("Signature generated with %s hash\n",
+ pgp_hash_algorithms[p.params.hash_algo]);
+
+ /* 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)) {
+ ret = (PTR_ERR(tfm) == -ENOENT ? -ENOPKG : PTR_ERR(tfm));
+ kleave(" = %d", ret);
+ return ERR_PTR(ret);
+ }
+
+ 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.
+ */
+ ctx = kzalloc(sizeof(*ctx) + sizeof(struct shash_desc) + desc_size,
+ GFP_KERNEL);
+ if (!ctx) {
+ ret = -ENOMEM;
+ goto error_have_shash;
+ }
+
+ ctx->sig = kzalloc(sizeof(*ctx->sig), GFP_KERNEL);
+ if (!ctx->sig) {
+ ret = -ENOMEM;
+ goto error_have_ctx;
+ }
+
+ ctx->sig->auth_ids[0] = asymmetric_key_generate_id(p.params.issuer.id,
+ sizeof(p.params.issuer.id), "", 0);
+ if (IS_ERR(ctx->sig->auth_ids[0])) {
+ ret = -ENOMEM;
+ goto error_have_ctx_sig;
+ }
+
+ ctx->sig->encoding = "pkcs1";
+ ctx->sig->pkey_algo = pkey_algo;
+ ctx->sig->hash_algo = pgp_hash_algorithms[p.params.hash_algo];
+ ctx->sig->digest_size = digest_size;
+ ctx->hash = (struct shash_desc *)((void *)ctx +
+ sizeof(*ctx));
+ ctx->hash->tfm = tfm;
+ ctx->sig_version = p.params.version;
+
+ memcpy(&ctx->pkt, &p.pkt, sizeof(p.pkt));
+
+ ret = crypto_shash_init(ctx->hash);
+ if (ret < 0)
+ goto error_have_auth_ids;
+
+ kleave(" = %p", ctx);
+ return ctx;
+
+error_have_auth_ids:
+ kfree(ctx->sig->auth_ids[0]);
+error_have_ctx_sig:
+ kfree(ctx->sig);
+error_have_ctx:
+ kfree(ctx);
+error_have_shash:
+ crypto_free_shash(tfm);
+ kleave(" = %d", ret);
+ return ERR_PTR(ret);
+
+unsupported_pkey_algo:
+ pr_debug("Unsupported public key algorithm %u\n",
+ p.params.pubkey_algo);
+ kleave(" = -ENOPKG [unsupp pk algo]");
+ return ERR_PTR(-ENOPKG);
+}
+
+/*
+ * Load data into the hash
+ */
+int pgp_sig_add_data(struct pgp_sig_verify *ctx, const void *data,
+ size_t datalen)
+{
+ return crypto_shash_update(ctx->hash, data, datalen);
+}
+
+/*
+ * Extract required metadata from the signature packet and add what we need to
+ * the hash; finalise the hash.
+ */
+static int pgp_digest_signature(struct pgp_sig_verify *ctx)
+{
+ enum pgp_signature_version version;
+ unsigned int nbytes, nbytes_alloc;
+ enum pgp_packet_tag type = ctx->pkt.type;
+ const u8 *data = ctx->pkt.data;
+ size_t datalen = ctx->pkt.datalen;
+ int ret;
+
+ kenter(",%u,,%zu", type, datalen);
+
+ if (ctx->sig->digest) {
+ kleave(" = 0 [digest found]");
+ return 0;
+ }
+
+ version = *data;
+ if (version == PGP_SIG_VERSION_3) {
+ /* We just include an excerpt of the metadata from a V3
+ * signature.
+ */
+ crypto_shash_update(ctx->hash, data + 2, 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->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->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->signed_hash_msw[0] = *data++;
+ ctx->signed_hash_msw[1] = *data++;
+ datalen -= 2;
+
+ /* And then the cryptographic data, which we'll need for the
+ * algorithm.
+ */
+ ret = mpi_key_length(data, datalen, NULL, &nbytes);
+ if (ret < 0) {
+ kleave(" = -EBADMSG [key length]");
+ return ret;
+ }
+
+ if (datalen != nbytes + 2) {
+ kleave(" = -EBADMSG [size mismatch]");
+ return -EBADMSG;
+ }
+
+ nbytes_alloc = DIV_ROUND_UP(nbytes, 8) * 8;
+
+ ctx->sig->s = kzalloc(nbytes_alloc, GFP_KERNEL);
+ if (!ctx->sig->s) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ memcpy(ctx->sig->s + nbytes_alloc - nbytes, data + 2, nbytes);
+ ctx->sig->s_size = nbytes_alloc;
+
+ ctx->sig->digest = kmalloc(ctx->sig->digest_size, GFP_KERNEL);
+ if (!ctx->sig->digest) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = crypto_shash_final(ctx->hash, ctx->sig->digest);
+ if (ret < 0)
+ goto out;
+
+ pr_debug("hash: %*phN\n", ctx->sig->digest_size, ctx->sig->digest);
+out:
+ kleave(" = %d", ret);
+ return ret;
+}
+
+/**
+ * pgp_sig_get_digest - Finalize digest calculation
+ * @ctx: PGP sig verification context to use
+ * @buf: Buffer digest is written to
+ * @len: Buffer length
+ * @hash_algo: Digest algorithm
+ *
+ * Copy the calculated digest, length and algorithm to the destinations provided
+ * by the caller.
+ *
+ * Return: 0 on success, a negative value on error
+ */
+int pgp_sig_get_digest(struct pgp_sig_verify *ctx, const u8 **buf, u32 *len,
+ enum hash_algo *hash_algo)
+{
+ int ret, i;
+
+ kenter("");
+
+ ret = pgp_digest_signature(ctx);
+ if (ret < 0)
+ goto out;
+
+ i = match_string(hash_algo_name, HASH_ALGO__LAST,
+ ctx->sig->hash_algo);
+ if (i < 0) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ *hash_algo = i;
+ *buf = ctx->sig->digest;
+ *len = ctx->sig->digest_size;
+out:
+ kleave(" = %d", ret);
+ return ret;
+}
+
+/**
+ * pgp_sig_verify - Verify the PGP signature
+ * @ctx: PGP sig verification context to use
+ * @keyring: Keyring containing the key for signature verification
+ *
+ * Search the key to be used for signature verification, and verify the PGP
+ * signature.
+ *
+ * Return: 0 if the signature is valid, a negative value otherwise
+ */
+int pgp_sig_verify(struct pgp_sig_verify *ctx, struct key *keyring)
+{
+ const struct public_key *pub;
+ struct key *key;
+ int ret;
+
+ kenter("");
+
+ ret = pgp_digest_signature(ctx);
+ if (ret < 0)
+ goto out;
+
+ if (ctx->sig->digest[0] != ctx->signed_hash_msw[0] ||
+ ctx->sig->digest[1] != ctx->signed_hash_msw[1]) {
+ pr_err("Hash (%02x%02x) mismatch against quick check (%02x%02x)\n",
+ ctx->sig->digest[0], ctx->sig->digest[1],
+ ctx->signed_hash_msw[0], ctx->signed_hash_msw[1]);
+ ret = -EKEYREJECTED;
+ goto out;
+ }
+
+ /* Now we need to find a key to use */
+ key = pgp_request_asymmetric_key(ctx, keyring);
+ if (IS_ERR(key)) {
+ ret = PTR_ERR(key);
+ goto out;
+ }
+
+ pub = key->payload.data[asym_crypto];
+
+ if (strcmp(ctx->sig->pkey_algo, pub->pkey_algo)) {
+ ret = -EKEYREJECTED;
+ goto out_key;
+ }
+
+ ret = verify_signature(key, ctx->sig);
+out_key:
+ key_put(key);
+out:
+ kleave(" = %d", ret);
+ return ret;
+}
+
+/**
+ * pgp_sig_verify_cancel - End the PGP signature verification
+ * @ctx: PGP sig verification context to use
+ * @keep_sig: Don't deallocate the signature
+ *
+ * Free the memory used for the signature verification.
+ */
+void pgp_sig_verify_cancel(struct pgp_sig_verify *ctx, bool keep_sig)
+{
+ kenter("");
+
+ crypto_free_shash(ctx->hash->tfm);
+ if (!keep_sig)
+ public_key_signature_free(ctx->sig);
+
+ kfree(ctx);
+
+ kleave("");
+}
+
+/**
+ * pgp_sig_get_sig - Return the PGP signature
+ * @ctx: PGP sig verification context to use
+ *
+ * Finalize the signature by calculating the digest if not already done. Then,
+ * return the PGP signature to the caller.
+ *
+ * Return: the PGP signature if successfully finalized, an error pointer
+ * otherwise
+ */
+struct public_key_signature *pgp_sig_get_sig(struct pgp_sig_verify *ctx)
+{
+ int ret;
+
+ ret = pgp_digest_signature(ctx);
+ if (ret < 0)
+ return ERR_PTR(-ENOENT);
+
+ return ctx->sig;
+}
+
+/**
+ * pgp_sig_get_version - Return the PGP signature version
+ * @ctx: PGP sig verification context to use
+ *
+ * Return the version of the PGP signature to the caller.
+ *
+ * Return: the PGP signature version
+ */
+u8 pgp_sig_get_version(struct pgp_sig_verify *ctx)
+{
+ return ctx->sig_version;
+}
diff --git a/include/crypto/pgp.h b/include/crypto/pgp.h
new file mode 100644
index 000000000000..a58453843dc8
--- /dev/null
+++ b/include/crypto/pgp.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/* PGP signature processing
+ *
+ * Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells ([email protected])
+ */
+
+#ifndef _CRYPTO_PGP_H
+#define _CRYPTO_PGP_H
+
+#include <crypto/hash_info.h>
+
+struct key;
+struct pgp_sig_verify;
+
+/*
+ * pgp_signature.c
+ */
+extern struct pgp_sig_verify *pgp_sig_parse(const u8 *sigdata, size_t siglen);
+extern int pgp_sig_add_data(struct pgp_sig_verify *ctx,
+ const void *data, size_t datalen);
+extern int pgp_sig_get_digest(struct pgp_sig_verify *ctx, const u8 **buf,
+ u32 *len, enum hash_algo *hash_algo);
+extern int pgp_sig_verify(struct pgp_sig_verify *ctx, struct key *keyring);
+extern void pgp_sig_verify_cancel(struct pgp_sig_verify *ctx, bool keep_sig);
+extern struct public_key_signature *pgp_sig_get_sig(struct pgp_sig_verify *ctx);
+extern u8 pgp_sig_get_version(struct pgp_sig_verify *ctx);
+
+#endif /* _CRYPTO_PGP_H */
--
2.32.0


2022-01-11 18:06:50

by Roberto Sassu

[permalink] [raw]
Subject: [PATCH 14/14] KEYS: Introduce load_pgp_public_keyring()

Preload PGP keys from 'pubring.gpg', placed in certs/ of the kernel source
directory.

Signed-off-by: Roberto Sassu <[email protected]>
---
certs/Kconfig | 11 +++++++++++
certs/Makefile | 7 +++++++
certs/system_certificates.S | 18 ++++++++++++++++++
certs/system_keyring.c | 21 +++++++++++++++++++++
4 files changed, 57 insertions(+)

diff --git a/certs/Kconfig b/certs/Kconfig
index ae7f2e876a31..2f7fa68cd958 100644
--- a/certs/Kconfig
+++ b/certs/Kconfig
@@ -126,4 +126,15 @@ config SYSTEM_REVOCATION_KEYS
containing X.509 certificates to be included in the default blacklist
keyring.

+config PGP_PRELOAD_PUBLIC_KEYS
+ bool "Preload PGP public keys"
+ depends on SYSTEM_TRUSTED_KEYRING
+ select PGP_PRELOAD
+ default n
+ help
+ Load at boot time the PGP public keys from a reserved area (populated
+ with the content of 'certs/pubring.gpg' provided at kernel build
+ time), and add them to the built-in keyring. Invalid keys are ignored
+ and the loading continues.
+
endmenu
diff --git a/certs/Makefile b/certs/Makefile
index 279433783b10..c85e0ff560ca 100644
--- a/certs/Makefile
+++ b/certs/Makefile
@@ -22,6 +22,13 @@ $(obj)/system_certificates.o: $(obj)/x509_certificate_list
# Cope with signing_key.x509 existing in $(srctree) not $(objtree)
AFLAGS_system_certificates.o := -I$(srctree)

+ifdef CONFIG_PGP_PRELOAD_PUBLIC_KEYS
+ifeq ($(shell ls $(srctree)/certs/pubring.gpg 2> /dev/null), $(srctree)/certs/pubring.gpg)
+AFLAGS_system_certificates.o += -DHAVE_PUBRING_GPG
+$(obj)/system_certificates.o: $(srctree)/certs/pubring.gpg
+endif
+endif
+
quiet_cmd_extract_certs = EXTRACT_CERTS $(patsubst "%",%,$(2))
cmd_extract_certs = scripts/extract-cert $(2) $@

diff --git a/certs/system_certificates.S b/certs/system_certificates.S
index e1645e6f4d97..03b361bec758 100644
--- a/certs/system_certificates.S
+++ b/certs/system_certificates.S
@@ -47,3 +47,21 @@ module_cert_size:
#else
.long __module_cert_end - __module_cert_start
#endif
+
+ .align 8
+ .globl pgp_public_keys
+pgp_public_keys:
+__pgp_key_list_start:
+#ifdef HAVE_PUBRING_GPG
+ .incbin "certs/pubring.gpg"
+#endif
+__pgp_key_list_end:
+
+ .align 8
+ .globl pgp_public_keys_size
+pgp_public_keys_size:
+#ifdef CONFIG_64BIT
+ .quad __pgp_key_list_end - __pgp_key_list_start
+#else
+ .long __pgp_key_list_end - __pgp_key_list_start
+#endif
diff --git a/certs/system_keyring.c b/certs/system_keyring.c
index 26a11b1dcd59..1612fb97a652 100644
--- a/certs/system_keyring.c
+++ b/certs/system_keyring.c
@@ -167,6 +167,27 @@ static __init int load_system_certificate_list(void)
}
late_initcall(load_system_certificate_list);

+#ifdef CONFIG_PGP_PRELOAD_PUBLIC_KEYS
+extern __initconst const u8 pgp_public_keys[];
+extern __initconst const unsigned long pgp_public_keys_size;
+
+/*
+ * Load a list of PGP keys.
+ */
+static __init int load_pgp_public_keyring(void)
+{
+ pr_notice("Load PGP public keys\n");
+
+ if (preload_pgp_keys(pgp_public_keys,
+ pgp_public_keys_size,
+ builtin_trusted_keys) < 0)
+ pr_err("Can't load PGP public keys\n");
+
+ return 0;
+}
+late_initcall(load_pgp_public_keyring);
+#endif /* CONFIG_PGP_PRELOAD_PUBLIC_KEYS */
+
#ifdef CONFIG_SYSTEM_DATA_VERIFICATION

/**
--
2.32.0


2022-01-11 20:33:33

by Maciej S. Szmigiero

[permalink] [raw]
Subject: Re: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

On 11.01.2022 19:03, Roberto Sassu wrote:
> Support for PGP keys and signatures was proposed by David long time ago,
> before the decision of using PKCS#7 for kernel modules signatures
> verification was made. After that, there has been not enough interest to
> support PGP too.
>
> Lately, when discussing a proposal of introducing fsverity signatures in
> Fedora [1], developers expressed their preference on not having a separate
> key for signing, which would complicate the management of the distribution.
> They would be more in favor of using the same PGP key, currently used for
> signing RPM headers, also for file-based signatures (not only fsverity, but
> also IMA ones).

Aren't PGP keys simply RSA / ECC / EdDSA keys with additional metadata?
Can't they be unwrapped from their (complex) PGP format in userspace and
loaded raw into the kernel, in a similar way as they are sometimes used
for SSH authentication?

This will save us from having to add complex parsers (a well-known source
of bugs) into the kernel - I guess there aren't any plans to add an
in-kernel PGP Web of Trust implementation.

Thanks,
Maciej

2022-01-12 09:16:28

by Roberto Sassu

[permalink] [raw]
Subject: RE: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

> From: Maciej S. Szmigiero [mailto:[email protected]]
> Sent: Tuesday, January 11, 2022 9:33 PM
> On 11.01.2022 19:03, Roberto Sassu wrote:
> > Support for PGP keys and signatures was proposed by David long time ago,
> > before the decision of using PKCS#7 for kernel modules signatures
> > verification was made. After that, there has been not enough interest to
> > support PGP too.
> >
> > Lately, when discussing a proposal of introducing fsverity signatures in
> > Fedora [1], developers expressed their preference on not having a separate
> > key for signing, which would complicate the management of the distribution.
> > They would be more in favor of using the same PGP key, currently used for
> > signing RPM headers, also for file-based signatures (not only fsverity, but
> > also IMA ones).
>
> Aren't PGP keys simply RSA / ECC / EdDSA keys with additional metadata?
> Can't they be unwrapped from their (complex) PGP format in userspace and
> loaded raw into the kernel, in a similar way as they are sometimes used
> for SSH authentication?

Probably, this would be possible by introducing a new asymmetric
key subtype parsing PGP keys and signatures in a more simple format,
after conversion by user space. But still, a parser would be required.
To be honest, I would prefer to implement (actually David did) a
parser following an RFC, than developing a new one.

> This will save us from having to add complex parsers (a well-known source
> of bugs) into the kernel - I guess there aren't any plans to add an
> in-kernel PGP Web of Trust implementation.

I extensively tested the implementation with an ad-hoc fault injector,
to see if the code can correctly handle errors. I also developed a
fuzzer to corrupt the data before it is read by the kernel. Finally,
I checked that there are not memory leaks. But I agree, there could
still be bugs.

If you mean that a key can be added to the kernel if is vouched for
by another key in the built-in keyring, I actually implemented this
(was missing in the original implementation). Some keyrings, e.g. .ima,
have this restriction.

The way this works is that, whenever you add a PGP key to the
kernel, the parser takes not only the public key and the user ID,
but also its signature by the same or another PGP key.

The signature is verified when the key is added to the keyring
with that restriction, and only if the verification is successful
the key can be added.

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Zhong Ronghua

> Thanks,
> Maciej

2022-01-12 20:15:57

by Maciej S. Szmigiero

[permalink] [raw]
Subject: Re: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

On 12.01.2022 10:16, Roberto Sassu wrote:
>> From: Maciej S. Szmigiero [mailto:[email protected]]
>> Sent: Tuesday, January 11, 2022 9:33 PM
>> On 11.01.2022 19:03, Roberto Sassu wrote:
>>> Support for PGP keys and signatures was proposed by David long time ago,
>>> before the decision of using PKCS#7 for kernel modules signatures
>>> verification was made. After that, there has been not enough interest to
>>> support PGP too.
>>>
>>> Lately, when discussing a proposal of introducing fsverity signatures in
>>> Fedora [1], developers expressed their preference on not having a separate
>>> key for signing, which would complicate the management of the distribution.
>>> They would be more in favor of using the same PGP key, currently used for
>>> signing RPM headers, also for file-based signatures (not only fsverity, but
>>> also IMA ones).
>>
>> Aren't PGP keys simply RSA / ECC / EdDSA keys with additional metadata?
>> Can't they be unwrapped from their (complex) PGP format in userspace and
>> loaded raw into the kernel, in a similar way as they are sometimes used
>> for SSH authentication?
>
> Probably, this would be possible by introducing a new asymmetric
> key subtype parsing PGP keys and signatures in a more simple format,
> after conversion by user space. But still, a parser would be required.
> To be honest, I would prefer to implement (actually David did) a
> parser following an RFC, than developing a new one.

A parser in userspace is preferred to one in kernel since if there is
a bug somewhere its consequences are much less severe.
And experience shows that parsers are especially prone to bugs.
A userspace implementation can also be tightly sandboxed for extra
security.

There are many existing OpenPGP parsing libraries to choose from.

>> This will save us from having to add complex parsers (a well-known source
>> of bugs) into the kernel - I guess there aren't any plans to add an
>> in-kernel PGP Web of Trust implementation.
>
> I extensively tested the implementation with an ad-hoc fault injector,
> to see if the code can correctly handle errors. I also developed a
> fuzzer to corrupt the data before it is read by the kernel. Finally,
> I checked that there are not memory leaks. But I agree, there could
> still be bugs.
>
> If you mean that a key can be added to the kernel if is vouched for
> by another key in the built-in keyring, I actually implemented this
> (was missing in the original implementation). Some keyrings, e.g. .ima,
> have this restriction.
>
> The way this works is that, whenever you add a PGP key to the
> kernel, the parser takes not only the public key and the user ID,
> but also its signature by the same or another PGP key.
>
> The signature is verified when the key is added to the keyring
> with that restriction, and only if the verification is successful
> the key can be added.

I understand but it would be great to make use as much as possible of
the existing in-kernel signature verification mechanisms.

> Roberto

Thanks,
Maciej


2022-01-13 09:12:09

by Roberto Sassu

[permalink] [raw]
Subject: RE: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

> From: Maciej S. Szmigiero [mailto:[email protected]]
> Sent: Wednesday, January 12, 2022 9:16 PM
> On 12.01.2022 10:16, Roberto Sassu wrote:
> >> From: Maciej S. Szmigiero [mailto:[email protected]]
> >> Sent: Tuesday, January 11, 2022 9:33 PM
> >> On 11.01.2022 19:03, Roberto Sassu wrote:
> >>> Support for PGP keys and signatures was proposed by David long time ago,
> >>> before the decision of using PKCS#7 for kernel modules signatures
> >>> verification was made. After that, there has been not enough interest to
> >>> support PGP too.
> >>>
> >>> Lately, when discussing a proposal of introducing fsverity signatures in
> >>> Fedora [1], developers expressed their preference on not having a separate
> >>> key for signing, which would complicate the management of the
> distribution.
> >>> They would be more in favor of using the same PGP key, currently used for
> >>> signing RPM headers, also for file-based signatures (not only fsverity, but
> >>> also IMA ones).
> >>
> >> Aren't PGP keys simply RSA / ECC / EdDSA keys with additional metadata?
> >> Can't they be unwrapped from their (complex) PGP format in userspace and
> >> loaded raw into the kernel, in a similar way as they are sometimes used
> >> for SSH authentication?
> >
> > Probably, this would be possible by introducing a new asymmetric
> > key subtype parsing PGP keys and signatures in a more simple format,
> > after conversion by user space. But still, a parser would be required.
> > To be honest, I would prefer to implement (actually David did) a
> > parser following an RFC, than developing a new one.
>
> A parser in userspace is preferred to one in kernel since if there is
> a bug somewhere its consequences are much less severe.
> And experience shows that parsers are especially prone to bugs.
> A userspace implementation can also be tightly sandboxed for extra
> security.
>
> There are many existing OpenPGP parsing libraries to choose from.

I understand your point. However, it does not seem to me less
risky to define a new format to upload the RSA key and the signature
to avoid the complexity of PGP. Also, it does not seem more
complex than PKCS#7, which is already in the kernel.

In addition, there are aspects of PGP that anyway have to be taken
into account. One example is the digest calculation, which depends
also on the PGP packet. Whenever the kernel verifies the signature,
the additional data need to be appended to the original data. This
risks to create more confusion, as the consumer of the data being
verified might not be prepared to handle the additional data
required for signature verification.

The kernel has already a well-defined way to process data with
a signature. It expects a data structure called module_signature
at the end of the data to verify, which include information required
for the verification such as the digest algorithm, key ID, etc. It
also has a selector called PKEY_ID_PGP, so that the code would
handle a PGP signature. This data structure does not include space
for the additional data required for the signature verification.

This patch set instead offers the new function verify_pgp_signature(),
which takes the same arguments as verify_pkcs7_signature(), and
can be used as in the example I mentioned above in a switch() where
the selector is the signature type.

This patch set also offers the individual functions called inside
verify_pgp_signature(), to support the case where the signature
verification process is split in multiple parts
(e.g. security/integrity/ima/ima_modsig.c). Also in this case,
the arguments passed to the PGP-related functions are similar
to the PKCS#7 ones.

Another concern that I have is that, the approach of using an
OpenPGP library still requires the Linux distribution vendors to
do a transformation from the source data they have to another
format. They have anyway to certify this transformation, even
if it is done in user space. Maybe it is easier to keep the original
data and verify the minimum necessary to handle PGP keys and
signature in the kernel, rather than verifying a library running
in user space with many other functions.

> >> This will save us from having to add complex parsers (a well-known source
> >> of bugs) into the kernel - I guess there aren't any plans to add an
> >> in-kernel PGP Web of Trust implementation.
> >
> > I extensively tested the implementation with an ad-hoc fault injector,
> > to see if the code can correctly handle errors. I also developed a
> > fuzzer to corrupt the data before it is read by the kernel. Finally,
> > I checked that there are not memory leaks. But I agree, there could
> > still be bugs.
> >
> > If you mean that a key can be added to the kernel if is vouched for
> > by another key in the built-in keyring, I actually implemented this
> > (was missing in the original implementation). Some keyrings, e.g. .ima,
> > have this restriction.
> >
> > The way this works is that, whenever you add a PGP key to the
> > kernel, the parser takes not only the public key and the user ID,
> > but also its signature by the same or another PGP key.
> >
> > The signature is verified when the key is added to the keyring
> > with that restriction, and only if the verification is successful
> > the key can be added.
>
> I understand but it would be great to make use as much as possible of
> the existing in-kernel signature verification mechanisms.

Yes. I think this is the purpose of the asymmetric subtypes. They
introduce a parser for the specific format, but once the relevant
information are extracted, the in-kernel mechanisms are used.

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Zhong Ronghua

> > Roberto
>
> Thanks,
> Maciej

2022-01-18 02:34:12

by Roberto Sassu

[permalink] [raw]
Subject: RE: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

> From: Jason A. Donenfeld [mailto:[email protected]]
> Sent: Monday, January 17, 2022 3:35 PM
> Hi,
>
> While it looks like you put a lot of work into this patchset, I think
> the general idea of adding PGP *to the kernel* is a pretty daunting
> proposition. The general consensus in the crypto engineering world is
> that PGP ought to be on its way out. We definitely don't want to
> perpetuate this project-on-life-support into the permanence of kernel
> code. Some quick Google searches will reveal a litany of blog posts to
> the tune of, "why oh why are people still using this?" Here's one from
> 2019: https://latacora.micro.blog/2019/07/16/the-pgp-problem.html . I
> think these are arguments to take seriously. And even if you disagree
> with some parts, you may want to consider whether the remaining parts
> warrant a bit of pause before adding this to the kernel and perpetuating
> PGP's design further.

Hi Jason

thanks a lot for the additional information. They could
make people more aware of the risks so that they transition
to more secure schemes.

The problem is that I don't see that transition coming soon.
Transition from PGP to another scheme would require Linux
distribution vendors to do an huge amount of work. It could
probably take years before that transition occurs.

More specifically, the first task would be to modify how
RPMs are signed (and thus how they are verified). The second
task would be to have a different way to certify the public key.
Lastly, Linux distribution vendors would have to change their
building infrastructure to use the new certified key, a new
version of the rpm package manager which takes as input
the new key, produces a different type of signature and embeds
it in the RPM header.

In this discussion:

https://lists.fedoraproject.org/archives/list/[email protected]/thread/JE2HGLJMLEKUJW3YBP6MQJWP43CSTC57/

people were concerned about the lifecycle of the secondary
key used for fsverity signatures. Likely, completely replacing
the key infrastructure would raise even bigger concerns.

The aim of this patch set is to make some security features
available in a short time, by significantly reducing the burden
of Linux distribution vendors for managing those security
features. I mentioned fsverity, but my primary use case would
be for DIGLIM (extract reference values for file digests from
RPM headers and use them for IMA measurement or appraisal).

The main advantage of this patch set, at least for DIGLIM, is
that it completely removes the need of changing the building
infrastructure. To show the DIGLIM benefits, I retrofitted two
already released Linux distributions (Fedora 34 and openSUSE
Leap 15.3) with DIGLIM and the necessary changes in IMA, so
that they prevent the execution of binaries and shared libraries
which were not released by the distribution (the mechanism is
completely configurable by the user to trust his binaries,
if he wishes to). If you are interested, here is the link of the
demo I developed:

https://lore.kernel.org/linux-integrity/[email protected]/

If in the future the transition from PGP to another scheme
occurs, support for PGP keys and signatures can be still
deprecated.

Roberto

HUAWEI TECHNOLOGIES Duesseldorf GmbH, HRB 56063
Managing Director: Li Peng, Zhong Ronghua

> If you're looking for a simple signature mechanism to replace the use of
> X.509 and all of that infrastructure, may I suggest just coming up with
> something simple using ed25519, similar to signify or minisign? Very
> minimal code in the kernel, in userspace, and very few moving parts to
> break.
>
> Jason

2022-01-18 03:02:57

by Maciej S. Szmigiero

[permalink] [raw]
Subject: Re: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

On 17.01.2022 17:59, Konstantin Ryabitsev wrote:
> On Mon, Jan 17, 2022 at 03:34:54PM +0100, Jason A. Donenfeld wrote:
>> If you're looking for a simple signature mechanism to replace the use of
>> X.509 and all of that infrastructure, may I suggest just coming up with
>> something simple using ed25519, similar to signify or minisign? Very
>> minimal code in the kernel, in userspace, and very few moving parts to
>> break.
>
> I am concerned that ed25519 private key management is very rudimentary -- more
> often than not it is just kept somewhere on disk, often without any passphrase
> encryption.
>
> With all its legacy warts, GnuPG at least has decent support for hardware
> off-load via OpenPGP smartcards or TPM integration in GnuPG 2.3, but the best
> we have with ed25519 is passhprase protection as implemented in minisign (and

I am not sure that I understood your point here correctly, but GnuPG
already supports ed25519 keys, including stored on a smartcard - for
example, on a YubiKey [1].

While the current software support for ed25519 might be limited, there
is certainly progress being made, RFC 8410 allowed these algos for X.509
certificates.
Support for such certificates is already implemented in OpenSSL [2].

ECDSA, on the other hand, is very fragile with respect to random number
generation at signing time.
We know that people got burned here in the past.

Thanks,
Maciej

[1]: https://developers.yubico.com/PGP/YubiKey_5.2.3_Enhancements_to_OpenPGP_3.4.html
[2]: https://blog.pinterjann.is/ed25519-certificates.html

2022-01-21 01:42:42

by Jason A. Donenfeld

[permalink] [raw]
Subject: Re: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

Hi Roberto,

Thanks for your detailed reply.

On Mon, Jan 17, 2022 at 4:21 PM Roberto Sassu <[email protected]> wrote:
> The problem is that I don't see that transition coming soon.
> Transition from PGP to another scheme would require Linux
> distribution vendors to do an huge amount of work. It could
> probably take years before that transition occurs.
> More specifically, the first task would be to modify how
> RPMs are signed (and thus how they are verified). The second
> task would be to have a different way to certify the public key.
> Lastly, Linux distribution vendors would have to change their
> building infrastructure to use the new certified key, a new
> version of the rpm package manager which takes as input
> the new key, produces a different type of signature and embeds
> it in the RPM header.

Hm, yea, I see your dilemma.

On the one hand, you recognize the problems with what currently
exists. On the other hand, you[r organization] hasn't made the
transition to something better. So, rather than putting in what might
be a lot of work to transition to something better (which includes
actually evaluating *what* the better thing would be), you'd prefer to
put in a smaller amount of work to make the current thing satisfy some
of your needs, even though you recognize its flaws. It seems like this
is one of those "short term" vs "long term" investment tradeoffs.

I don't have a whole lot _technical_ to say about long term vs short
term thinking, but it does strike me that PGP is one of these cases
where people have known about the flaws for decades, but the ecosystem
keeps being extended because people continue to go with the short term
solutions, one by one, and now they've wound up here, at the doorstep
of the kernel. Maybe if at some point somebody puts down the foot and
says, "the road of short term intentions stops here," there might
gradually be impetus toward looking into long term solutions, e.g.
viable PGP replacements? Just a thought.

> If in the future the transition from PGP to another scheme
> occurs, support for PGP keys and signatures can be still
> deprecated.

Things in the kernel rarely disappear. At best, they become subtly
neglected, and then somebody gets bit by some security bug. At worst,
we're stuck maintaining a PGP implementation until the end of
eternity.


On the technical front, though, I had sort of the same thought as
Maciej: is there some way that you can unwrap the PGP data in
userspace, and re-encode it in ASN.1, and somehow magically account
for the various metadata included in the signatures? The devil here
might be in the details, and I'm not sure whether it's feasible. But
if it is, this would seem to be a much nicer solution. I'm not the
hugest fan of having an ASN.1 parser in the kernel either, but it's
_already_ there, and if you could somehow piggyback on top of it, that
means we'd be able to avoid importing this PGP implementation.

Concretely, it looks like the hardest part of this is the fact that
pgp_digest_signature seems to hash in some PGP-specific metadata, not
just the raw data. Am I reading that right, and that's the case? If
so, that might spell trouble. You also mentioned in that other thread
the possibility of using a new/custom PGP packet type for this? Is the
idea there that you'd come up with something that could be unwrapped
into an ASN.1-verifable blob, as a custom extension of PGP that
distros could then distribute?

Jason

2022-01-21 08:20:21

by Antony Vennard

[permalink] [raw]
Subject: Re: [PATCH 00/14] KEYS: Add support for PGP keys and signatures


Hi All,

On 17/01/2022 16:02, James Bottomley wrote:
> On Mon, 2022-01-17 at 15:34 +0100, Jason A. Donenfeld wrote:
>> Hi,
>>
>> While it looks like you put a lot of work into this patchset, I think
>> the general idea of adding PGP *to the kernel* is a pretty daunting
>> proposition. The general consensus in the crypto engineering world is
>> that PGP ought to be on its way out. We definitely don't want to
>> perpetuate this project-on-life-support into the permanence of kernel
>> code. Some quick Google searches will reveal a litany of blog posts
>> to the tune of, "why oh why are people still using this?" Here's one
>> from 2019:
>> https://latacora.micro.blog/2019/07/16/the-pgp-problem.html . I
>> think these are arguments to take seriously. And even if you disagree
>> with some parts, you may want to consider whether the remaining parts
>> warrant a bit of pause before adding this to the kernel and
>> perpetuating PGP's design further.

So while I understand why this is being proposed and clearly effort has
gone into it, I also think it is not the right approach. It seems this
proposal is to include a full PGP packet parser and verification logic
in the kernel as an equivalent to allow PGP signatures to be submitted
via FS_IOC_ENABLE_VERITY:

"FS_IOC_ENABLE_VERITY accepts a pointer to a PKCS#7 formatted detached
signature in DER format of the file’s fs-verity digest."

I may be misinterpreting, but as I understand it logic for X.509/PKCS
validation already exists in-kernel because of UEFI and module signing,
so this signature would be verified up to trusted roots. The proposal is
to duplicate all of this logic but in terms of PGP-formatted keys.

I believe this is unnecessary. Since it seems to require both a
signature and verification up to a root, and distributions like Fedora
already deal with x509 module signing keys for their kernel modules, I
can't see the merit in including the full PGP system too. The
least-effort approach, I would suggest, is to include an x509
representation of the signature in the RPM and use the existing API to
push that up into the kernel, with the trust anchor the same as is used
for module signing keys, or whatever distributions prefer (they, after
all, control the kernel trust anchors).

I understand this requires some effort, but so too does maintaining a
fully fledged PGP packet parser as privileged code for all time, and I
think maintaining this in userspace is a) easier and b) less costly than
doing it in kernel. As an added bonus, a PGP-parsing library in a
memory-safe language could be used in userspace.

A slightly more drastic step would be to wholesale move to PKCS
signatures for packaging. Linux distributions could be their own trusted
roots for this purpose if they so desired, and since they control
certificate bundles anyway they've no need to add them to the browser
list if that is a concern, and can issue certs without SSL Client/SSL
Server types and OIDs.

> The reason is simple though: for all the detractors and whining, no-
> one's actually been able to come up with a more usable replacement.
> Very few people who complain about GPG actually submit patches to fix
> it. A few come up with their own infrastructure which tends to have
> even more problems.

Probably replacing with PKCS is a non-starter, but it is at least
possible and it is highly likely distros package one or more tools
capable of validating such signatures in their base installs.

There are multiple problems with PGP that receive complaints. They are:

1) No forward secrecy in messaging-based crypto.
2) The data format.
3) Outdated cryptography still supported.
4) UX.

Of these, all four could be levelled against PKCS standards and related
tools too (except TLS protocols for the first point), and only 2 and 3
are relevant here since we are concerned with signature validation only.

I'm not "against" PGP per se, but I'm not convinced by the idea of
adding PGP support just for fs-verity.

>> If you're looking for a simple signature mechanism to replace the use
>> of X.509 and all of that infrastructure, may I suggest just coming up
>> with something simple using ed25519,
>
> Please, no, use universally supported crypto that we can use TPM
> hardware for, which for EC currently means P-256. It may be possible
> to get the TCG and the other security bodies to add Edwards signatures
> but the rate of progression of quantum means that Grover's Algorithm
> will likely get there first and we'll need P-521 or X448.

I agree: whatever is chosen should in my view have decent support for
hardware-backed keys, because I strongly hope that is what distribution
key storage looks like. If not I might need to move to Gentoo.

The current RPM signing keys for Fedora are RSA-4096, which in my
opinion does not actually meet the bar for "good hardware support for
algorithm". RSA-2048 tokens are common, but 4096 much less so.

Unfortunately for this reason signify/minisign is not ideal as tools: so
far as I can see it has no support for hardware-backed keys. That's not
to say they couldn't, although they both use ed25519.

I am not sure if we'll end up in the situation where Grover's algorithm
is efficient but Shor is not, but this is all guesswork until we get
closer to a quantum computer with enough logical Qubits. But your
substantive point I think is valid: hardware vendors are disappointingly
slow at adopting edwards curves.

>> similar to signify or minisign? Very minimal code in the kernel, in
>> userspace, and very few moving parts to break.
>
> Heh, this is the classic cryptographers dilemma: go for something
> pejorative which can be minimal but which has only a relatively small
> set of possible use cases and no future proofing or go for something
> extensible which ends up more complex and others then criticize as
> being a "swiss army knife".

I think this is the wrong framing for the problem. We already have one
extensible system that is complicated in the kernel. This patch proposes
to add a second one achieving the same purpose instead of a userspace
solution to take advantage of the existing code, which I think would be
preferable and safer.

Antony

2022-01-21 11:14:43

by Eric Biggers

[permalink] [raw]
Subject: Re: [PATCH 00/14] KEYS: Add support for PGP keys and signatures

On Tue, Jan 18, 2022 at 09:50:21PM +0100, Antony Vennard wrote:
>
> Hi All,
>
> On 17/01/2022 16:02, James Bottomley wrote:
> > On Mon, 2022-01-17 at 15:34 +0100, Jason A. Donenfeld wrote:
> > > Hi,
> > >
> > > While it looks like you put a lot of work into this patchset, I think
> > > the general idea of adding PGP *to the kernel* is a pretty daunting
> > > proposition. The general consensus in the crypto engineering world is
> > > that PGP ought to be on its way out. We definitely don't want to
> > > perpetuate this project-on-life-support into the permanence of kernel
> > > code. Some quick Google searches will reveal a litany of blog posts
> > > to the tune of, "why oh why are people still using this?" Here's one
> > > from 2019:
> > > https://latacora.micro.blog/2019/07/16/the-pgp-problem.html . I
> > > think these are arguments to take seriously. And even if you disagree
> > > with some parts, you may want to consider whether the remaining parts
> > > warrant a bit of pause before adding this to the kernel and
> > > perpetuating PGP's design further.
>
> So while I understand why this is being proposed and clearly effort has gone
> into it, I also think it is not the right approach. It seems this proposal
> is to include a full PGP packet parser and verification logic in the kernel
> as an equivalent to allow PGP signatures to be submitted via
> FS_IOC_ENABLE_VERITY:
>
> "FS_IOC_ENABLE_VERITY accepts a pointer to a PKCS#7 formatted detached
> signature in DER format of the file’s fs-verity digest."
>

It's worth noting that if fs-verity built-in signatures are used, a trusted
userspace program is still required to determine and enforce the policy of which
files are required to be signed. The kernel only handles the actual signature
verification. This was basically a proof-of-concept which reused the kernel's
module signature verification code (which happens to use PKCS#7).

I'd encourage new users to either go all-in on a userspace solution, using a
trusted userspace program to verify signatures of fs-verity file digests;
*or* go all-in on an in-kernel solution, using the IMA support for fs-verity
which Mimi Zohar is working on. A userspace solution could use a simple
signature format, using a modern algorithm such as Ed25519. IMA uses a simple
signature format too, though it uses a complex format (X.509) for public keys.

- Eric