From: Daniel Kahn Gillmor Date: Thu, 12 Jun 2008 17:31:13 +0000 (-0400) Subject: reorganizing to prepare for transition to openpgp2ssh. X-Git-Tag: monkeysphere_0.1-1~47 X-Git-Url: https://codewiz.org/gitweb?p=monkeysphere.git;a=commitdiff_plain;h=00719770b7608af0c37dfd6c14a04eb7ebc4cad0 reorganizing to prepare for transition to openpgp2ssh. --- diff --git a/src/gpg2ssh/Makefile b/src/gpg2ssh/Makefile deleted file mode 100644 index 65a5f0f..0000000 --- a/src/gpg2ssh/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -all: monkeysphere gpg2ssh ssh2gpg - -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 - -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 $< - -clean: - rm -f monkeysphere gpg2ssh ssh2gpg *.o - -.PHONY: clean all diff --git a/src/gpg2ssh/gnutls-helpers.c b/src/gpg2ssh/gnutls-helpers.c deleted file mode 100644 index 6eae29e..0000000 --- a/src/gpg2ssh/gnutls-helpers.c +++ /dev/null @@ -1,364 +0,0 @@ -/* Author: Daniel Kahn Gillmor */ -/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ -/* License: GPL v3 or later */ - -#include "gnutls-helpers.h" -/* for htonl() */ -#include - -/* for setlocale() */ -#include - -/* for isalnum() */ -#include - -int loglevel = 0; - - -void err(const char* fmt, ...) { - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - fflush(stderr); -} - -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() { - const char* version = NULL; - const char* debug_string = NULL; - int ret; - - if (ret = gnutls_global_init(), ret) { - err("Failed to do gnutls_global_init() (error: %d)\n", ret); - return 1; - } - - version = gnutls_check_version(NULL); - - if (version) - err("gnutls version: %s\n", version); - else { - err("no version found!\n"); - return 1; - } - - if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) { - loglevel = atoi(debug_string); - gnutls_global_set_log_function(logfunc); - - gnutls_global_set_log_level(loglevel); - err("set log level to %d\n", loglevel); - } - return 0; -} - -void init_datum(gnutls_datum_t* d) { - d->data = NULL; - d->size = 0; -} -void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) { - dest->data = gnutls_realloc(dest->data, src->size); - dest->size = src->size; - memcpy(dest->data, src->data, src->size); -} -int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) { - if (a->size > b->size) { - err("a is larger\n"); - return 1; - } - if (a->size < b->size) { - err("b is larger\n"); - return -1; - } - return memcmp(a->data, b->data, a->size); -} -void free_datum(gnutls_datum_t* d) { - gnutls_free(d->data); - d->data = NULL; - d->size = 0; -} - -/* read the passed-in string, store in a single datum */ -int set_datum_string(gnutls_datum_t* d, const char* s) { - unsigned int x = strlen(s)+1; - unsigned char* c = NULL; - - c = gnutls_realloc(d->data, x); - if (NULL == c) - return -1; - d->data = c; - d->size = x; - memcpy(d->data, s, x); - return 0; -} - -/* read the passed-in file descriptor until EOF, store in a single - datum */ -int set_datum_fd(gnutls_datum_t* d, int fd) { - unsigned int bufsize = 1024; - unsigned int len = 0; - - FILE* f = fdopen(fd, "r"); - if (bufsize > d->size) { - bufsize = 1024; - d->data = gnutls_realloc(d->data, bufsize); - if (d->data == NULL) { - err("out of memory!\n"); - return -1; - } - d->size = bufsize; - } else { - bufsize = d->size; - } - f = fdopen(fd, "r"); - if (NULL == f) { - err("could not fdopen FD %d\n", fd); - } - clearerr(f); - while (!feof(f) && !ferror(f)) { - if (len == bufsize) { - /* allocate more space by doubling: */ - bufsize *= 2; - d->data = gnutls_realloc(d->data, bufsize); - if (d->data == NULL) { - err("out of memory!\n"); - return -1; - }; - d->size = bufsize; - } - len += fread(d->data + len, 1, bufsize - len, f); - /* err("read %d bytes\n", len); */ - } - if (ferror(f)) { - err("Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno)); - return -1; - } - - /* touch up buffer size to match reality: */ - d->data = gnutls_realloc(d->data, len); - d->size = len; - return 0; -} - -/* 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) { - struct stat sbuf; - unsigned char* c = NULL; - FILE* file = NULL; - size_t x = 0; - - if (0 != stat(fname, &sbuf)) { - err("failed to stat '%s'\n", fname); - return -1; - } - - c = gnutls_realloc(d->data, sbuf.st_size); - if (NULL == c) { - err("failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname); - return -1; - } - - d->data = c; - d->size = sbuf.st_size; - file = fopen(fname, "r"); - if (NULL == file) { - err("failed to open '%s' for reading\n", fname); - return -1; - } - - x = fread(d->data, d->size, 1, file); - if (x != 1) { - err("tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname); - fclose(file); - return -1; - } - fclose(file); - return 0; -} - -int write_datum_fd(int fd, const gnutls_datum_t* d) { - if (d->size != write(fd, d->data, d->size)) { - err("failed to write body of datum.\n"); - return -1; - } - return 0; -} - - -int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) { - uint32_t len; - int looks_negative = (d->data[0] & 0x80); - unsigned char zero = 0; - - /* if the first bit is 1, then the datum will appear negative in the - MPI encoding style used by OpenSSH. In that case, we'll increase - the length by one, and dump out one more byte */ - - if (looks_negative) { - len = htonl(d->size + 1); - } else { - len = htonl(d->size); - } - if (write(fd, &len, sizeof(len)) != sizeof(len)) { - err("failed to write size of datum.\n"); - return -2; - } - if (looks_negative) { - if (write(fd, &zero, 1) != 1) { - err("failed to write padding byte for MPI.\n"); - return -2; - } - } - return write_datum_fd(fd, d); -} - -int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) { - unsigned int i; - int ret; - - for (i = 0; i < num; i++) - if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0) - return ret; - - return 0; -} - - -int datum_from_string(gnutls_datum_t* d, const char* str) { - d->size = strlen(str); - d->data = gnutls_realloc(d->data, d->size); - if (d->data == 0) - return ENOMEM; - memcpy(d->data, str, d->size); - return 0; -} - - -int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) { - int p[2]; - int ret; - - if (pid == NULL) { - err("bad pointer passed to create_writing_pipe()\n"); - return -1; - } - - if (ret = pipe(p), ret == -1) { - err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno)); - return -1; - } - - *pid = fork(); - if (*pid == -1) { - err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno)); - return -1; - } - if (*pid == 0) { /* this is the child */ - close(p[1]); /* close unused write end */ - - if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */ - err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno)); - exit(1); - } - execv(path, argv); - err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno)); - /* close the open file descriptors */ - close(p[0]); - close(0); - - exit(1); - } else { /* this is the parent */ - close(p[0]); /* close unused read end */ - return p[1]; - } -} - -int validate_ssh_host_userid(const char* userid) { - char* oldlocale = setlocale(LC_ALL, "C"); - - /* choke if userid does not match the expected format - ("ssh://fully.qualified.domain.name") */ - if (strncmp("ssh://", userid, strlen("ssh://")) != 0) { - err("The user ID should start with ssh:// for a host key\n"); - goto fail; - } - /* so that isalnum will work properly */ - userid += strlen("ssh://"); - while (0 != (*userid)) { - if (!isalnum(*userid)) { - err("label did not start with a letter or a digit! (%s)\n", userid); - goto fail; - } - userid++; - while (isalnum(*userid) || ('-' == (*userid))) - userid++; - if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label: - check last char - isalnum */ - if (!isalnum(*(userid - 1))) { - err("label did not end with a letter or a digit!\n"); - goto fail; - } - if ('.' == (*userid)) /* advance to the start of the next label */ - userid++; - } else { - err("invalid character in domain name: %c\n", *userid); - goto fail; - } - } - /* ensure that the last character is valid: */ - if (!isalnum(*(userid - 1))) { - err("hostname did not end with a letter or a digit!\n"); - goto fail; - } - /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we - make sure that we've got an OK string? */ - - return 0; - - fail: - 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; -} diff --git a/src/gpg2ssh/gnutls-helpers.h b/src/gpg2ssh/gnutls-helpers.h deleted file mode 100644 index 9ea22a3..0000000 --- a/src/gpg2ssh/gnutls-helpers.h +++ /dev/null @@ -1,72 +0,0 @@ -/* Author: Daniel Kahn Gillmor */ -/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ -/* License: GPL v3 or later */ - - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Functions to help dealing with GnuTLS for monkeysphere key - translation projects: */ - -/* set everything up, including logging levels. Return 0 on - success */ -int init_gnutls(); - -/* logging and output functions: */ - -void err(const char* fmt, ...); -void logfunc(int level, const char* string); - -/* basic datum manipulations: */ - -void init_datum(gnutls_datum_t* d); -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); -int write_datum_fd(int fd, const gnutls_datum_t* d); -int write_datum_fd_with_length(int fd, const gnutls_datum_t* d); -int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num); - -/* set up a datum from a null-terminated string */ -int datum_from_string(gnutls_datum_t* d, const char* str); - -/* 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 */ -int set_datum_string(gnutls_datum_t* d, const char* s); - -/* read the passed-in file descriptor until EOF, store in a single - datum */ -int set_datum_fd(gnutls_datum_t* d, int fd); - -/* 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); - -/* set up file descriptor pipe for writing (child process pid gets - stored in pid, fd is returned)*/ -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/src/gpg2ssh/gpg2ssh.c b/src/gpg2ssh/gpg2ssh.c deleted file mode 100644 index c99f03f..0000000 --- a/src/gpg2ssh/gpg2ssh.c +++ /dev/null @@ -1,293 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* for waitpid() */ -#include -#include - -/* - Author: Daniel Kahn Gillmor - 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; - 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(&p); - init_datum(&q); - 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_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: */ - - /* FIXME: we should be auto-detecting the input format, and - translating it as needed. */ - - 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; - } - - /* FIXME: We're currently looking at the primary key or maybe the - first authentication-capable subkey. - - Instead, we should be iterating through the primary key and all - subkeys: for each one with the authentication usage flag set of a - algorithm we can handle, we should output matching UserIDs and - the SSH version of the key. */ - - - 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 && - usage & GNUTLS_KEY_KEY_ENCIPHERMENT) { - err("the primary key can be used for authentication and communication encryption!\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 not good for authentication and communication encryption. Trying subkeys...\n"); - - if (ret = gnutls_openpgp_crt_get_auth_subkey(openpgp_crt, keyid, 0), ret) { - err("failed to find a subkey capable of authentication and communication encryption (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 && - usage & GNUTLS_KEY_KEY_ENCIPHERMENT) { - err("could not find a subkey with authentication and communication encryption.\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; - } - } - - /* make sure userid is NULL-terminated */ - userid[sizeof(userid) - 1] = 0; - uidsz--; - - /* FIXME: we're just choosing the first UserID from the certificate: - instead, we should be selecting every User ID that is adequately - signed and matches the spec, and aggregating them with commas for - known_hosts output */ - - if (ret = gnutls_openpgp_crt_get_name(openpgp_crt, 0, userid, &uidsz), ret) { - err("Failed to fetch the first UserID (error: %d)\n", ret); - return ret; - } - - if (ret = validate_ssh_host_userid(userid), ret) { - err("bad userid: not a valid ssh host.\n"); - return ret; - } - - /* remove ssh:// from the beginning of userid */ - memmove(userid, userid + strlen("ssh://"), 1 + strlen(userid) - strlen("ssh://")); - - - /* now we have algo, and the various MPI data are set. Can we - export them cleanly? */ - - /* for the moment, we'll just dump the info raw, and pipe it - externally through coreutils' /usr/bin/base64 */ - - if (algo == GNUTLS_PK_RSA) { - algoname = "ssh-rsa"; - mpicount = 3; - - all[0] = &algolabel; - all[1] = &e; - all[2] = &m; - } else if (algo == GNUTLS_PK_DSA) { - algoname = "ssh-dss"; - mpicount = 5; - - all[0] = &algolabel; - all[1] = &p; - all[2] = &q; - all[3] = &g; - all[4] = &y; - } else { - err("no idea what this algorithm is: %d\n", algo); - return 1; - } - - if (ret = datum_from_string(&algolabel, algoname), ret) { - err("couldn't label string (error: %d)\n", ret); - return ret; - } - - snprintf(output_data, sizeof(output_data), "%s %s ", userid, algoname); - - pipefd = create_writing_pipe(&child_pid, args[0], args); - if (pipefd < 0) { - err("failed to create a writing pipe (returned %d)\n", pipefd); - return pipefd; - } - - write(1, output_data, strlen(output_data)); - - if (0 != write_data_fd_with_length(pipefd, all, mpicount)) { - err("was not able to write out RSA key data\n"); - return 1; - } - close(pipefd); - if (child_pid != waitpid(child_pid, &pipestatus, 0)) { - err("could not wait for child process to return for some reason.\n"); - return 1; - } - if (pipestatus != 0) { - err("base64 pipe died with return code %d\n", pipestatus); - return pipestatus; - } - - write(1, "\n", 1); - - - - gnutls_openpgp_crt_deinit(openpgp_crt); - gnutls_global_deinit(); - return 0; -} diff --git a/src/gpg2ssh/main.c b/src/gpg2ssh/main.c deleted file mode 100644 index d6bac68..0000000 --- a/src/gpg2ssh/main.c +++ /dev/null @@ -1,271 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* - Author: Daniel Kahn Gillmor - Date: Tue, 01 Apr 2008 - License: GPL v3 or later - - monkeysphere private key translator: execute this with an GPG - secret key on stdin (at the moment, only passphraseless RSA keys - work). - - It will spit out a PEM-encoded version of the key on stdout, which - can be fed into ssh-add like this: - - gpg --export-secret-keys $KEYID | monkeysphere | ssh-add -c /dev/stdin - - Requirements: I've only built this so far with GnuTLS v2.3.4 -- - version 2.2.0 does not contain the appropriate pieces. - - Notes: gpgkey2ssh doesn't seem to provide the same public - keys. Mighty weird! - -0 wt215@squeak:~/monkeysphere$ gpg --export-secret-keys 1DCDF89F | ~dkg/src/monkeysphere/monkeysphere | ssh-add -c /dev/stdin -gnutls version: 2.3.4 -OpenPGP RSA Key, with 1024 bits -Identity added: /dev/stdin (/dev/stdin) -The user has to confirm each use of the key -0 wt215@squeak:~/monkeysphere$ ssh-add -L -ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9gWQqfrnhQKDQnND/3eOexpddE64J+1zp9fcyCje7H5LKclb6DBV2HS6WgW32PJhIzvP+fYZM3dzXea3fpv14y1SicXiRBDgF9SnsNA1qWn2RyzkLcKy7PmM0PDYtU1oiLTcQj/xkWcqW2sLKHT/WW+vZP5XP7RMGN/yWNMfE2Q== /dev/stdin -0 wt215@squeak:~/monkeysphere$ gpgkey2ssh 1DCDF89F -ssh-rsa AAAAB3NzaC1yc2EAAACBAL2BZCp+ueFAoNCc0P/d457Gl10Trgn7XOn19zIKN7sfkspyVvoMFXYdLpaBbfY8mEjO8/59hkzd3Nd5rd+m/XjLVKJxeJEEOAX1Kew0DWpafZHLOQtwrLs+YzQ8Ni1TWiItNxCP/GRZypbawsodP9Zb69k/lc/tEwY3/JY0x8TZAAAAAwEAAQ== COMMENT -0 wt215@squeak:~/monkeysphere$ - - */ - - -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; - - 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_openpgp_privkey_init(&pgp_privkey), ret) { - err("Failed to initialized OpenPGP private key (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 private keys\n"); - 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, input, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0), ret) - err("failed to import the OpenPGP private key in BASE64 format (error: %d)\n", ret); - } - - pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(pgp_privkey, &pgp_bits); - if (pgp_algo < 0) { - err("failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo); - return 1; - } - if (pgp_algo == GNUTLS_PK_RSA) { - err("OpenPGP RSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_rsa_raw(pgp_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_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; - } - } else if (pgp_algo == GNUTLS_PK_DSA) { - err("OpenPGP DSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_dsa_raw(pgp_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_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; - } - } else { - err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", pgp_algo); - return 1; - } - - 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, - &ods); - printf("ret: %u; ods: %u;\n", ret, ods); - if (ret == 0) { - write(1, output_data, ods); - } - - - gnutls_x509_privkey_deinit(x509_privkey); - gnutls_global_deinit(); - return 0; -} diff --git a/src/gpg2ssh/ssh2gpg.c b/src/gpg2ssh/ssh2gpg.c deleted file mode 100644 index b14a540..0000000 --- a/src/gpg2ssh/ssh2gpg.c +++ /dev/null @@ -1,171 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* for waitpid() */ -#include -#include - -/* for time() */ -#include - -/* for htons() */ -#include - - -/* - Author: Daniel Kahn Gillmor - 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; -} diff --git a/src/keytrans/Makefile b/src/keytrans/Makefile new file mode 100644 index 0000000..fdd4ccd --- /dev/null +++ b/src/keytrans/Makefile @@ -0,0 +1,19 @@ +all: openpgp2ssh + +openpgp2ssh: openpgp2ssh.c gnutls-helpers.o + gcc -g -Wall --pedantic -o openpgp2ssh openpgp2ssh.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o + +%.o: %.c + gcc -g -Wall --pedantic -o $@ -c $< + +clean: + rm -f openpgp2ssh gpg2ssh ssh2gpg *.o + +# deprecated: +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 + +.PHONY: clean all diff --git a/src/keytrans/gnutls-helpers.c b/src/keytrans/gnutls-helpers.c new file mode 100644 index 0000000..6eae29e --- /dev/null +++ b/src/keytrans/gnutls-helpers.c @@ -0,0 +1,364 @@ +/* Author: Daniel Kahn Gillmor */ +/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ +/* License: GPL v3 or later */ + +#include "gnutls-helpers.h" +/* for htonl() */ +#include + +/* for setlocale() */ +#include + +/* for isalnum() */ +#include + +int loglevel = 0; + + +void err(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); +} + +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() { + const char* version = NULL; + const char* debug_string = NULL; + int ret; + + if (ret = gnutls_global_init(), ret) { + err("Failed to do gnutls_global_init() (error: %d)\n", ret); + return 1; + } + + version = gnutls_check_version(NULL); + + if (version) + err("gnutls version: %s\n", version); + else { + err("no version found!\n"); + return 1; + } + + if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) { + loglevel = atoi(debug_string); + gnutls_global_set_log_function(logfunc); + + gnutls_global_set_log_level(loglevel); + err("set log level to %d\n", loglevel); + } + return 0; +} + +void init_datum(gnutls_datum_t* d) { + d->data = NULL; + d->size = 0; +} +void copy_datum(gnutls_datum_t* dest, const gnutls_datum_t* src) { + dest->data = gnutls_realloc(dest->data, src->size); + dest->size = src->size; + memcpy(dest->data, src->data, src->size); +} +int compare_data(const gnutls_datum_t* a, const gnutls_datum_t* b) { + if (a->size > b->size) { + err("a is larger\n"); + return 1; + } + if (a->size < b->size) { + err("b is larger\n"); + return -1; + } + return memcmp(a->data, b->data, a->size); +} +void free_datum(gnutls_datum_t* d) { + gnutls_free(d->data); + d->data = NULL; + d->size = 0; +} + +/* read the passed-in string, store in a single datum */ +int set_datum_string(gnutls_datum_t* d, const char* s) { + unsigned int x = strlen(s)+1; + unsigned char* c = NULL; + + c = gnutls_realloc(d->data, x); + if (NULL == c) + return -1; + d->data = c; + d->size = x; + memcpy(d->data, s, x); + return 0; +} + +/* read the passed-in file descriptor until EOF, store in a single + datum */ +int set_datum_fd(gnutls_datum_t* d, int fd) { + unsigned int bufsize = 1024; + unsigned int len = 0; + + FILE* f = fdopen(fd, "r"); + if (bufsize > d->size) { + bufsize = 1024; + d->data = gnutls_realloc(d->data, bufsize); + if (d->data == NULL) { + err("out of memory!\n"); + return -1; + } + d->size = bufsize; + } else { + bufsize = d->size; + } + f = fdopen(fd, "r"); + if (NULL == f) { + err("could not fdopen FD %d\n", fd); + } + clearerr(f); + while (!feof(f) && !ferror(f)) { + if (len == bufsize) { + /* allocate more space by doubling: */ + bufsize *= 2; + d->data = gnutls_realloc(d->data, bufsize); + if (d->data == NULL) { + err("out of memory!\n"); + return -1; + }; + d->size = bufsize; + } + len += fread(d->data + len, 1, bufsize - len, f); + /* err("read %d bytes\n", len); */ + } + if (ferror(f)) { + err("Error reading from fd %d (error: %d) (error: %d '%s')\n", fd, ferror(f), errno, strerror(errno)); + return -1; + } + + /* touch up buffer size to match reality: */ + d->data = gnutls_realloc(d->data, len); + d->size = len; + return 0; +} + +/* 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) { + struct stat sbuf; + unsigned char* c = NULL; + FILE* file = NULL; + size_t x = 0; + + if (0 != stat(fname, &sbuf)) { + err("failed to stat '%s'\n", fname); + return -1; + } + + c = gnutls_realloc(d->data, sbuf.st_size); + if (NULL == c) { + err("failed to allocate %d bytes for '%s'\n", sbuf.st_size, fname); + return -1; + } + + d->data = c; + d->size = sbuf.st_size; + file = fopen(fname, "r"); + if (NULL == file) { + err("failed to open '%s' for reading\n", fname); + return -1; + } + + x = fread(d->data, d->size, 1, file); + if (x != 1) { + err("tried to read %d bytes, read %d instead from '%s'\n", d->size, x, fname); + fclose(file); + return -1; + } + fclose(file); + return 0; +} + +int write_datum_fd(int fd, const gnutls_datum_t* d) { + if (d->size != write(fd, d->data, d->size)) { + err("failed to write body of datum.\n"); + return -1; + } + return 0; +} + + +int write_datum_fd_with_length(int fd, const gnutls_datum_t* d) { + uint32_t len; + int looks_negative = (d->data[0] & 0x80); + unsigned char zero = 0; + + /* if the first bit is 1, then the datum will appear negative in the + MPI encoding style used by OpenSSH. In that case, we'll increase + the length by one, and dump out one more byte */ + + if (looks_negative) { + len = htonl(d->size + 1); + } else { + len = htonl(d->size); + } + if (write(fd, &len, sizeof(len)) != sizeof(len)) { + err("failed to write size of datum.\n"); + return -2; + } + if (looks_negative) { + if (write(fd, &zero, 1) != 1) { + err("failed to write padding byte for MPI.\n"); + return -2; + } + } + return write_datum_fd(fd, d); +} + +int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num) { + unsigned int i; + int ret; + + for (i = 0; i < num; i++) + if (ret = write_datum_fd_with_length(fd, d[i]), ret != 0) + return ret; + + return 0; +} + + +int datum_from_string(gnutls_datum_t* d, const char* str) { + d->size = strlen(str); + d->data = gnutls_realloc(d->data, d->size); + if (d->data == 0) + return ENOMEM; + memcpy(d->data, str, d->size); + return 0; +} + + +int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]) { + int p[2]; + int ret; + + if (pid == NULL) { + err("bad pointer passed to create_writing_pipe()\n"); + return -1; + } + + if (ret = pipe(p), ret == -1) { + err("failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno)); + return -1; + } + + *pid = fork(); + if (*pid == -1) { + err("Failed to fork (error: %d \"%s\")\n", errno, strerror(errno)); + return -1; + } + if (*pid == 0) { /* this is the child */ + close(p[1]); /* close unused write end */ + + if (0 != dup2(p[0], 0)) { /* map the reading end into stdin */ + err("Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno)); + exit(1); + } + execv(path, argv); + err("exec %s failed (error: %d \"%s\")\n", path, errno, strerror(errno)); + /* close the open file descriptors */ + close(p[0]); + close(0); + + exit(1); + } else { /* this is the parent */ + close(p[0]); /* close unused read end */ + return p[1]; + } +} + +int validate_ssh_host_userid(const char* userid) { + char* oldlocale = setlocale(LC_ALL, "C"); + + /* choke if userid does not match the expected format + ("ssh://fully.qualified.domain.name") */ + if (strncmp("ssh://", userid, strlen("ssh://")) != 0) { + err("The user ID should start with ssh:// for a host key\n"); + goto fail; + } + /* so that isalnum will work properly */ + userid += strlen("ssh://"); + while (0 != (*userid)) { + if (!isalnum(*userid)) { + err("label did not start with a letter or a digit! (%s)\n", userid); + goto fail; + } + userid++; + while (isalnum(*userid) || ('-' == (*userid))) + userid++; + if (('.' == (*userid)) || (0 == (*userid))) { /* clean end of label: + check last char + isalnum */ + if (!isalnum(*(userid - 1))) { + err("label did not end with a letter or a digit!\n"); + goto fail; + } + if ('.' == (*userid)) /* advance to the start of the next label */ + userid++; + } else { + err("invalid character in domain name: %c\n", *userid); + goto fail; + } + } + /* ensure that the last character is valid: */ + if (!isalnum(*(userid - 1))) { + err("hostname did not end with a letter or a digit!\n"); + goto fail; + } + /* FIXME: fqdn's can be unicode now, thanks to RFC 3490 -- how do we + make sure that we've got an OK string? */ + + return 0; + + fail: + 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; +} diff --git a/src/keytrans/gnutls-helpers.h b/src/keytrans/gnutls-helpers.h new file mode 100644 index 0000000..9ea22a3 --- /dev/null +++ b/src/keytrans/gnutls-helpers.h @@ -0,0 +1,72 @@ +/* Author: Daniel Kahn Gillmor */ +/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ +/* License: GPL v3 or later */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Functions to help dealing with GnuTLS for monkeysphere key + translation projects: */ + +/* set everything up, including logging levels. Return 0 on + success */ +int init_gnutls(); + +/* logging and output functions: */ + +void err(const char* fmt, ...); +void logfunc(int level, const char* string); + +/* basic datum manipulations: */ + +void init_datum(gnutls_datum_t* d); +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); +int write_datum_fd(int fd, const gnutls_datum_t* d); +int write_datum_fd_with_length(int fd, const gnutls_datum_t* d); +int write_data_fd_with_length(int fd, const gnutls_datum_t** d, unsigned int num); + +/* set up a datum from a null-terminated string */ +int datum_from_string(gnutls_datum_t* d, const char* str); + +/* 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 */ +int set_datum_string(gnutls_datum_t* d, const char* s); + +/* read the passed-in file descriptor until EOF, store in a single + datum */ +int set_datum_fd(gnutls_datum_t* d, int fd); + +/* 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); + +/* set up file descriptor pipe for writing (child process pid gets + stored in pid, fd is returned)*/ +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/src/keytrans/gpg2ssh.c b/src/keytrans/gpg2ssh.c new file mode 100644 index 0000000..c99f03f --- /dev/null +++ b/src/keytrans/gpg2ssh.c @@ -0,0 +1,293 @@ +#include "gnutls-helpers.h" + +#include +#include + +/* for waitpid() */ +#include +#include + +/* + Author: Daniel Kahn Gillmor + 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; + 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(&p); + init_datum(&q); + 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_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: */ + + /* FIXME: we should be auto-detecting the input format, and + translating it as needed. */ + + 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; + } + + /* FIXME: We're currently looking at the primary key or maybe the + first authentication-capable subkey. + + Instead, we should be iterating through the primary key and all + subkeys: for each one with the authentication usage flag set of a + algorithm we can handle, we should output matching UserIDs and + the SSH version of the key. */ + + + 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 && + usage & GNUTLS_KEY_KEY_ENCIPHERMENT) { + err("the primary key can be used for authentication and communication encryption!\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 not good for authentication and communication encryption. Trying subkeys...\n"); + + if (ret = gnutls_openpgp_crt_get_auth_subkey(openpgp_crt, keyid, 0), ret) { + err("failed to find a subkey capable of authentication and communication encryption (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 && + usage & GNUTLS_KEY_KEY_ENCIPHERMENT) { + err("could not find a subkey with authentication and communication encryption.\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; + } + } + + /* make sure userid is NULL-terminated */ + userid[sizeof(userid) - 1] = 0; + uidsz--; + + /* FIXME: we're just choosing the first UserID from the certificate: + instead, we should be selecting every User ID that is adequately + signed and matches the spec, and aggregating them with commas for + known_hosts output */ + + if (ret = gnutls_openpgp_crt_get_name(openpgp_crt, 0, userid, &uidsz), ret) { + err("Failed to fetch the first UserID (error: %d)\n", ret); + return ret; + } + + if (ret = validate_ssh_host_userid(userid), ret) { + err("bad userid: not a valid ssh host.\n"); + return ret; + } + + /* remove ssh:// from the beginning of userid */ + memmove(userid, userid + strlen("ssh://"), 1 + strlen(userid) - strlen("ssh://")); + + + /* now we have algo, and the various MPI data are set. Can we + export them cleanly? */ + + /* for the moment, we'll just dump the info raw, and pipe it + externally through coreutils' /usr/bin/base64 */ + + if (algo == GNUTLS_PK_RSA) { + algoname = "ssh-rsa"; + mpicount = 3; + + all[0] = &algolabel; + all[1] = &e; + all[2] = &m; + } else if (algo == GNUTLS_PK_DSA) { + algoname = "ssh-dss"; + mpicount = 5; + + all[0] = &algolabel; + all[1] = &p; + all[2] = &q; + all[3] = &g; + all[4] = &y; + } else { + err("no idea what this algorithm is: %d\n", algo); + return 1; + } + + if (ret = datum_from_string(&algolabel, algoname), ret) { + err("couldn't label string (error: %d)\n", ret); + return ret; + } + + snprintf(output_data, sizeof(output_data), "%s %s ", userid, algoname); + + pipefd = create_writing_pipe(&child_pid, args[0], args); + if (pipefd < 0) { + err("failed to create a writing pipe (returned %d)\n", pipefd); + return pipefd; + } + + write(1, output_data, strlen(output_data)); + + if (0 != write_data_fd_with_length(pipefd, all, mpicount)) { + err("was not able to write out RSA key data\n"); + return 1; + } + close(pipefd); + if (child_pid != waitpid(child_pid, &pipestatus, 0)) { + err("could not wait for child process to return for some reason.\n"); + return 1; + } + if (pipestatus != 0) { + err("base64 pipe died with return code %d\n", pipestatus); + return pipestatus; + } + + write(1, "\n", 1); + + + + gnutls_openpgp_crt_deinit(openpgp_crt); + gnutls_global_deinit(); + return 0; +} diff --git a/src/keytrans/openpgp2ssh.c b/src/keytrans/openpgp2ssh.c new file mode 100644 index 0000000..d6bac68 --- /dev/null +++ b/src/keytrans/openpgp2ssh.c @@ -0,0 +1,271 @@ +#include "gnutls-helpers.h" + +#include +#include + +/* + Author: Daniel Kahn Gillmor + Date: Tue, 01 Apr 2008 + License: GPL v3 or later + + monkeysphere private key translator: execute this with an GPG + secret key on stdin (at the moment, only passphraseless RSA keys + work). + + It will spit out a PEM-encoded version of the key on stdout, which + can be fed into ssh-add like this: + + gpg --export-secret-keys $KEYID | monkeysphere | ssh-add -c /dev/stdin + + Requirements: I've only built this so far with GnuTLS v2.3.4 -- + version 2.2.0 does not contain the appropriate pieces. + + Notes: gpgkey2ssh doesn't seem to provide the same public + keys. Mighty weird! + +0 wt215@squeak:~/monkeysphere$ gpg --export-secret-keys 1DCDF89F | ~dkg/src/monkeysphere/monkeysphere | ssh-add -c /dev/stdin +gnutls version: 2.3.4 +OpenPGP RSA Key, with 1024 bits +Identity added: /dev/stdin (/dev/stdin) +The user has to confirm each use of the key +0 wt215@squeak:~/monkeysphere$ ssh-add -L +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC9gWQqfrnhQKDQnND/3eOexpddE64J+1zp9fcyCje7H5LKclb6DBV2HS6WgW32PJhIzvP+fYZM3dzXea3fpv14y1SicXiRBDgF9SnsNA1qWn2RyzkLcKy7PmM0PDYtU1oiLTcQj/xkWcqW2sLKHT/WW+vZP5XP7RMGN/yWNMfE2Q== /dev/stdin +0 wt215@squeak:~/monkeysphere$ gpgkey2ssh 1DCDF89F +ssh-rsa AAAAB3NzaC1yc2EAAACBAL2BZCp+ueFAoNCc0P/d457Gl10Trgn7XOn19zIKN7sfkspyVvoMFXYdLpaBbfY8mEjO8/59hkzd3Nd5rd+m/XjLVKJxeJEEOAX1Kew0DWpafZHLOQtwrLs+YzQ8Ni1TWiItNxCP/GRZypbawsodP9Zb69k/lc/tEwY3/JY0x8TZAAAAAwEAAQ== COMMENT +0 wt215@squeak:~/monkeysphere$ + + */ + + +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; + + 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_openpgp_privkey_init(&pgp_privkey), ret) { + err("Failed to initialized OpenPGP private key (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 private keys\n"); + 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, input, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0), ret) + err("failed to import the OpenPGP private key in BASE64 format (error: %d)\n", ret); + } + + pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(pgp_privkey, &pgp_bits); + if (pgp_algo < 0) { + err("failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo); + return 1; + } + if (pgp_algo == GNUTLS_PK_RSA) { + err("OpenPGP RSA Key, with %d bits\n", pgp_bits); + ret = gnutls_openpgp_privkey_export_rsa_raw(pgp_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_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; + } + } else if (pgp_algo == GNUTLS_PK_DSA) { + err("OpenPGP DSA Key, with %d bits\n", pgp_bits); + ret = gnutls_openpgp_privkey_export_dsa_raw(pgp_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_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; + } + } else { + err("OpenPGP Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", pgp_algo); + return 1; + } + + 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, + &ods); + printf("ret: %u; ods: %u;\n", ret, ods); + if (ret == 0) { + write(1, output_data, ods); + } + + + gnutls_x509_privkey_deinit(x509_privkey); + gnutls_global_deinit(); + return 0; +} diff --git a/src/keytrans/ssh2gpg.c b/src/keytrans/ssh2gpg.c new file mode 100644 index 0000000..b14a540 --- /dev/null +++ b/src/keytrans/ssh2gpg.c @@ -0,0 +1,171 @@ +#include "gnutls-helpers.h" + +#include +#include + +/* for waitpid() */ +#include +#include + +/* for time() */ +#include + +/* for htons() */ +#include + + +/* + Author: Daniel Kahn Gillmor + 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; +}