working with subkeys: preparing to write a gnutls-based gpg2ssh capable of feeding...
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Wed, 9 Apr 2008 01:02:35 +0000 (21:02 -0400)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Wed, 9 Apr 2008 01:02:35 +0000 (21:02 -0400)
.gitignore
Makefile
gnutls-helpers.c
gnutls-helpers.h
gpg2ssh.c [new file with mode: 0644]
main.c

index d2cf5887b24001a514277a311ac82635dc56c07b..ae9fbd8fc60e6a7ceb6ad9991cf9a03f79ea2ffb 100644 (file)
@@ -1,3 +1,4 @@
 *~
 *.[ao]
 monkeysphere
+gpg2ssh
index b8545a99b22d8e743d2c43f64ae1937c2b243ec4..4fb4556c493bc7cadf3183566857fabf81f3f121 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,9 @@
 monkeysphere: main.c gnutls-helpers.o
        gcc -g -Wall --pedantic -o monkeysphere main.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o
 
+gpg2ssh: gpg2ssh.c gnutls-helpers.o
+       gcc -g -Wall --pedantic -o gpg2ssh gpg2ssh.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o
+
 %.o: %.c
        gcc -g -Wall --pedantic -o $@ -c $<
 
index f5d37e602eb40da9aad86d93d4f1473b067a19cb..a2f8446c614653509c0c0e3c1db91356ab5d9fca 100644 (file)
@@ -18,6 +18,24 @@ void logfunc(int level, const char* string) {
   fprintf(stderr, "GnuTLS Logging (%d): %s\n", level, string);
 }
 
+void init_keyid(gnutls_openpgp_keyid_t keyid) {
+  memset(keyid, 'x', sizeof(gnutls_openpgp_keyid_t));
+}
+
+
+
+void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid)
+{
+  static const char hex[16] = "0123456789ABCDEF";
+  unsigned int kix = 0, outix = 0;
+  
+  while (kix < sizeof(gnutls_openpgp_keyid_t)) {
+    out[outix] = hex[(keyid[kix] >> 4) & 0x0f];
+    out[outix + 1] = hex[keyid[kix] & 0x0f];
+    kix++;
+    outix += 2;
+  }
+}
 
 
 int init_gnutls() {
index 1bf7a1d77cda6056ab5c1371fc26627ecb7d2d51..c07997f3aada77df22ddc2a9f7aac648d567d82d 100644 (file)
@@ -4,6 +4,7 @@
 
 
 #include <gnutls/gnutls.h>
+#include <gnutls/openpgp.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -32,6 +33,12 @@ void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src);
 int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b);
 void free_datum(gnutls_datum_t* d);
 
+/* keyid manipulations: */
+typedef unsigned char printable_keyid[16];
+
+void init_keyid(gnutls_openpgp_keyid_t keyid);
+void make_keyid_printable(printable_keyid out, gnutls_openpgp_keyid_t keyid);
+
 /* functions to get data into datum objects: */
 
 /* read the passed-in string, store in a single datum */
@@ -44,3 +51,4 @@ int set_datum_fd(gnutls_datum_t* d, int fd);
 /* read the file indicated (by na1me) in the fname parameter.  store
    its entire contents in a single datum. */
 int set_datum_file(gnutls_datum_t* d, const char* fname);
+
diff --git a/gpg2ssh.c b/gpg2ssh.c
new file mode 100644 (file)
index 0000000..87b62a3
--- /dev/null
+++ b/gpg2ssh.c
@@ -0,0 +1,189 @@
+#include "gnutls-helpers.h"
+
+#include <gnutls/openpgp.h>
+#include <gnutls/x509.h>
+
+/* for htonl() */
+#include <arpa/inet.h>
+
+
+/* 
+   Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+   Date: Tue, 08 Apr 2008
+   License: GPL v3 or later
+
+   monkeysphere public key translator: execute this with an GPG
+   certificate (public key(s) + userid(s)) on stdin.  It currently
+   only works with RSA keys.
+
+   It will spit out a version of the first key capable of being used
+   for authentication on stdout.  The output format should be suitable
+   for appending a known_hosts file.
+
+   Requirements: I've only built this so far with GnuTLS v2.3.4 --
+   version 2.2.0 does not contain the appropriate pieces.
+
+ */
+
+int main(int argc, char* argv[]) {
+  gnutls_datum_t data;
+  int ret;
+  gnutls_openpgp_crt_t openpgp_crt;
+  gnutls_openpgp_keyid_t keyid;
+  printable_keyid p_keyid;
+  unsigned int keyidx;
+  unsigned int usage, bits;
+  gnutls_pk_algorithm_t algo;
+
+  gnutls_datum_t m, e, p, q, g, y;
+
+  char output_data[10240];
+  size_t ods = sizeof(output_data);
+
+  init_gnutls();
+  
+  init_datum(&data);
+
+  init_datum(&m);
+  init_datum(&e);
+  init_datum(&p);
+  init_datum(&q);
+  init_datum(&g);
+  init_datum(&y);
+
+  init_keyid(keyid);
+
+  /* slurp in the private key from stdin */
+  if (ret = set_datum_fd(&data, 0), ret) {
+    err("didn't read file descriptor 0\n");
+    return 1;
+  }
+
+
+  if (ret = gnutls_openpgp_crt_init(&openpgp_crt), ret) {
+    err("Failed to initialize OpenPGP certificate (error: %d)\n", ret);
+    return 1;
+  }
+
+  /* format could be either: GNUTLS_OPENPGP_FMT_RAW,
+     GNUTLS_OPENPGP_FMT_BASE64; if MONKEYSPHERE_RAW is set, use RAW,
+     otherwise, use BASE64: */
+
+  if (getenv("MONKEYSPHERE_RAW")) {
+    err("assuming RAW formatted certificate\n");
+    if (ret = gnutls_openpgp_crt_import(openpgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW), ret) {
+      err("failed to import the OpenPGP certificate in RAW format (error: %d)\n", ret);
+      return ret;
+    }
+  } else {
+    err("assuming BASE64 formatted certificate\n");
+    if (ret = gnutls_openpgp_crt_import (openpgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64), ret) {
+      err("failed to import the OpenPGP certificate in BASE64 format (error: %d)\n", ret);
+      return ret;
+    }
+  }
+
+  if (gnutls_openpgp_crt_get_revoked_status(openpgp_crt)) {
+    err("the primary key was revoked!\n");
+    return 1;
+  }
+
+  if (ret = gnutls_openpgp_crt_get_key_usage(openpgp_crt, &usage), ret) {
+    err("failed to get the usage flags for the primary key (error: %d)\n", ret);
+    return ret;
+  }
+  if (usage & GNUTLS_KEY_KEY_AGREEMENT) {
+    err("the primary key can be used for authentication\n");
+
+    algo = gnutls_openpgp_crt_get_pk_algorithm(openpgp_crt, &bits);
+    if (algo < 0) {
+      err("failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo);
+      return algo;
+    } else if (algo == GNUTLS_PK_RSA) {
+      
+      err("OpenPGP RSA certificate, with %d bits\n", bits);
+      ret = gnutls_openpgp_crt_get_pk_rsa_raw(openpgp_crt, &m, &e);
+      if (GNUTLS_E_SUCCESS != ret) {
+       err ("failed to export RSA key parameters (error: %d)\n", ret);
+       return 1;
+      }
+    } else if (algo == GNUTLS_PK_DSA) {
+      err("OpenPGP DSA Key, with %d bits\n", bits);
+      ret = gnutls_openpgp_crt_get_pk_dsa_raw(openpgp_crt, &p, &q, &g, &y);
+      if (GNUTLS_E_SUCCESS != ret) {
+       err ("failed to export DSA key parameters (error: %d)\n", ret);
+       return 1;
+      }
+    } else {
+      err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", algo);
+      return 1;
+    }
+    
+  } else {
+    err("primary key is only good for: 0x%08x.  Trying subkeys...\n", usage);
+    
+    if (ret = gnutls_openpgp_crt_get_auth_subkey(openpgp_crt, keyid), ret) {
+      err("failed to find a subkey capable of authentication (error: %d)\n", ret);
+      return ret;
+    }
+    make_keyid_printable(p_keyid, keyid);
+    err("found authentication subkey %.16s\n", p_keyid);
+
+    ret = gnutls_openpgp_crt_get_subkey_idx(openpgp_crt, keyid);
+    if (ret < 0) {
+      err("could not get the index of subkey %.16s (error: %d)\n", ret);
+      return ret;
+    }
+    keyidx = ret;
+
+    if (gnutls_openpgp_crt_get_subkey_revoked_status(openpgp_crt, keyidx)) {
+      err("The authentication subkey was revoked!\n");
+      return 1;
+    }
+
+    if (ret = gnutls_openpgp_crt_get_subkey_usage(openpgp_crt, keyidx, &usage), ret) {
+      err("could not figure out usage of subkey %.16s (error: %d)\n", p_keyid, ret);
+      return ret;
+    }
+    if ((usage & GNUTLS_KEY_KEY_AGREEMENT) == 0) {
+      err("could not find a subkey with authentication privileges.\n");
+      return 1;
+    }
+
+    /* switch, based on the algorithm in question, to extract the MPI
+       components: */
+
+    algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(openpgp_crt, keyidx, &bits);
+    if (algo < 0) {
+      err("failed to get the algorithm of the authentication subkey (error: %d)\n", algo);
+      return algo;
+    } else if (algo == GNUTLS_PK_RSA) {
+      
+      err("OpenPGP RSA subkey, with %d bits\n", bits);
+      ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(openpgp_crt, keyidx, &m, &e);
+      if (GNUTLS_E_SUCCESS != ret) {
+       err ("failed to export RSA subkey parameters (error: %d)\n", ret);
+       return 1;
+      }
+    } else if (algo == GNUTLS_PK_DSA) {
+      err("OpenPGP DSA subkey, with %d bits\n", bits);
+      ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(openpgp_crt, keyidx, &p, &q, &g, &y);
+      if (GNUTLS_E_SUCCESS != ret) {
+       err ("failed to export DSA subkey parameters (error: %d)\n", ret);
+       return 1;
+      }
+    } else {
+      err("OpenPGP subkey was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", algo);
+      return 1;
+    }
+  } 
+
+  /* now we have algo, and the various MPI data set.  Can we export
+     them cleanly? */
+
+  
+
+  gnutls_openpgp_crt_deinit(openpgp_crt);
+  gnutls_global_deinit();
+  return 0;
+}
diff --git a/main.c b/main.c
index 638666baabe416ec4c0dcdcbe528db373763bb96..d6bac685399c090749c50eeb33e80b4ad735b6ce 100644 (file)
--- a/main.c
+++ b/main.c
@@ -37,32 +37,13 @@ ssh-rsa AAAAB3NzaC1yc2EAAACBAL2BZCp+ueFAoNCc0P/d457Gl10Trgn7XOn19zIKN7sfkspyVvoM
  */
 
 
-
-int main(int argc, char* argv[]) {
-  gnutls_x509_privkey_t x509_privkey;
-  gnutls_datum_t data, test, clean;
-  int ret;
-
-  /*  
-      const char *certfile, *keyfile;
-      gnutls_certificate_credentials_t pgp_creds;
-  */
-  gnutls_datum_t m, e, d, p, q, u, g, y, x;
-
-  /*  gnutls_x509_crt_t crt; */
-
+int convert_pgp_to_x509(gnutls_x509_privkey_t* output, gnutls_datum_t* input) {
   gnutls_openpgp_privkey_t pgp_privkey;
+  gnutls_datum_t m, e, d, p, q, u, g, y, x;
   gnutls_pk_algorithm_t pgp_algo;
   unsigned int pgp_bits;
+  int ret;
 
-  char output_data[10240];
-  size_t ods = sizeof(output_data);
-
-  init_gnutls();
-  
-  init_datum(&data);
-  init_datum(&test);
-  init_datum(&clean);
   init_datum(&m);
   init_datum(&e);
   init_datum(&d);
@@ -73,48 +54,11 @@ int main(int argc, char* argv[]) {
   init_datum(&y);
   init_datum(&x);
 
-
-  if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) {
-    err("Failed to initialize X.509 private key (error: %d)\n", ret);
-    return 1;
-  }
-
   if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) {
     err("Failed to initialized OpenPGP private key (error: %d)\n", ret);
     return 1;
   }
 
-  /* slurp in the private key from stdin */
-  if (ret = set_datum_fd(&data, 0), ret) {
-    err("didn't read file descriptor 0\n");
-    return 1;
-  }
-
-  /* Or, instead, read in key from a file name: 
-  if (ret = set_datum_file(&data, argv[1]), ret) {
-    err("didn't read file '%s'\n", argv[1]);
-    return 1;
-  }
-*/
-
-  /* treat the passed file as an X.509 private key, and extract its
-     component values: */
-
-/*   if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) { */
-/*     err("Failed to import the X.509 key (error: %d)\n", ret); */
-/*     return 1; */
-/*   } */
-/*   gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); */
-
-  /* try to print the PEM-encoded private key: */
-/*   ret = gnutls_x509_privkey_export (x509_privkey, */
-/*                                 GNUTLS_X509_FMT_PEM, */
-/*                                 output_data, */
-/*                                 &ods); */
-/*   printf("ret: %u; ods: %u;\n", ret, ods); */
-/*   if (ret == 0) { */
-/*     write(0, output_data, ods); */
-/*   } */
 
   /* format could be either: GNUTLS_OPENPGP_FMT_RAW,
      GNUTLS_OPENPGP_FMT_BASE64; if MONKEYSPHERE_RAW is set, use RAW,
@@ -122,11 +66,11 @@ int main(int argc, char* argv[]) {
 
   if (getenv("MONKEYSPHERE_RAW")) {
     err("assuming RAW formatted private keys\n");
-    if (ret = gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0), ret)
+    if (ret = gnutls_openpgp_privkey_import(pgp_privkey, input, GNUTLS_OPENPGP_FMT_RAW, NULL, 0), ret)
       err("failed to import the OpenPGP private key in RAW format (error: %d)\n", ret);
   } else {
     err("assuming BASE64 formatted private keys\n");
-    if (ret = gnutls_openpgp_privkey_import (pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0), ret)
+    if (ret = gnutls_openpgp_privkey_import (pgp_privkey, input, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0), ret)
       err("failed to import the OpenPGP private key in BASE64 format (error: %d)\n", ret);
   }
 
@@ -143,7 +87,7 @@ int main(int argc, char* argv[]) {
       return 1;
     }
 
-    ret = gnutls_x509_privkey_import_rsa_raw (x509_privkey, &m, &e, &d, &p, &q, &u); 
+    ret = gnutls_x509_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); 
     if (GNUTLS_E_SUCCESS != ret) {
       err ("failed to import RSA key parameters (error: %d)\n", ret);
       return 1;
@@ -156,7 +100,7 @@ int main(int argc, char* argv[]) {
       return 1;
     }
 
-    ret = gnutls_x509_privkey_import_dsa_raw (x509_privkey, &p, &q, &g, &y, &x); 
+    ret = gnutls_x509_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); 
     if (GNUTLS_E_SUCCESS != ret) {
       err ("failed to import DSA key parameters (error: %d)\n", ret);
       return 1;
@@ -166,11 +110,151 @@ int main(int argc, char* argv[]) {
     return 1;
   }
   
-  ret = gnutls_x509_privkey_fix(x509_privkey);
+  ret = gnutls_x509_privkey_fix(*output);
   if (ret != 0) {
     err("failed to fix up the private key in X.509 format (error: %d)\n", ret);
     return 1; 
   }
+
+  gnutls_openpgp_privkey_deinit(pgp_privkey);
+  return 0;
+}
+
+int convert_x509_to_pgp(gnutls_openpgp_privkey_t* output, gnutls_datum_t* input) {
+  gnutls_x509_privkey_t x509_privkey;
+  gnutls_datum_t m, e, d, p, q, u, g, y, x;
+  gnutls_pk_algorithm_t x509_algo;
+  int ret;
+
+  init_datum(&m);
+  init_datum(&e);
+  init_datum(&d);
+  init_datum(&p);
+  init_datum(&q);
+  init_datum(&u);
+  init_datum(&g);
+  init_datum(&y);
+  init_datum(&x);
+
+  if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) {
+    err("Failed to initialized X.509 private key (error: %d)\n", ret);
+    return 1;
+  }
+
+
+  /* format could be either:     GNUTLS_X509_FMT_DER,
+    GNUTLS_X509_FMT_PEM; if MONKEYSPHERE_DER is set, use DER,
+     otherwise, use PEM: */
+
+  if (getenv("MONKEYSPHERE_DER")) {
+    err("assuming DER formatted private keys\n");
+    if (ret = gnutls_x509_privkey_import(x509_privkey, input, GNUTLS_X509_FMT_DER), ret)
+      err("failed to import the X.509 private key in DER format (error: %d)\n", ret);
+  } else {
+    err("assuming PEM formatted private keys\n");
+    if (ret = gnutls_x509_privkey_import (x509_privkey, input, GNUTLS_X509_FMT_PEM), ret)
+      err("failed to import the X.509 private key in PEM format (error: %d)\n", ret);
+  }
+
+  x509_algo = gnutls_x509_privkey_get_pk_algorithm(x509_privkey);
+  if (x509_algo < 0) {
+    err("failed to get X.509 key algorithm (error: %d)\n", x509_algo);
+    return 1;
+  }
+  if (x509_algo == GNUTLS_PK_RSA) {
+    err("X.509 RSA Key\n");
+    ret = gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u);
+    if (GNUTLS_E_SUCCESS != ret) {
+      err ("failed to export RSA key parameters (error: %d)\n", ret);
+      return 1;
+    }
+
+    /*     ret = gnutls_openpgp_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u);  */
+    ret = GNUTLS_E_UNIMPLEMENTED_FEATURE;
+    if (GNUTLS_E_SUCCESS != ret) {
+      err ("failed to import RSA key parameters (error: %d)\n", ret);
+      return 1;
+    }
+  } else if (x509_algo == GNUTLS_PK_DSA) {
+    err("X.509 DSA Key\n");
+    ret = gnutls_x509_privkey_export_dsa_raw(x509_privkey, &p, &q, &g, &y, &x);
+    if (GNUTLS_E_SUCCESS != ret) {
+      err ("failed to export DSA key parameters (error: %d)\n", ret);
+      return 1;
+    }
+
+    /*    ret = gnutls_openpgp_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x);  */
+    ret = GNUTLS_E_UNIMPLEMENTED_FEATURE;
+    if (GNUTLS_E_SUCCESS != ret) {
+      err ("failed to import DSA key parameters (error: %d)\n", ret);
+      return 1;
+    }
+  } else {
+    err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", x509_algo);
+    return 1;
+  }
+  
+  gnutls_x509_privkey_deinit(x509_privkey);
+  return 0;
+}
+
+
+int main(int argc, char* argv[]) {
+  gnutls_datum_t data;
+  int ret;
+  gnutls_x509_privkey_t x509_privkey;
+
+  char output_data[10240];
+  size_t ods = sizeof(output_data);
+
+  init_gnutls();
+  
+  init_datum(&data);
+
+  /* slurp in the private key from stdin */
+  if (ret = set_datum_fd(&data, 0), ret) {
+    err("didn't read file descriptor 0\n");
+    return 1;
+  }
+
+
+
+  /* Or, instead, read in key from a file name: 
+  if (ret = set_datum_file(&data, argv[1]), ret) {
+    err("didn't read file '%s'\n", argv[1]);
+    return 1;
+  }
+*/
+
+  /* treat the passed file as an X.509 private key, and extract its
+     component values: */
+
+/*   if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) { */
+/*     err("Failed to import the X.509 key (error: %d)\n", ret); */
+/*     return 1; */
+/*   } */
+/*   gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); */
+
+  /* try to print the PEM-encoded private key: */
+/*   ret = gnutls_x509_privkey_export (x509_privkey, */
+/*                                 GNUTLS_X509_FMT_PEM, */
+/*                                 output_data, */
+/*                                 &ods); */
+/*   printf("ret: %u; ods: %u;\n", ret, ods); */
+/*   if (ret == 0) { */
+/*     write(0, output_data, ods); */
+/*   } */
+
+
+  if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) {
+    err("Failed to initialize X.509 private key (error: %d)\n", ret);
+    return 1;
+  }
+
+  if (ret = convert_pgp_to_x509(&x509_privkey, &data), ret) {
+    return ret;
+  }
+
   ret = gnutls_x509_privkey_export (x509_privkey,
                                    GNUTLS_X509_FMT_PEM,
                                    output_data,
@@ -182,7 +266,6 @@ int main(int argc, char* argv[]) {
 
 
   gnutls_x509_privkey_deinit(x509_privkey);
-  gnutls_openpgp_privkey_deinit(pgp_privkey);
   gnutls_global_deinit();
   return 0;
 }