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

Message ID 20230706144225.1046544-12-roberto.sassu@huaweicloud.com
State New
Headers
Series KEYS: Introduce user asymmetric keys and signatures |

Commit Message

Roberto Sassu July 6, 2023, 2:42 p.m. UTC
  From: Roberto Sassu <roberto.sassu@huawei.com>

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@huawei.com>
---
 configure.ac      |   7 ++
 doc/gpg.texi      |   4 +
 g10/Makefile.am   |   4 +
 g10/conv-packet.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++
 g10/conv-packet.h |  37 ++++++
 g10/gpg.c         |  15 ++-
 g10/mainproc.c    |  17 ++-
 g10/options.h     |   2 +
 8 files changed, 371 insertions(+), 2 deletions(-)
 create mode 100644 g10/conv-packet.c
 create mode 100644 g10/conv-packet.h
  

Patch

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..360db30eb8d
--- /dev/null
+++ b/g10/conv-packet.c
@@ -0,0 +1,287 @@ 
+/* conv-packet.c - convert packets
+ * Copyright (C) 2023 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu@huawei.com>
+ *
+ * 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/uasym_parser.h>
+#ifdef __BIG_ENDIAN__
+#include <linux/byteorder/big_endian.h>
+#else
+#include <linux/byteorder/little_endian.h>
+#endif
+#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@HansenPartnership.com
+ */
+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 uasym_hdr hdr = { 0 };
+  struct uasym_entry e_algo = { 0 };
+  struct uasym_entry e_keyid = { 0 };
+  struct uasym_entry e_key_pub = { 0 };
+  struct uasym_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_be16(KEY_ALGO);
+  e_algo.length = __cpu_to_be32(sizeof(algo));
+  total_len += sizeof(e_algo) + sizeof(algo);
+
+  /* key id */
+  e_keyid.field = __cpu_to_be16(KEY_KID0);
+  e_keyid.length = __cpu_to_be32(sizeof(*pk->keyid) * 2);
+  total_len += sizeof(e_keyid) + sizeof(*pk->keyid) * 2;
+
+  /* key desc */
+  e_key_desc.field = __cpu_to_be16(KEY_DESC);
+  e_key_desc.length = __cpu_to_be32(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_be16(KEY_PUB);
+  e_key_pub.length = __cpu_to_be32(buffer_len);
+  total_len += sizeof(e_key_pub) + buffer_len;
+
+  hdr.data_type = TYPE_KEY;
+  hdr.num_fields = __cpu_to_be16(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@huawei.com>
+ *
+ * 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;