initial pass at converting PEM-encoded private keys into OpenPGP public keys.
authorDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Mon, 21 Apr 2008 07:10:19 +0000 (03:10 -0400)
committerDaniel Kahn Gillmor <dkg@fifthhorseman.net>
Mon, 21 Apr 2008 07:10:19 +0000 (03:10 -0400)
.gitignore
Makefile
gnutls-helpers.c
gnutls-helpers.h
ssh2gpg.c [new file with mode: 0644]

index ae9fbd8fc60e6a7ceb6ad9991cf9a03f79ea2ffb..80bf65d079a0fd09d8b7292fd3b2846f1ab16765 100644 (file)
@@ -2,3 +2,4 @@
 *.[ao]
 monkeysphere
 gpg2ssh
+ssh2gpg
index 4fb4556c493bc7cadf3183566857fabf81f3f121..aa18aaad5a2e096e9456008fcafc41f65d45bb64 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,9 @@ monkeysphere: main.c 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
 
+ssh2gpg: ssh2gpg.c gnutls-helpers.o
+       gcc -g -Wall --pedantic -o ssh2gpg ssh2gpg.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o
+
 %.o: %.c
        gcc -g -Wall --pedantic -o $@ -c $<
 
index 5a567e2940ef9376f35a535382f31107e0b0e640..6eae29e0aa3a32352c62212587a33118d5ca973f 100644 (file)
@@ -345,3 +345,20 @@ int validate_ssh_host_userid(const char* userid) {
   setlocale(LC_ALL, oldlocale);
   return 1;
 }
+
+/* http://tools.ietf.org/html/rfc4880#section-5.5.2 */
+size_t get_openpgp_mpi_size(gnutls_datum_t* d) {
+  return 2 + d->size;
+}
+
+int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) {
+  uint16_t x;
+
+  x = d->size * 8;
+  x = htons(x);
+  
+  write(fd, &x, sizeof(x));
+  write(fd, d->data, d->size);
+  
+  return 0;
+}
index 398413fdfd2015e2bdd6389db5418102a06af624..9ea22a3491c90e8266fcd5a9b4c3c7c3f9a9732d 100644 (file)
@@ -54,7 +54,7 @@ int set_datum_string(gnutls_datum_t* d, const char* s);
    datum */
 int set_datum_fd(gnutls_datum_t* d, int fd);
 
-/* read the file indicated (by na1me) in the fname parameter.  store
+/* read the file indicated (by name) in the fname parameter.  store
    its entire contents in a single datum. */
 int set_datum_file(gnutls_datum_t* d, const char* fname);
 
@@ -64,3 +64,9 @@ int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]);
 
 /* return 0 if userid matches the monkeysphere spec for ssh host user IDs */
 int validate_ssh_host_userid(const char* userid);
+
+/* how many bytes will it take to write out this datum in OpenPGP MPI form? */
+size_t get_openpgp_mpi_size(gnutls_datum_t* d);
+
+/* write the MPI stored in gnutls_datum_t to file descriptor fd: */
+int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d);
diff --git a/ssh2gpg.c b/ssh2gpg.c
new file mode 100644 (file)
index 0000000..b14a540
--- /dev/null
+++ b/ssh2gpg.c
@@ -0,0 +1,171 @@
+#include "gnutls-helpers.h"
+
+#include <gnutls/openpgp.h>
+#include <gnutls/x509.h>
+
+/* for waitpid() */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+/* for time() */
+#include <time.h>
+
+/* for htons() */
+#include <arpa/inet.h>
+
+
+/* 
+   Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
+   Date: Sun, 2008-04-20
+   License: GPL v3 or later
+
+   monkeysphere public key translator: execute this with an ssh
+   private key on stdin.  It currently only works with RSA keys.
+
+   it should eventually work with OpenSSH-style public keys instead of
+   the full private key, but it was easier to do this way.
+
+   It shoud spit out a version of the public key suitable for acting
+   as an OpenPGP public sub key packet.
+
+ */
+
+int main(int argc, char* argv[]) {
+  gnutls_datum_t data;
+  int ret;
+  gnutls_x509_privkey_t x509_privkey;
+  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;
+
+  unsigned char packettag;
+  unsigned char openpgpversion;
+  time_t timestamp;
+  uint32_t clunkytime;
+  unsigned char openpgpalgo;
+  unsigned int packetlen;
+  uint16_t plen;
+
+  gnutls_datum_t m, e, d, p, q, u, g, y;
+  gnutls_datum_t algolabel;
+
+  char output_data[10240];
+  char userid[10240];
+  size_t uidsz = sizeof(userid);
+
+  const gnutls_datum_t* all[5];
+  int pipefd;
+  pid_t child_pid;
+  char* const args[] = {"/usr/bin/base64", "--wrap=0", NULL};
+  const char* algoname;
+  int mpicount;
+  int pipestatus;
+
+  init_gnutls();
+  
+  init_datum(&data);
+
+  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(&algolabel);
+
+  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_x509_privkey_init(&x509_privkey), ret) {
+    err("Failed to initialize private key structure (error: %d)\n", ret);
+    return 1;
+  }
+
+  err("assuming PEM formatted private key\n");
+  if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) {
+    err("failed to import the PEM-encoded private key (error: %d)\n", ret);
+    return ret;
+  }
+
+  algo = gnutls_x509_privkey_get_pk_algorithm(x509_privkey);
+  if (algo < 0) {
+    err("failed to get the algorithm of the PEM-encoded public key (error: %d)\n", algo);
+    return algo;
+  } else if (algo == GNUTLS_PK_RSA) {
+    err("RSA private 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;
+    }
+    err("Modulus size %d, exponent size %d\n", m.size, e.size);
+  } else if (algo == GNUTLS_PK_DSA) {
+    err("DSA Key, not implemented!!\n", bits);
+    return 1;
+  } else {
+    err("Key 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 are set.  Can we
+     export them as a public subkey packet? */
+
+  /* this packet should be tagged 14, and should contain:
+
+     1 octet: version (4)
+     4 octets: time of generation (seconds since 1970)
+     1 octet: algo (http://tools.ietf.org/html/rfc4880#section-5.5.2 implies 1 for RSA)
+     MPI: modulus
+     MPI: exponent
+  */
+
+  packetlen = 1 + 4 + 1;
+  /* FIXME: this is RSA only.  for DSA, there'll be more: */
+  packetlen += get_openpgp_mpi_size(&m) + get_openpgp_mpi_size(&e);
+
+  /* FIXME: we should generate this bound more cleanly -- i just
+     happen to know that 65535 is 2^16-1: */
+  if (packetlen > 65535) {
+    err("packet length is too long (%d)\n", packetlen);
+    return 1;
+  }
+
+  /* we're going to emit an old-style packet, with tag 14 (public
+     subkey), with a two-octet packet length */
+  packettag = 0x80 | (14 << 2) | 1;
+  
+  write(1, &packettag, sizeof(packettag));
+  plen = htons(packetlen);
+  write(1, &plen, sizeof(plen));
+
+  openpgpversion = 4;
+  write(1, &openpgpversion, 1);
+
+  timestamp = time(NULL);
+  clunkytime = htonl(timestamp);
+  write(1, &clunkytime, 4);
+
+  /* FIXME: handle things other than RSA */
+  openpgpalgo = 1;
+  write(1, &openpgpalgo, 1);
+  
+  write_openpgp_mpi_to_fd(1, &m);
+  write_openpgp_mpi_to_fd(1, &e);
+
+  gnutls_x509_privkey_deinit(x509_privkey);
+  gnutls_global_deinit();
+  return 0;
+}