[RFC][GNUPG][PATCH v3 1/2] Convert PGP keys to the user asymmetric keys format

From: Roberto Sassu
Date: Thu Jul 20 2023 - 11:37:24 EST


From: Roberto Sassu <roberto.sassu@xxxxxxxxxx>

Introduce the new gpg command --conv-kernel, to convert PGP keys to the
user asymmetric keys format.

The --export command cannot be used, as it would not allow to convert
signatures from a file.

Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
---
configure.ac | 7 ++
doc/gpg.texi | 4 +
g10/Makefile.am | 4 +
g10/conv-packet.c | 284 ++++++++++++++++++++++++++++++++++++++++++++++
g10/conv-packet.h | 37 ++++++
g10/gpg.c | 15 ++-
g10/mainproc.c | 17 ++-
g10/options.h | 2 +
8 files changed, 368 insertions(+), 2 deletions(-)
create mode 100644 g10/conv-packet.c
create mode 100644 g10/conv-packet.h

diff --git a/configure.ac b/configure.ac
index fe7e821089b..6c867e6409e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -105,6 +105,7 @@ have_libusb=no
have_libtss=no
have_system_resolver=no
gnupg_have_ldap="n/a"
+have_uasym_support=no

use_zip=yes
use_bzip2=yes
@@ -1817,6 +1818,11 @@ if test x"$use_run_gnupg_user_socket" = x"yes"; then
[If defined try /run/gnupg/user before /run/user])
fi

+AC_CHECK_HEADERS([linux/uasym_parser.h], [have_uasym_support=yes], [])
+AM_CONDITIONAL([UASYM_KEYS_SIGS], [test "$have_uasym_support" = yes])
+if test "$have_uasym_support" = yes; then
+ CFLAGS="$CFLAGS -DUASYM_KEYS_SIGS"
+fi

#
# Decide what to build
@@ -2158,6 +2164,7 @@ echo "
TLS support: $use_tls_library
TOFU support: $use_tofu
Tor support: $show_tor_support
+ Uasym support: $have_uasym_support
"
if test "$have_libtss" != no -a -z "$TPMSERVER" -a -z "$SWTPM"; then
cat <<G10EOF
diff --git a/doc/gpg.texi b/doc/gpg.texi
index 6b584a91306..e4d6f0adc59 100644
--- a/doc/gpg.texi
+++ b/doc/gpg.texi
@@ -652,6 +652,10 @@ Set the TOFU policy for all the bindings associated with the specified
@pxref{trust-model-tofu}. The @var{keys} may be specified either by their
fingerprint (preferred) or their keyid.

+@item --conv-kernel
+@opindex conv-kernel
+Convert PGP keys into a format understood by the Linux kernel.
+
@c @item --server
@c @opindex server
@c Run gpg in server mode. This feature is not yet ready for use and
diff --git a/g10/Makefile.am b/g10/Makefile.am
index c5691f551b1..7e6f30dc0b5 100644
--- a/g10/Makefile.am
+++ b/g10/Makefile.am
@@ -130,6 +130,10 @@ common_source = \
objcache.c objcache.h \
ecdh.c

+if UASYM_KEYS_SIGS
+common_source += conv-packet.c
+endif
+
gpg_sources = server.c \
$(common_source) \
pkclist.c \
diff --git a/g10/conv-packet.c b/g10/conv-packet.c
new file mode 100644
index 00000000000..8f2fc40b980
--- /dev/null
+++ b/g10/conv-packet.c
@@ -0,0 +1,284 @@
+/* conv-packet.c - convert packets
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/types.h>
+#include <linux/tlv_parser.h>
+#include <linux/uasym_parser.h>
+#include <asm/byteorder.h>
+#include <linux/pub_key_info.h>
+
+#include "gpg.h"
+#include "../common/util.h"
+#include "packet.h"
+#include "conv-packet.h"
+#include "options.h"
+#include "../common/i18n.h"
+
+static estream_t listfp;
+
+static void init_output(void)
+{
+ if (!listfp)
+ {
+ listfp = es_stdout;
+
+ if (opt.outfile) {
+ listfp = es_fopen (opt.outfile, "wb");
+ if (!listfp) {
+ log_error(_("cannot open %s for writing\n"), opt.outfile);
+ exit(1);
+ }
+ }
+ }
+}
+
+/*
+ * Simple encoder primitives for ASN.1 BER/DER/CER
+ *
+ * Copyright (C) 2019 James.Bottomley@xxxxxxxxxxxxxxxxxxxxx
+ */
+static int asn1_encode_length(unsigned char *data, __u32 *data_len, __u32 len)
+{
+ if (len <= 0x7f) {
+ data[0] = len;
+ *data_len = 1;
+ return 0;
+ }
+
+ if (len <= 0xff) {
+ data[0] = 0x81;
+ data[1] = len & 0xff;
+ *data_len = 2;
+ return 0;
+ }
+
+ if (len <= 0xffff) {
+ data[0] = 0x82;
+ data[1] = (len >> 8) & 0xff;
+ data[2] = len & 0xff;
+ *data_len = 3;
+ return 0;
+ }
+
+ if (len > 0xffffff)
+ return -EINVAL;
+
+ data[0] = 0x83;
+ data[1] = (len >> 16) & 0xff;
+ data[2] = (len >> 8) & 0xff;
+ data[3] = len & 0xff;
+ *data_len = 4;
+
+ return 0;
+}
+
+static int mpis_to_asn1_sequence(gcry_mpi_t *pkey, int num_keys,
+ unsigned char **buffer, size_t *buffer_len)
+{
+ unsigned char asn1_key_len[PUBKEY_MAX_NSKEY][4];
+ unsigned char asn1_seq_len[4];
+ unsigned char *buffer_ptr;
+ __u32 asn1_key_len_len[PUBKEY_MAX_NSKEY];
+ __u32 asn1_seq_len_len;
+ __u32 asn1_seq_payload_len = 0;
+ size_t nbytes;
+ gpg_error_t err;
+ int ret, i;
+
+ for (i = 0, nbytes = 0; i < num_keys; i++) {
+ err = gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &nbytes, pkey[i]);
+ if (err)
+ return -EINVAL;
+
+ ret = asn1_encode_length(asn1_key_len[i], &asn1_key_len_len[i], nbytes);
+ if (ret < 0)
+ return ret;
+
+ asn1_seq_payload_len += 1 + asn1_key_len_len[i] + nbytes;
+ }
+
+ ret = asn1_encode_length(asn1_seq_len, &asn1_seq_len_len,
+ asn1_seq_payload_len);
+ if (ret < 0)
+ return ret;
+
+ *buffer_len = 1 + asn1_seq_len_len + asn1_seq_payload_len;
+ *buffer = xmalloc_clear(*buffer_len);
+ if (!*buffer)
+ return -ENOMEM;
+
+ buffer_ptr = *buffer;
+
+ /* ASN1_SEQUENCE */
+ *buffer_ptr++ = 0x30;
+ memcpy(buffer_ptr, &asn1_seq_len, asn1_seq_len_len);
+ buffer_ptr += asn1_seq_len_len;
+
+ for (i = 0; i < num_keys; i++) {
+ /* ASN1_INTEGER */
+ *buffer_ptr++ = 0x02;
+ memcpy(buffer_ptr, &asn1_key_len[i], asn1_key_len_len[i]);
+ buffer_ptr += asn1_key_len_len[i];
+
+ err = gcry_mpi_print(GCRYMPI_FMT_USG, buffer_ptr,
+ *buffer_len - (buffer_ptr - *buffer), &nbytes, pkey[i]);
+ if (err) {
+ xfree(*buffer);
+ return -EINVAL;
+ }
+
+ buffer_ptr += nbytes;
+ }
+
+ *buffer_len = buffer_ptr - *buffer;
+
+ return 0;
+}
+
+static int pgp_to_kernel_algo(int pgp_algorithm, gcry_mpi_t pkey, __u8 *algo)
+{
+ char *curve = NULL;
+ const char *name;
+ int ret = 0;
+
+ switch (pgp_algorithm) {
+ case PUBKEY_ALGO_RSA:
+ case PUBKEY_ALGO_RSA_S:
+ *algo = PKEY_ALGO_RSA;
+ break;
+ case PUBKEY_ALGO_ECDSA:
+ *algo = PKEY_ALGO_ECDSA;
+ if (!pkey)
+ break;
+
+ curve = openpgp_oid_to_str (pkey);
+ name = openpgp_oid_to_curve (curve, 0);
+ if (!strcmp(name, "nistp192"))
+ *algo = PKEY_ALGO_ECDSA_P192;
+ else if (!strcmp(name, "nistp256"))
+ *algo = PKEY_ALGO_ECDSA_P256;
+ else if (!strcmp(name, "nistp384"))
+ *algo = PKEY_ALGO_ECDSA_P384;
+ else
+ ret = -EOPNOTSUPP;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ xfree(curve);
+ return ret;
+}
+
+int write_kernel_key(PKT_public_key *pk)
+{
+ unsigned char *buffer = NULL;
+ size_t buffer_len = 0;
+ struct tlv_hdr hdr = { 0 };
+ struct tlv_entry e_algo = { 0 };
+ struct tlv_entry e_keyid = { 0 };
+ struct tlv_entry e_key_pub = { 0 };
+ struct tlv_entry e_key_desc = { 0 };
+ __u8 algo;
+ __u32 keyid[2], _keyid;
+ __u64 total_len = 0;
+ /* PGP: <keyid> */
+ char key_desc[4 + 1 + 8 + 1];
+ gpg_error_t err;
+ int ret = 0;
+
+ init_output();
+ keyid_from_pk (pk, keyid);
+
+ ret = pgp_to_kernel_algo(pk->pubkey_algo, pk->pkey[0], &algo);
+ if (ret < 0)
+ return ret;
+
+ /* algo */
+ e_algo.field = __cpu_to_be64(KEY_ALGO);
+ e_algo.length = __cpu_to_be64(sizeof(algo));
+ total_len += sizeof(e_algo) + sizeof(algo);
+
+ /* key id */
+ e_keyid.field = __cpu_to_be64(KEY_KID0);
+ e_keyid.length = __cpu_to_be64(sizeof(*pk->keyid) * 2);
+ total_len += sizeof(e_keyid) + sizeof(*pk->keyid) * 2;
+
+ /* key desc */
+ e_key_desc.field = __cpu_to_be64(KEY_DESC);
+ e_key_desc.length = __cpu_to_be64(sizeof(key_desc));
+ total_len += sizeof(e_key_desc) + sizeof(key_desc);
+
+ snprintf(key_desc, sizeof(key_desc), "PGP: %08x", pk->keyid[1]);
+
+ switch (pk->pubkey_algo) {
+ case PUBKEY_ALGO_RSA:
+ case PUBKEY_ALGO_RSA_S:
+ ret = mpis_to_asn1_sequence(pk->pkey, pubkey_get_npkey(pk->pubkey_algo),
+ &buffer, &buffer_len);
+ break;
+ case PUBKEY_ALGO_ECDSA:
+ err = gcry_mpi_aprint(GCRYMPI_FMT_USG, &buffer, &buffer_len, pk->pkey[1]);
+ if (err)
+ ret = -EINVAL;
+ break;
+ default:
+ ret = -EOPNOTSUPP;
+ break;
+ }
+
+ if (ret < 0)
+ goto out;
+
+ /* key blob */
+ e_key_pub.field = __cpu_to_be64(KEY_PUB);
+ e_key_pub.length = __cpu_to_be64(buffer_len);
+ total_len += sizeof(e_key_pub) + buffer_len;
+
+ hdr.data_type = __cpu_to_be64(TYPE_KEY);
+ hdr.num_fields = __cpu_to_be64(4);
+ hdr.total_len = __cpu_to_be64(total_len);
+
+ es_write(listfp, &hdr, sizeof(hdr), NULL);
+
+ es_write(listfp, &e_algo, sizeof(e_algo), NULL);
+ es_write(listfp, &algo, sizeof(algo), NULL);
+
+ es_write(listfp, &e_keyid, sizeof(e_keyid), NULL);
+ _keyid = __cpu_to_be32(pk->keyid[0]);
+ es_write(listfp, &_keyid, sizeof(_keyid), NULL);
+ _keyid = __cpu_to_be32(pk->keyid[1]);
+ es_write(listfp, &_keyid, sizeof(_keyid), NULL);
+
+ es_write(listfp, &e_key_pub, sizeof(e_key_pub), NULL);
+ es_write(listfp, buffer, buffer_len, NULL);
+
+ es_write(listfp, &e_key_desc, sizeof(e_key_desc), NULL);
+ es_write(listfp, key_desc, sizeof(key_desc), NULL);
+out:
+ xfree(buffer);
+ return 0;
+}
diff --git a/g10/conv-packet.h b/g10/conv-packet.h
new file mode 100644
index 00000000000..d35acb985fc
--- /dev/null
+++ b/g10/conv-packet.h
@@ -0,0 +1,37 @@
+/* conv-packet.h - header of conv-packet.c
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef G10_CONV_PACKET_H
+#define G10_CONV_PACKET_H
+
+#include "../common/openpgpdefs.h"
+
+#ifdef UASYM_KEYS_SIGS
+int write_kernel_key(PKT_public_key *pk);
+#else
+static inline int write_kernel_key(PKT_public_key *pk)
+{
+ (void)pk;
+ return 0;
+}
+
+#endif /* UASYM_KEYS_SIGS */
+#endif /*G10_CONV_PACKET_H*/
diff --git a/g10/gpg.c b/g10/gpg.c
index 6e54aa7636c..410be8ab2ad 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -186,6 +186,7 @@ enum cmd_and_opt_values
aPasswd,
aServer,
aTOFUPolicy,
+ aConvKernel,

oMimemode,
oTextmode,
@@ -583,6 +584,8 @@ static gpgrt_opt_t opts[] = {
ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */
ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */
ARGPARSE_c (aShowKeys, "show-key", "@"), /* alias */
+ ARGPARSE_c (aConvKernel, "conv-kernel",
+ N_("convert to Linux kernel uasym format")),



@@ -2657,6 +2660,7 @@ main (int argc, char **argv)

case aCheckKeys:
case aListPackets:
+ case aConvKernel:
case aImport:
case aFastImport:
case aSendKeys:
@@ -5381,6 +5385,12 @@ main (int argc, char **argv)
log_info (_("WARNING: no command supplied."
" Trying to guess what you mean ...\n"));
/*FALLTHRU*/
+ case aConvKernel:
+#ifndef UASYM_KEYS_SIGS
+ log_error(_("No support for user asymmetric keys and signatures\n"));
+ exit(1);
+#endif
+ /*FALLTHRU*/
case aListPackets:
if( argc > 1 )
wrong_args("[filename]");
@@ -5411,7 +5421,10 @@ main (int argc, char **argv)
if( cmd == aListPackets ) {
opt.list_packets=1;
set_packet_list_mode(1);
- }
+ } else if( cmd == aConvKernel ) {
+ opt.list_packets=1;
+ opt.conv_kernel=1;
+ }
rc = proc_packets (ctrl, NULL, a );
if( rc )
{
diff --git a/g10/mainproc.c b/g10/mainproc.c
index 7dea4972894..edef9907127 100644
--- a/g10/mainproc.c
+++ b/g10/mainproc.c
@@ -496,6 +496,16 @@ proc_symkey_enc (CTX c, PACKET *pkt)
free_packet (pkt, NULL);
}

+static void
+proc_conv (PACKET *pkt)
+{
+ switch (pkt->pkttype)
+ {
+ case PKT_PUBLIC_KEY: write_kernel_key(pkt->pkt.public_key); break;
+ default: break;
+ }
+ free_packet(pkt, NULL);
+}

static void
proc_pubkey_enc (CTX c, PACKET *pkt)
@@ -1652,7 +1662,7 @@ do_proc_packets (CTX c, iobuf_t a)
continue;
}
newpkt = -1;
- if (opt.list_packets)
+ if (opt.list_packets && !opt.conv_kernel)
{
switch (pkt->pkttype)
{
@@ -1665,6 +1675,11 @@ do_proc_packets (CTX c, iobuf_t a)
default: newpkt = 0; break;
}
}
+ else if (opt.conv_kernel)
+ {
+ proc_conv(pkt);
+ newpkt = 0;
+ }
else if (c->sigs_only)
{
switch (pkt->pkttype)
diff --git a/g10/options.h b/g10/options.h
index 914c24849f2..08125481511 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -26,6 +26,7 @@
#include <stdint.h>
#include "main.h"
#include "packet.h"
+#include "conv-packet.h"
#include "tofu.h"
#include "../common/session-env.h"
#include "../common/compliance.h"
@@ -91,6 +92,7 @@ struct
int list_sigs; /* list signatures */
int no_armor;
int list_packets; /* Option --list-packets active. */
+ int conv_kernel; /* Option --conv-kernel active. */
int def_cipher_algo;
int force_mdc;
int disable_mdc;
--
2.34.1