From: Daniel Kahn Gillmor Date: Sun, 1 Mar 2009 16:45:38 +0000 (-0500) Subject: transition to the perl-based keytrans implementation. X-Git-Tag: monkeysphere_0.24~47 X-Git-Url: https://codewiz.org/gitweb?p=monkeysphere.git;a=commitdiff_plain;h=2c427b22f6a780cbf0d4e22fce26071727e985a1 transition to the perl-based keytrans implementation. --- diff --git a/Makefile b/Makefile index 0284a8a..2c6077e 100755 --- a/Makefile +++ b/Makefile @@ -14,10 +14,8 @@ ETCSUFFIX ?= PREFIX ?= /usr MANPREFIX ?= $(PREFIX)/share/man -all: keytrans - -keytrans: - $(MAKE) -C src/keytrans +# nothing actually needs to be built now. +all: tarball: clean rm -rf monkeysphere-$(MONKEYSPHERE_VERSION) @@ -40,7 +38,6 @@ freebsd-distinfo: ./utils/build-freebsd-distinfo clean: - $(MAKE) -C src/keytrans clean # clean up old monkeysphere packages lying around as well. rm -f monkeysphere_* @@ -52,9 +49,12 @@ install: all installman mkdir -p $(DESTDIR)$(ETCPREFIX)/etc/monkeysphere mkdir -p $(DESTDIR)$(PREFIX)/share/doc/monkeysphere install -m 0644 VERSION $(DESTDIR)$(PREFIX)/share/monkeysphere - install src/monkeysphere src/keytrans/openpgp2ssh src/keytrans/pem2openpgp $(DESTDIR)$(PREFIX)/bin + install src/monkeysphere $(DESTDIR)$(PREFIX)/bin install src/monkeysphere-host src/monkeysphere-authentication $(DESTDIR)$(PREFIX)/sbin install -m 0644 src/share/common $(DESTDIR)$(PREFIX)/share/monkeysphere + install -m 0755 src/share/keytrans $(DESTDIR)$(PREFIX)/share/monkeysphere + ln -s ../share/monkeysphere/keytrans $(DESTDIR)$(PREFIX)/bin/pem2openpgp + ln -s ../share/monkeysphere/keytrans $(DESTDIR)$(PREFIX)/bin/openpgp2ssh install -m 0744 src/transitions/* $(DESTDIR)$(PREFIX)/share/monkeysphere/transitions install -m 0644 src/transitions/README.txt $(DESTDIR)$(PREFIX)/share/monkeysphere/transitions install -m 0644 src/share/m/* $(DESTDIR)$(PREFIX)/share/monkeysphere/m diff --git a/src/keytrans/Makefile b/src/keytrans/Makefile deleted file mode 100644 index 4d54be7..0000000 --- a/src/keytrans/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -CFLAGS=`libgnutls-config --libs --cflags` -g -Wall --pedantic -CC=gcc - -all: openpgp2ssh - -openpgp2ssh: openpgp2ssh.c gnutls-helpers.o - $(CC) $(CFLAGS) -o openpgp2ssh openpgp2ssh.c gnutls-helpers.o - -.c.o: - $(CC) $(CFLAGS) -c $< - -clean: - rm -f openpgp2ssh *.o - -.PHONY: clean all diff --git a/src/keytrans/gnutls-helpers.c b/src/keytrans/gnutls-helpers.c deleted file mode 100644 index 8d8ec17..0000000 --- a/src/keytrans/gnutls-helpers.c +++ /dev/null @@ -1,466 +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 - -/* for exit() */ -#include - -#include - -/* higher levels allow more frivolous error messages through. - this is set with the MONKEYSPHERE_DEBUG variable */ -static int loglevel = 0; - -void err(int level, const char* fmt, ...) { - va_list ap; - if (level > loglevel) - return; - 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) -{ - assert(sizeof(out) >= 2*sizeof(keyid)); - hex_print_data((char*)out, (const unsigned char*)keyid, sizeof(keyid)); -} - -/* you must have twice as many bytes in the out buffer as in the in buffer */ -void hex_print_data(char* out, const unsigned char* in, size_t incount) -{ - static const char hex[16] = "0123456789ABCDEF"; - unsigned int inix = 0, outix = 0; - - while (inix < incount) { - out[outix] = hex[(in[inix] >> 4) & 0x0f]; - out[outix + 1] = hex[in[inix] & 0x0f]; - inix++; - outix += 2; - } -} - -unsigned char hex2bin(unsigned char x) { - if ((x >= '0') && (x <= '9')) - return x - '0'; - if ((x >= 'A') && (x <= 'F')) - return 10 + x - 'A'; - if ((x >= 'a') && (x <= 'f')) - return 10 + x - 'a'; - return 0xff; -} - -void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in) { - unsigned int pkix = 0, outkix = 0; - while (pkix < sizeof(printable_keyid)) { - unsigned hi = hex2bin(in[pkix]); - unsigned lo = hex2bin(in[pkix + 1]); - if (hi == 0xff) { - err(0, "character '%c' is not a hex char\n", in[pkix]); - exit(1); - } - if (lo == 0xff) { - err(0, "character '%c' is not a hex char\n", in[pkix + 1]); - exit(1); - } - out[outkix] = lo | (hi << 4); - - pkix += 2; - outkix++; - } -} - -unsigned int hexstring2bin(unsigned char* out, const char* in) { - unsigned int pkix = 0, outkix = 0; - int hi = 0; /* which nybble is it? */ - - while (in[pkix]) { - unsigned char z = hex2bin(in[pkix]); - if (z != 0xff) { - if (!hi) { - if (out) out[outkix] = (z << 4); - hi = 1; - } else { - if (out) out[outkix] |= z; - hi = 0; - outkix++; - } - pkix++; - } - } - return outkix*8 + (hi ? 4 : 0); -} - -int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str) { - printable_keyid p; - int ret; - - ret = convert_string_to_printable_keyid(p, str); - if (ret == 0) - collapse_printable_keyid(out, p); - return ret; -} -int convert_string_to_printable_keyid(printable_keyid pkeyid, const char* str) { - int arglen, x; - arglen = 0; - x = 0; - while ((arglen <= sizeof(printable_keyid)) && - (str[x] != '\0')) { - if (isxdigit(str[x])) { - if (arglen == sizeof(printable_keyid)) { - err(0, "There are more than %d hex digits in the keyid '%s'\n", sizeof(printable_keyid), str); - return 1; - } - pkeyid[arglen] = str[x]; - arglen++; - } - x++; - } - - if (arglen != sizeof(printable_keyid)) { - err(0, "Keyid '%s' is not %d hex digits in length\n", str, sizeof(printable_keyid)); - return 1; - } - return 0; -} - - - -int init_gnutls() { - const char* version = NULL; - const char* debug_string = NULL; - int ret; - - if (debug_string = getenv("MONKEYSPHERE_DEBUG"), debug_string) { - loglevel = atoi(debug_string); - } - - if (ret = gnutls_global_init(), ret) { - err(0, "Failed to do gnutls_global_init() (error: %d)\n", ret); - return 1; - } - - version = gnutls_check_version(NULL); - - if (version) - err(1, "gnutls version: %s\n", version); - else { - err(0, "no gnutls version found!\n"); - return 1; - } - - gnutls_global_set_log_function(logfunc); - - gnutls_global_set_log_level(loglevel); - err(1, "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(0,"a is larger\n"); - return 1; - } - if (a->size < b->size) { - err(0,"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(0,"out of memory!\n"); - return -1; - } - d->size = bufsize; - } else { - bufsize = d->size; - } - f = fdopen(fd, "r"); - if (NULL == f) { - err(0,"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(0,"out of memory!\n"); - return -1; - }; - d->size = bufsize; - } - len += fread(d->data + len, 1, bufsize - len, f); - /* err(0,"read %d bytes\n", len); */ - } - if (ferror(f)) { - err(0,"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(0,"failed to stat '%s'\n", fname); - return -1; - } - - c = gnutls_realloc(d->data, sbuf.st_size); - if (NULL == c) { - err(0,"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(0,"failed to open '%s' for reading\n", fname); - return -1; - } - - x = fread(d->data, d->size, 1, file); - if (x != 1) { - err(0,"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(0,"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(0,"failed to write size of datum.\n"); - return -2; - } - if (looks_negative) { - if (write(fd, &zero, 1) != 1) { - err(0,"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(0,"bad pointer passed to create_writing_pipe()\n"); - return -1; - } - - if (ret = pipe(p), ret == -1) { - err(0,"failed to create a pipe (error: %d \"%s\")\n", errno, strerror(errno)); - return -1; - } - - *pid = fork(); - if (*pid == -1) { - err(0,"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(0,"Failed to transfer reading file descriptor to stdin (error: %d \"%s\")\n", errno, strerror(errno)); - exit(1); - } - execvp(path, argv); - err(0,"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(0,"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(0,"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(0,"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(0,"invalid character in domain name: %c\n", *userid); - goto fail; - } - } - /* ensure that the last character is valid: */ - if (!isalnum(*(userid - 1))) { - err(0,"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 deleted file mode 100644 index bf54af0..0000000 --- a/src/keytrans/gnutls-helpers.h +++ /dev/null @@ -1,89 +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(int level, 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); -void collapse_printable_keyid(gnutls_openpgp_keyid_t out, printable_keyid in); -int convert_string_to_keyid(gnutls_openpgp_keyid_t out, const char* str); -int convert_string_to_printable_keyid(printable_keyid out, const char* str); - -/* you must have twice as many bytes in the out buffer as in the in buffer */ -void hex_print_data(char* out, const unsigned char* in, size_t incount); - -/* expects a null-terminated string as in, containing an even number - of hexadecimal characters. - - returns length in *bits* of raw data as output. - - the out buffer must be at least half as long as in to hold the - output. if out is NULL, no output will be generated, but the - length will still be returned. -*/ -unsigned int hexstring2bin(unsigned char* out, const char* in); - -/* 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/openpgp2ssh.c b/src/keytrans/openpgp2ssh.c deleted file mode 100644 index f16eac5..0000000 --- a/src/keytrans/openpgp2ssh.c +++ /dev/null @@ -1,507 +0,0 @@ -#include "gnutls-helpers.h" - -#include -#include - -/* for waitpid() */ -#include -#include - -/* - Author: Daniel Kahn Gillmor - Date: 2008-06-12 13:47:41-0400 - License: GPL v3 or later - - monkeysphere key translator: execute this with an OpenPGP key on - stdin, (please indicate the specific keyid that you want as the - first argument if there are subkeys). At the moment, only public - keys and passphraseless secret keys work. - - For secret keys, 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 | openpgp2ssh $KEYID | ssh-add -c /dev/stdin - - For public keys, it will spit out a single line of text that can - (with some massaging) be used in an openssh known_hosts or - authorized_keys file. For example: - - echo server.example.org $(gpg --export $KEYID | openpgp2ssh $KEYID) >> ~/.ssh/known_hosts - - Requirements: I've only built this so far with GnuTLS v2.3.x. - GnuTLS 2.2.x does not contain the appropriate functionality. - - */ - - -/* FIXME: keyid should be const as well */ -int convert_private_pgp_to_x509(gnutls_x509_privkey_t* output, const gnutls_openpgp_privkey_t* pgp_privkey, const unsigned char* keyfpr, unsigned int fprlen) { - gnutls_datum_t m, e, d, p, q, u, g, y, x; - gnutls_pk_algorithm_t pgp_algo; - unsigned int pgp_bits; - int ret; - int subkeyidx; - int subkeycount; - int found = 0; - unsigned char fingerprint[20]; - size_t fingerprint_length = sizeof(fingerprint); - - 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); - - subkeycount = gnutls_openpgp_privkey_get_subkey_count(*pgp_privkey); - if (subkeycount < 0) { - err(0,"Could not determine subkey count (got value %d)\n", subkeycount); - return 1; - } - - if ((keyfpr == NULL) && - (subkeycount > 0)) { - err(0,"No key identifier passed in, but there were %d keys to choose from\n", subkeycount + 1); - return 1; - } - - if (keyfpr != NULL) { - ret = gnutls_openpgp_privkey_get_fingerprint(*pgp_privkey, fingerprint, &fingerprint_length); - if (ret) { - err(0,"Could not get fingerprint (error: %d)\n", ret); - return 1; - } - if (fprlen > fingerprint_length) { - err(0, "Requested key identifier is longer than computed fingerprint\n"); - return 1; - } - if (fingerprint_length > fprlen) { - err(0, "Only comparing last %d bits of key fingerprint\n", fprlen*8); - } - } - if ((keyfpr == NULL) || (memcmp(fingerprint + (fingerprint_length - fprlen), keyfpr, fprlen) == 0)) { - /* we want to export the primary key: */ - err(0,"exporting primary key\n"); - - /* FIXME: this is almost identical to the block below for subkeys. - This clumsiness seems inherent in the gnutls OpenPGP API, - though. ugh. */ - pgp_algo = gnutls_openpgp_privkey_get_pk_algorithm(*pgp_privkey, &pgp_bits); - if (pgp_algo < 0) { - err(0, "failed to get OpenPGP key algorithm (error: %d)\n", pgp_algo); - return 1; - } - if (pgp_algo == GNUTLS_PK_RSA) { - err(0,"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(0, "failed to export RSA key parameters (error: %d)\n", ret); - return 1; - } - - } else if (pgp_algo == GNUTLS_PK_DSA) { - err(0,"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(0,"failed to export DSA key parameters (error: %d)\n", ret); - return 1; - } - } - found = 1; - } else { - /* lets trawl through the subkeys until we find the one we want: */ - for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) { - ret = gnutls_openpgp_privkey_get_subkey_fingerprint(*pgp_privkey, subkeyidx, fingerprint, &fingerprint_length); - if (ret) { - err(0,"Could not get fingerprint of subkey with index %d (error: %d)\n", subkeyidx, ret); - return 1; - } - if (fprlen > fingerprint_length) { - err(0, "Requested key identifier is longer than computed fingerprint\n"); - return 1; - } - if (fingerprint_length > fprlen) { - err(1, "Only comparing last %d bits of key fingerprint\n", fprlen*8); - } - if (memcmp(fingerprint + (fingerprint_length - fprlen), keyfpr, fprlen) == 0) { - err(0,"exporting subkey index %d\n", subkeyidx); - - /* FIXME: this is almost identical to the block above for the - primary key. */ - pgp_algo = gnutls_openpgp_privkey_get_subkey_pk_algorithm(*pgp_privkey, subkeyidx, &pgp_bits); - if (pgp_algo < 0) { - err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", pgp_algo); - return pgp_algo; - } else if (pgp_algo == GNUTLS_PK_RSA) { - err(0,"OpenPGP RSA key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_subkey_rsa_raw(*pgp_privkey, subkeyidx, &m, &e, &d, &p, &q, &u); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to export RSA key parameters (error: %d)\n", ret); - return 1; - } - } else if (pgp_algo == GNUTLS_PK_DSA) { - err(0,"OpenPGP DSA Key, with %d bits\n", pgp_bits); - ret = gnutls_openpgp_privkey_export_subkey_dsa_raw(*pgp_privkey, subkeyidx, &p, &q, &g, &y, &x); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to export DSA key parameters (error: %d)\n", ret); - return 1; - } - } - found = 1; - } - } - } - - if (!found) { - err(0,"Could not find key in input\n"); - return 1; - } - - if (pgp_algo == GNUTLS_PK_RSA) { - ret = gnutls_x509_privkey_import_rsa_raw (*output, &m, &e, &d, &p, &q, &u); - if (GNUTLS_E_SUCCESS != ret) { - err(0, "failed to import RSA key parameters (error: %d)\n", ret); - return 1; - } - } else if (pgp_algo == GNUTLS_PK_DSA) { - ret = gnutls_x509_privkey_import_dsa_raw (*output, &p, &q, &g, &y, &x); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to import DSA key parameters (error: %d)\n", ret); - return 1; - } - } else { - err(0,"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(0,"failed to fix up the private key in X.509 format (error: %d)\n", ret); - return 1; - } - - return 0; -} - -/* FIXME: keyid should be const also */ -int emit_public_openssh_from_pgp(const gnutls_openpgp_crt_t* pgp_crt, const unsigned char* keyfpr, size_t fprlen) { - int ret; - int subkeyidx; - int subkeycount; - int found = 0; - gnutls_datum_t m, e, p, q, g, y, algolabel; - unsigned int bits; - gnutls_pk_algorithm_t algo; - const gnutls_datum_t* all[5]; - const char* algoname; - int mpicount; - /* output_data must be at least 2 chars longer than the maximum possible - algorithm name: */ - char output_data[20]; - - unsigned char fingerprint[20]; - size_t fingerprint_length = sizeof(fingerprint); - - /* variables for the output conversion: */ - int pipestatus; - int pipefd, child_pid; - char* const b64args[] = {"sh", "-c", "base64 | tr -c -d '[A-Za-z0-9=+/]'", NULL}; - - init_datum(&m); - init_datum(&e); - init_datum(&p); - init_datum(&q); - init_datum(&g); - init_datum(&algolabel); - - - /* figure out if we've got the right thing: */ - subkeycount = gnutls_openpgp_crt_get_subkey_count(*pgp_crt); - if (subkeycount < 0) { - err(0,"Could not determine subkey count (got value %d)\n", subkeycount); - return 1; - } - - if ((keyfpr == NULL) && - (subkeycount > 0)) { - err(0,"No key identifier passed in, but there were %d keys to choose from\n", subkeycount + 1); - return 1; - } - - if (keyfpr != NULL) { - ret = gnutls_openpgp_crt_get_fingerprint(*pgp_crt, fingerprint, &fingerprint_length); - if (ret) { - err(0,"Could not get key fingerprint (error: %d)\n", ret); - return 1; - } - if (fprlen > fingerprint_length) { - err(0, "Requested key identifier is longer than computed fingerprint\n"); - return 1; - } - if (fingerprint_length > fprlen) { - err(0, "Only comparing last %d bits of key fingerprint\n", fprlen*8); - } - } - if ((keyfpr == NULL) || (memcmp(fingerprint + (fingerprint_length - fprlen), keyfpr, fprlen) == 0)) { - /* we want to export the primary key: */ - err(0,"exporting primary key\n"); - - /* FIXME: this is almost identical to the block below for subkeys. - This clumsiness seems inherent in the gnutls OpenPGP API, - though. ugh. */ - algo = gnutls_openpgp_crt_get_pk_algorithm(*pgp_crt, &bits); - if (algo < 0) { - err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); - return algo; - } else if (algo == GNUTLS_PK_RSA) { - err(0,"OpenPGP RSA certificate, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_pk_rsa_raw(*pgp_crt, &m, &e); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to export RSA certificate parameters (error: %d)\n", ret); - return 1; - } - } else if (algo == GNUTLS_PK_DSA) { - err(0,"OpenPGP DSA certificate, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_pk_dsa_raw(*pgp_crt, &p, &q, &g, &y); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to export DSA certificate parameters (error: %d)\n", ret); - return 1; - } - } - found = 1; - - } else { - /* lets trawl through the subkeys until we find the one we want: */ - for (subkeyidx = 0; (subkeyidx < subkeycount) && !found; subkeyidx++) { - ret = gnutls_openpgp_crt_get_subkey_fingerprint(*pgp_crt, subkeyidx, fingerprint, &fingerprint_length); - if (ret) { - err(0,"Could not get fingerprint of subkey with index %d (error: %d)\n", subkeyidx, ret); - return 1; - } - if (fprlen > fingerprint_length) { - err(0, "Requested key identifier is longer than computed fingerprint\n"); - return 1; - } - if (fingerprint_length > fprlen) { - err(1, "Only comparing last %d bits of key fingerprint\n", fprlen*8); - } - if (memcmp(fingerprint + (fingerprint_length - fprlen), keyfpr, fprlen) == 0) { - err(0,"exporting subkey index %d\n", subkeyidx); - - /* FIXME: this is almost identical to the block above for the - primary key. */ - algo = gnutls_openpgp_crt_get_subkey_pk_algorithm(*pgp_crt, subkeyidx, &bits); - if (algo < 0) { - err(0,"failed to get the algorithm of the OpenPGP public key (error: %d)\n", algo); - return algo; - } else if (algo == GNUTLS_PK_RSA) { - err(0,"OpenPGP RSA certificate, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_subkey_pk_rsa_raw(*pgp_crt, subkeyidx, &m, &e); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to export RSA certificate parameters (error: %d)\n", ret); - return 1; - } - } else if (algo == GNUTLS_PK_DSA) { - err(0,"OpenPGP DSA certificate, with %d bits\n", bits); - ret = gnutls_openpgp_crt_get_subkey_pk_dsa_raw(*pgp_crt, subkeyidx, &p, &q, &g, &y); - if (GNUTLS_E_SUCCESS != ret) { - err(0,"failed to export DSA certificate parameters (error: %d)\n", ret); - return 1; - } - } - found = 1; - - } - } - } - - if (!found) { - err(0,"Could not find key in input\n"); - return 1; - } - - /* if we made it this far, we've got MPIs, and we've got the - algorithm, so we just need to emit the info */ - 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(0,"Key algorithm was neither DSA nor RSA (it was %d). Can't deal. Sorry!\n", algo); - return 1; - } - - if (ret = datum_from_string(&algolabel, algoname), ret) { - err(0,"couldn't label string (error: %d)\n", ret); - return ret; - } - - snprintf(output_data, sizeof(output_data), "%s ", algoname); - - pipefd = create_writing_pipe(&child_pid, b64args[0], b64args); - if (pipefd < 0) { - err(0,"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(0,"was not able to write out RSA key data\n"); - return 1; - } - close(pipefd); - if (child_pid != waitpid(child_pid, &pipestatus, 0)) { - err(0,"could not wait for child process to return for some reason.\n"); - return 1; - } - if (pipestatus != 0) { - err(0,"base64 pipe died with return code %d\n", pipestatus); - return pipestatus; - } - - write(1, "\n", 1); - - return 0; -} - -int main(int argc, char* argv[]) { - gnutls_datum_t data; - int ret = 0; - gnutls_x509_privkey_t x509_privkey; - gnutls_openpgp_privkey_t pgp_privkey; - gnutls_openpgp_crt_t pgp_crt; - - char output_data[10240]; - size_t ods = sizeof(output_data); - - unsigned char * fingerprint = NULL; - size_t fpr_size; - char * prettyfpr = NULL; - - init_gnutls(); - - /* figure out what key we should be looking for: */ - if (argv[1] != NULL) { - if (strlen(argv[1]) > 81) { - /* safety check to avoid some sort of wacky overflow situation: - there's no reason that the key id should be longer than twice - a sane fingerprint (one byte between chars, and then another - two at the beginning and end) */ - err(0, "Key identifier is way too long. Please use at most 40 hex digits.\n"); - return 1; - } - - fpr_size = hexstring2bin(NULL, argv[1]); - if (fpr_size > 40*4) { - err(0, "Key identifier is longer than 40 hex digits\n"); - return 1; - } - /* since fpr_size is initially in bits: */ - if (fpr_size % 8 != 0) { - err(0, "Please provide an even number of hex digits for the key identifier\n"); - return 1; - } - fpr_size /= 8; - - fingerprint = malloc(sizeof(unsigned char) * fpr_size); - bzero(fingerprint, sizeof(unsigned char) * fpr_size); - hexstring2bin(fingerprint, argv[1]); - - prettyfpr = malloc(sizeof(unsigned char)*fpr_size*2 + 1); - if (prettyfpr != NULL) { - hex_print_data(prettyfpr, fingerprint, fpr_size); - prettyfpr[sizeof(unsigned char)*fpr_size*2] = '\0'; - err(1, "searching for key with fingerprint '%s'\n", prettyfpr); - free(prettyfpr); - } - - if (fpr_size < 4) { - err(0, "You MUST provide at least 8 hex digits in any key identifier\n"); - return 1; - } - if (fpr_size < 8) - err(0, "You should provide at least 16 hex digits in any key identifier (proceeding with %d digits anyway)\n", fpr_size*2); - - } - - - init_datum(&data); - - /* slurp in the key from stdin */ - if (ret = set_datum_fd(&data, 0), ret) { - err(0,"didn't read file descriptor 0\n"); - return 1; - } - - - if (ret = gnutls_openpgp_privkey_init(&pgp_privkey), ret) { - err(0,"Failed to initialized OpenPGP private key (error: %d)\n", ret); - return 1; - } - /* check whether it's a private key or a public key, by trying them: */ - if ((gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_RAW, NULL, 0) == 0) || - (gnutls_openpgp_privkey_import(pgp_privkey, &data, GNUTLS_OPENPGP_FMT_BASE64, NULL, 0) == 0)) { - /* we're dealing with a private key */ - err(0,"Translating private key\n"); - if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { - err(0,"Failed to initialize X.509 private key for output (error: %d)\n", ret); - return 1; - } - - ret = convert_private_pgp_to_x509(&x509_privkey, &pgp_privkey, fingerprint, fpr_size); - - gnutls_openpgp_privkey_deinit(pgp_privkey); - if (ret) - return ret; - - ret = gnutls_x509_privkey_export (x509_privkey, - GNUTLS_X509_FMT_PEM, - output_data, - &ods); - if (ret == 0) { - write(1, output_data, ods); - } - gnutls_x509_privkey_deinit(x509_privkey); - - } else { - if (ret = gnutls_openpgp_crt_init(&pgp_crt), ret) { - err(0,"Failed to initialized OpenPGP certificate (error: %d)\n", ret); - return 1; - } - - if ((gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_RAW) == 0) || - (gnutls_openpgp_crt_import(pgp_crt, &data, GNUTLS_OPENPGP_FMT_BASE64) == 0)) { - /* we're dealing with a public key */ - err(0,"Translating public key\n"); - - ret = emit_public_openssh_from_pgp(&pgp_crt, fingerprint, fpr_size); - if (ret != 0) - return ret; - - } else { - /* we have no idea what kind of key this is at all anyway! */ - err(0,"Input does not contain any form of OpenPGP key I recognize.\n"); - return 1; - } - } - - gnutls_global_deinit(); - free(fingerprint); - return 0; -} diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp deleted file mode 100755 index 8bf17fb..0000000 --- a/src/keytrans/pem2openpgp +++ /dev/null @@ -1,775 +0,0 @@ -#!/usr/bin/perl -w -T - -# pem2openpgp: take a PEM-encoded RSA private-key on standard input, a -# User ID as the first argument, and generate an OpenPGP secret key -# and certificate from it. - -# WARNING: the secret key material *will* appear on stdout (albeit in -# OpenPGP form) -- if you redirect stdout to a file, make sure the -# permissions on that file are appropriately locked down! - -# Usage: - -# pem2openpgp 'ssh://'$(hostname -f) < /etc/ssh/ssh_host_rsa_key | gpg --import - -# Authors: -# Jameson Rollins -# Daniel Kahn Gillmor - -# Started on: 2009-01-07 02:01:19-0500 - -# License: GPL v3 or later (we may need to adjust this given that this -# connects to OpenSSL via perl) - -use strict; -use warnings; -use File::Basename; -use Crypt::OpenSSL::RSA; -use Crypt::OpenSSL::Bignum; -use Crypt::OpenSSL::Bignum::CTX; -use Digest::SHA1; -use MIME::Base64; -use POSIX; - -## make sure all length() and substr() calls use bytes only: -use bytes; - -my $old_format_packet_lengths = { one => 0, - two => 1, - four => 2, - indeterminate => 3, -}; - -# see RFC 4880 section 9.1 (ignoring deprecated algorithms for now) -my $asym_algos = { rsa => 1, - elgamal => 16, - dsa => 17, - }; - -# see RFC 4880 section 9.2 -my $ciphers = { plaintext => 0, - idea => 1, - tripledes => 2, - cast5 => 3, - blowfish => 4, - aes128 => 7, - aes192 => 8, - aes256 => 9, - twofish => 10, - }; - -# see RFC 4880 section 9.3 -my $zips = { uncompressed => 0, - zip => 1, - zlib => 2, - bzip2 => 3, - }; - -# see RFC 4880 section 9.4 -my $digests = { md5 => 1, - sha1 => 2, - ripemd160 => 3, - sha256 => 8, - sha384 => 9, - sha512 => 10, - sha224 => 11, - }; - -# see RFC 4880 section 5.2.3.21 -my $usage_flags = { certify => 0x01, - sign => 0x02, - encrypt_comms => 0x04, - encrypt_storage => 0x08, - encrypt => 0x0c, ## both comms and storage - split => 0x10, # the private key is split via secret sharing - authenticate => 0x20, - shared => 0x80, # more than one person holds the entire private key - }; - -# see RFC 4880 section 4.3 -my $packet_types = { pubkey_enc_session => 1, - sig => 2, - symkey_enc_session => 3, - onepass_sig => 4, - seckey => 5, - pubkey => 6, - sec_subkey => 7, - compressed_data => 8, - symenc_data => 9, - marker => 10, - literal => 11, - trust => 12, - uid => 13, - pub_subkey => 14, - uat => 17, - symenc_w_integrity => 18, - mdc => 19, - }; - -# see RFC 4880 section 5.2.1 -my $sig_types = { binary_doc => 0x00, - text_doc => 0x01, - standalone => 0x02, - generic_certification => 0x10, - persona_certification => 0x11, - casual_certification => 0x12, - positive_certification => 0x13, - subkey_binding => 0x18, - primary_key_binding => 0x19, - key_signature => 0x1f, - key_revocation => 0x20, - subkey_revocation => 0x28, - certification_revocation => 0x30, - timestamp => 0x40, - thirdparty => 0x50, - }; - - -# see RFC 4880 section 5.2.3.1 -my $subpacket_types = { sig_creation_time => 2, - sig_expiration_time => 3, - exportable => 4, - trust_sig => 5, - regex => 6, - revocable => 7, - key_expiration_time => 9, - preferred_cipher => 11, - revocation_key => 12, - issuer => 16, - notation => 20, - preferred_digest => 21, - preferred_compression => 22, - keyserver_prefs => 23, - preferred_keyserver => 24, - primary_uid => 25, - policy_uri => 26, - usage_flags => 27, - signers_uid => 28, - revocation_reason => 29, - features => 30, - signature_target => 31, - embedded_signature => 32, - }; - -# bitstring (see RFC 4880 section 5.2.3.24) -my $features = { mdc => 0x01 - }; - -# bitstring (see RFC 4880 5.2.3.17) -my $keyserver_prefs = { nomodify => 0x80 - }; - -###### end lookup tables ###### - -# FIXME: if we want to be able to interpret openpgp data as well as -# produce it, we need to produce key/value-swapped lookup tables as well. - - -########### Math/Utility Functions ############## - - -# see the bottom of page 43 of RFC 4880 -sub simple_checksum { - my $bytes = shift; - - return unpack("%32W*",$bytes) % 65536; -} - -# calculate the multiplicative inverse of a mod b this is euclid's -# extended algorithm. For more information see: -# http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm the -# arguments here should be Crypt::OpenSSL::Bignum objects. $a should -# be the larger of the two values, and the two values should be -# coprime. - -sub modular_multi_inverse { - my $a = shift; - my $b = shift; - - - my $origdivisor = $b->copy(); - - my $ctx = Crypt::OpenSSL::Bignum::CTX->new(); - my $x = Crypt::OpenSSL::Bignum->zero(); - my $y = Crypt::OpenSSL::Bignum->one(); - my $lastx = Crypt::OpenSSL::Bignum->one(); - my $lasty = Crypt::OpenSSL::Bignum->zero(); - - my $finalquotient; - my $finalremainder; - - while (! $b->is_zero()) { - my ($quotient, $remainder) = $a->div($b, $ctx); - - $a = $b; - $b = $remainder; - - my $temp = $x; - $x = $lastx->sub($quotient->mul($x, $ctx)); - $lastx = $temp; - - $temp = $y; - $y = $lasty->sub($quotient->mul($y, $ctx)); - $lasty = $temp; - } - - if (!$a->is_one()) { - die "did this math wrong.\n"; - } - - # let's make sure that we return a positive value because RFC 4880, - # section 3.2 only allows unsigned values: - - ($finalquotient, $finalremainder) = $lastx->add($origdivisor)->div($origdivisor, $ctx); - - return $finalremainder; -} - - -############ OpenPGP formatting functions ############ - -# make an old-style packet out of the given packet type and body. -# old-style (see RFC 4880 section 4.2) -sub make_packet { - my $type = shift; - my $body = shift; - my $options = shift; - - my $len = length($body); - my $pseudolen = $len; - - # if the caller wants to use at least N octets of packet length, - # pretend that we're using that many. - if (defined $options && defined $options->{'packet_length'}) { - $pseudolen = 2**($options->{'packet_length'} * 8) - 1; - } - if ($pseudolen < $len) { - $pseudolen = $len; - } - - my $lenbytes; - my $lencode; - - if ($pseudolen < 2**8) { - $lenbytes = $old_format_packet_lengths->{one}; - $lencode = 'C'; - } elsif ($pseudolen < 2**16) { - $lenbytes = $old_format_packet_lengths->{two}; - $lencode = 'n'; - } elsif ($pseudolen < 2**31) { - ## not testing against full 32 bits because i don't want to deal - ## with potential overflow. - $lenbytes = $old_format_packet_lengths->{four}; - $lencode = 'N'; - } else { - ## what the hell do we do here? - $lenbytes = $old_format_packet_lengths->{indeterminate}; - $lencode = ''; - } - - return pack('C'.$lencode, 0x80 + ($type * 4) + $lenbytes, $len). - $body; -} - - -# takes a Crypt::OpenSSL::Bignum, returns it formatted as OpenPGP MPI -# (RFC 4880 section 3.2) -sub mpi_pack { - my $num = shift; - - my $val = $num->to_bin(); - my $mpilen = length($val)*8; - -# this is a kludgy way to get the number of significant bits in the -# first byte: - my $bitsinfirstbyte = length(sprintf("%b", ord($val))); - - $mpilen -= (8 - $bitsinfirstbyte); - - return pack('n', $mpilen).$val; -} - -# takes a Crypt::OpenSSL::Bignum, returns an MPI packed in preparation -# for an OpenSSH-style public key format. see: -# http://marc.info/?l=openssh-unix-dev&m=121866301718839&w=2 -sub openssh_mpi_pack { - my $num = shift; - - my $val = $num->to_bin(); - my $mpilen = length($val); - - my $ret = pack('N', $mpilen); - - # if the first bit of the leading byte is high, we should include a - # 0 byte: - if (ord($val) & 0x80) { - $ret = pack('NC', $mpilen+1, 0); - } - - return $ret.$val; -} - -sub openssh_pubkey_pack { - my $key = shift; - - my ($modulus, $exponent) = $key->get_key_parameters(); - - return openssh_mpi_pack(Crypt::OpenSSL::Bignum->new_from_bin("ssh-rsa")). - openssh_mpi_pack($exponent). - openssh_mpi_pack($modulus); -} - -# pull an OpenPGP-specified MPI off of a given stream, returning it as -# a Crypt::OpenSSL::Bignum. -sub read_mpi { - my $instr = shift; - my $readtally = shift; - - my $bitlen; - read($instr, $bitlen, 2) or die "could not read MPI length.\n"; - $bitlen = unpack('n', $bitlen); - $$readtally += 2; - - my $bytestoread = POSIX::floor(($bitlen + 7)/8); - my $ret; - read($instr, $ret, $bytestoread) or die "could not read MPI body.\n"; - $$readtally += $bytestoread; - return Crypt::OpenSSL::Bignum->new_from_bin($ret); -} - - -# FIXME: genericize these to accept either RSA or DSA keys: -sub make_rsa_pub_key_body { - my $key = shift; - my $timestamp = shift; - - my ($n, $e) = $key->get_key_parameters(); - - return - pack('CN', 4, $timestamp). - pack('C', $asym_algos->{rsa}). - mpi_pack($n). - mpi_pack($e); -} - -sub make_rsa_sec_key_body { - my $key = shift; - my $timestamp = shift; - - # we're not using $a and $b, but we need them to get to $c. - my ($n, $e, $d, $p, $q) = $key->get_key_parameters(); - - my $c3 = modular_multi_inverse($p, $q); - - my $secret_material = mpi_pack($d). - mpi_pack($p). - mpi_pack($q). - mpi_pack($c3); - - # according to Crypt::OpenSSL::RSA, the closest value we can get out - # of get_key_parameters is 1/q mod p; but according to sec 5.5.3 of - # RFC 4880, we're actually looking for u, the multiplicative inverse - # of p, mod q. This is why we're calculating the value directly - # with modular_multi_inverse. - - return - pack('CN', 4, $timestamp). - pack('C', $asym_algos->{rsa}). - mpi_pack($n). - mpi_pack($e). - pack('C', 0). # seckey material is not encrypted -- see RFC 4880 sec 5.5.3 - $secret_material. - pack('n', simple_checksum($secret_material)); -} - -# expects an RSA key (public or private) and a timestamp -sub fingerprint { - my $key = shift; - my $timestamp = shift; - - my $rsabody = make_rsa_pub_key_body($key, $timestamp); - - return Digest::SHA1::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); -} - - -# FIXME: handle DSA keys as well! -sub pem2openpgp { - my $rsa = shift; - my $uid = shift; - my $args = shift; - - $rsa->use_sha1_hash(); - - # see page 22 of RFC 4880 for why i think this is the right padding - # choice to use: - $rsa->use_pkcs1_padding(); - - if (! $rsa->check_key()) { - die "key does not check"; - } - - my $version = pack('C', 4); - # strong assertion of identity: - my $sigtype = pack('C', $sig_types->{positive_certification}); - # RSA - my $pubkey_algo = pack('C', $asym_algos->{rsa}); - # SHA1 - my $hash_algo = pack('C', $digests->{sha1}); - - # FIXME: i'm worried about generating a bazillion new OpenPGP - # certificates from the same key, which could easily happen if you run - # this script more than once against the same key (because the - # timestamps will differ). How can we prevent this? - - # this environment variable (if set) overrides the current time, to - # be able to create a standard key? If we read the key from a file - # instead of stdin, should we use the creation time on the file? - my $timestamp = 0; - if (defined $args->{timestamp}) { - $timestamp = ($args->{timestamp} + 0); - } else { - $timestamp = time(); - } - - my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); - - - my $flags = 0; - if (! defined $args->{usage_flags}) { - $flags = $usage_flags->{certify}; - } else { - my @ff = split(",", $args->{usage_flags}); - foreach my $f (@ff) { - if (! defined $usage_flags->{$f}) { - die "No such flag $f"; - } - $flags |= $usage_flags->{$f}; - } - } - - my $usage_packet = pack('CCC', 2, $subpacket_types->{usage_flags}, $flags); - - - # how should we determine how far off to set the expiration date? - # default is no expiration. Specify the timestamp in seconds from the - # key creation. - my $expiration_packet = ''; - if (defined $args->{expiration}) { - my $expires_in = $args->{expiration} + 0; - $expiration_packet = pack('CCN', 5, $subpacket_types->{key_expiration_time}, $expires_in); - } - - - # prefer AES-256, AES-192, AES-128, CAST5, 3DES: - my $pref_sym_algos = pack('CCCCCCC', 6, $subpacket_types->{preferred_cipher}, - $ciphers->{aes256}, - $ciphers->{aes192}, - $ciphers->{aes128}, - $ciphers->{cast5}, - $ciphers->{tripledes} - ); - - # prefer SHA-1, SHA-256, RIPE-MD/160 - my $pref_hash_algos = pack('CCCCC', 4, $subpacket_types->{preferred_digest}, - $digests->{sha1}, - $digests->{sha256}, - $digests->{ripemd160} - ); - - # prefer ZLIB, BZip2, ZIP - my $pref_zip_algos = pack('CCCCC', 4, $subpacket_types->{preferred_compression}, - $zips->{zlib}, - $zips->{bzip2}, - $zips->{zip} - ); - - # we support the MDC feature: - my $feature_subpacket = pack('CCC', 2, $subpacket_types->{features}, - $features->{mdc}); - - # keyserver preference: only owner modify (???): - my $keyserver_pref = pack('CCC', 2, $subpacket_types->{keyserver_prefs}, - $keyserver_prefs->{nomodify}); - - my $subpackets_to_be_hashed = - $creation_time_packet. - $usage_packet. - $expiration_packet. - $pref_sym_algos. - $pref_hash_algos. - $pref_zip_algos. - $feature_subpacket. - $keyserver_pref; - - my $subpacket_octets = pack('n', length($subpackets_to_be_hashed)); - - my $sig_data_to_be_hashed = - $version. - $sigtype. - $pubkey_algo. - $hash_algo. - $subpacket_octets. - $subpackets_to_be_hashed; - - my $pubkey = make_rsa_pub_key_body($rsa, $timestamp); - my $seckey = make_rsa_sec_key_body($rsa, $timestamp); - - # this is for signing. it needs to be an old-style header with a - # 2-packet octet count. - - my $key_data = make_packet($packet_types->{pubkey}, $pubkey, {'packet_length'=>2}); - - # take the last 8 bytes of the fingerprint as the keyid: - my $keyid = substr(fingerprint($rsa, $timestamp), 20 - 8, 8); - - # the v4 signature trailer is: - - # version number, literal 0xff, and then a 4-byte count of the - # signature data itself. - my $trailer = pack('CCN', 4, 0xff, length($sig_data_to_be_hashed)); - - my $uid_data = - pack('CN', 0xb4, length($uid)). - $uid; - - my $datatosign = - $key_data. - $uid_data. - $sig_data_to_be_hashed. - $trailer; - - my $data_hash = Digest::SHA1::sha1_hex($datatosign); - - my $issuer_packet = pack('CCa8', 9, $subpacket_types->{issuer}, $keyid); - - my $sig = Crypt::OpenSSL::Bignum->new_from_bin($rsa->sign($datatosign)); - - my $sig_body = - $sig_data_to_be_hashed. - pack('n', length($issuer_packet)). - $issuer_packet. - pack('n', hex(substr($data_hash, 0, 4))). - mpi_pack($sig); - - return - make_packet($packet_types->{seckey}, $seckey). - make_packet($packet_types->{uid}, $uid). - make_packet($packet_types->{sig}, $sig_body); -} - - -sub openpgp2ssh { - my $instr = shift; - my $fpr = shift; - - if (defined $fpr) { - if (length($fpr) < 8) { - die "We need at least 8 hex digits of fingerprint.\n"; - } - $fpr = uc($fpr); - } - - my $packettag; - my $dummy; - my $tag; - - my $key; - - while (! eof($instr)) { - read($instr, $packettag, 1); - $packettag = ord($packettag); - - my $packetlen; - if ( ! (0x80 & $packettag)) { - die "This is not an OpenPGP packet\n"; - } - if (0x40 & $packettag) { - $tag = (0x3f & $packettag); - my $nextlen = 0; - read($instr, $nextlen, 1); - $nextlen = ord($nextlen); - if ($nextlen < 192) { - $packetlen = $nextlen; - } elsif ($nextlen < 224) { - my $newoct; - read($instr, $newoct, 1); - $newoct = ord($newoct); - $packetlen = (($nextlen - 192) << 8) + ($newoct) + 192; - } elsif ($nextlen == 255) { - read($instr, $nextlen, 4); - $packetlen = unpack('N', $nextlen); - } else { - # packet length is undefined. - } - } else { - my $lentype; - $lentype = 0x03 & $packettag; - $tag = ( 0x3c & $packettag ) >> 2; - if ($lentype == 0) { - read($instr, $packetlen, 1) or die "could not read packet length\n"; - $packetlen = unpack('C', $packetlen); - } elsif ($lentype == 1) { - read($instr, $packetlen, 2) or die "could not read packet length\n"; - $packetlen = unpack('n', $packetlen); - } elsif ($lentype == 2) { - read($instr, $packetlen, 4) or die "could not read packet length\n"; - $packetlen = unpack('N', $packetlen); - } else { - # packet length is undefined. - } - } - - if (! defined($packetlen)) { - die "Undefined packet lengths are not supported.\n"; - } - - if ($tag == $packet_types->{pubkey} || - $tag == $packet_types->{pub_subkey} || - $tag == $packet_types->{seckey} || - $tag == $packet_types->{sec_subkey}) { - my $ver; - my $readbytes = 0; - read($instr, $ver, 1) or die "could not read key version\n"; - $readbytes += 1; - $ver = ord($ver); - - if ($ver != 4) { - printf(STDERR "We only work with version 4 keys. This key appears to be version %s.\n", $ver); - read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; - } else { - - my $timestamp; - read($instr, $timestamp, 4) or die "could not read key timestamp.\n"; - $readbytes += 4; - $timestamp = unpack('N', $timestamp); - - my $algo; - read($instr, $algo, 1) or die "could not read key algorithm.\n"; - $readbytes += 1; - $algo = ord($algo); - if ($algo != $asym_algos->{rsa}) { - printf(STDERR "We only support RSA keys (this key used algorithm %d).\n", $algo); - read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; - } else { - ## we have an RSA key. - my $modulus = read_mpi($instr, \$readbytes); - my $exponent = read_mpi($instr, \$readbytes); - - my $pubkey = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, $exponent); - my $foundfpr = fingerprint($pubkey, $timestamp); - - my $foundfprstr = Crypt::OpenSSL::Bignum->new_from_bin($foundfpr)->to_hex(); - - # is this a match? - if ((!defined($fpr)) || - (substr($foundfprstr, -1 * length($fpr)) eq $fpr)) { - if (defined($key)) { - die "Found two matching keys.\n"; - } - $key = $pubkey; - } - - if ($tag == $packet_types->{seckey} || - $tag == $packet_types->{sec_subkey}) { - if (!defined($key)) { # we don't think the public part of - # this key matches - read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; - } else { - my $s2k; - read($instr, $s2k, 1) or die "Could not read S2K octet.\n"; - $readbytes += 1; - $s2k = ord($s2k); - if ($s2k == 0) { - # secret material is unencrypted - # see http://tools.ietf.org/html/rfc4880#section-5.5.3 - my $d = read_mpi($instr, \$readbytes); - my $p = read_mpi($instr, \$readbytes); - my $q = read_mpi($instr, \$readbytes); - my $u = read_mpi($instr, \$readbytes); - - my $checksum; - read($instr, $checksum, 2) or die "Could not read checksum of secret key material.\n"; - $readbytes += 2; - $checksum = unpack('n', $checksum); - - # FIXME: compare with the checksum! how? the data is - # gone into the Crypt::OpenSSL::Bignum - - $key = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, - $exponent, - $d, - $p, - $q); - - $key->check_key() or die "Secret key is not a valid RSA key.\n"; - } else { - print(STDERR "We cannot handle encrypted secret keys. Skipping!\n") ; - read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; - } - } - } - - } - } - } else { - read($instr, $dummy, $packetlen) or die "Could not skip past this packet!\n"; - } - } - - return $key; -} - - -for (basename($0)) { - if (/^pem2openpgp$/) { - my $rsa; - my $stdin; - - my $uid = shift; - defined($uid) or die "You must specify a user ID string.\n"; - - # FIXME: fail if there is no given user ID; or should we default to - # hostname_long() from Sys::Hostname::Long ? - - - if (defined $ENV{PEM2OPENPGP_NEWKEY}) { - $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY}); - } else { - $stdin = do { - local $/; # slurp! - ; - }; - - $rsa = Crypt::OpenSSL::RSA->new_private_key($stdin); - } - - print pem2openpgp($rsa, - $uid, - { timestamp => $ENV{PEM2OPENPGP_TIMESTAMP}, - expiration => $ENV{PEM2OPENPGP_EXPIRATION}, - usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS}, - } - ); - } - elsif (/^openpgp2ssh$/) { - my $fpr = shift; - my $instream; - open($instream,'-'); - binmode($instream, ":bytes"); - my $key = openpgp2ssh($instream, $fpr); - if (defined($key)) { - if ($key->is_private()) { - print $key->get_private_key_string(); - } else { - print "ssh-rsa ".encode_base64(openssh_pubkey_pack($key), '')."\n"; - } - } else { - die "No matching key found.\n"; - } - } - else { - die "Unrecognized keytrans call.\n"; - } -} - diff --git a/src/openpgp2ssh b/src/openpgp2ssh new file mode 120000 index 0000000..edcb6a3 --- /dev/null +++ b/src/openpgp2ssh @@ -0,0 +1 @@ +share/keytrans \ No newline at end of file diff --git a/src/pem2openpgp b/src/pem2openpgp new file mode 120000 index 0000000..edcb6a3 --- /dev/null +++ b/src/pem2openpgp @@ -0,0 +1 @@ +share/keytrans \ No newline at end of file diff --git a/src/share/keytrans b/src/share/keytrans new file mode 100755 index 0000000..8bf17fb --- /dev/null +++ b/src/share/keytrans @@ -0,0 +1,775 @@ +#!/usr/bin/perl -w -T + +# pem2openpgp: take a PEM-encoded RSA private-key on standard input, a +# User ID as the first argument, and generate an OpenPGP secret key +# and certificate from it. + +# WARNING: the secret key material *will* appear on stdout (albeit in +# OpenPGP form) -- if you redirect stdout to a file, make sure the +# permissions on that file are appropriately locked down! + +# Usage: + +# pem2openpgp 'ssh://'$(hostname -f) < /etc/ssh/ssh_host_rsa_key | gpg --import + +# Authors: +# Jameson Rollins +# Daniel Kahn Gillmor + +# Started on: 2009-01-07 02:01:19-0500 + +# License: GPL v3 or later (we may need to adjust this given that this +# connects to OpenSSL via perl) + +use strict; +use warnings; +use File::Basename; +use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Bignum; +use Crypt::OpenSSL::Bignum::CTX; +use Digest::SHA1; +use MIME::Base64; +use POSIX; + +## make sure all length() and substr() calls use bytes only: +use bytes; + +my $old_format_packet_lengths = { one => 0, + two => 1, + four => 2, + indeterminate => 3, +}; + +# see RFC 4880 section 9.1 (ignoring deprecated algorithms for now) +my $asym_algos = { rsa => 1, + elgamal => 16, + dsa => 17, + }; + +# see RFC 4880 section 9.2 +my $ciphers = { plaintext => 0, + idea => 1, + tripledes => 2, + cast5 => 3, + blowfish => 4, + aes128 => 7, + aes192 => 8, + aes256 => 9, + twofish => 10, + }; + +# see RFC 4880 section 9.3 +my $zips = { uncompressed => 0, + zip => 1, + zlib => 2, + bzip2 => 3, + }; + +# see RFC 4880 section 9.4 +my $digests = { md5 => 1, + sha1 => 2, + ripemd160 => 3, + sha256 => 8, + sha384 => 9, + sha512 => 10, + sha224 => 11, + }; + +# see RFC 4880 section 5.2.3.21 +my $usage_flags = { certify => 0x01, + sign => 0x02, + encrypt_comms => 0x04, + encrypt_storage => 0x08, + encrypt => 0x0c, ## both comms and storage + split => 0x10, # the private key is split via secret sharing + authenticate => 0x20, + shared => 0x80, # more than one person holds the entire private key + }; + +# see RFC 4880 section 4.3 +my $packet_types = { pubkey_enc_session => 1, + sig => 2, + symkey_enc_session => 3, + onepass_sig => 4, + seckey => 5, + pubkey => 6, + sec_subkey => 7, + compressed_data => 8, + symenc_data => 9, + marker => 10, + literal => 11, + trust => 12, + uid => 13, + pub_subkey => 14, + uat => 17, + symenc_w_integrity => 18, + mdc => 19, + }; + +# see RFC 4880 section 5.2.1 +my $sig_types = { binary_doc => 0x00, + text_doc => 0x01, + standalone => 0x02, + generic_certification => 0x10, + persona_certification => 0x11, + casual_certification => 0x12, + positive_certification => 0x13, + subkey_binding => 0x18, + primary_key_binding => 0x19, + key_signature => 0x1f, + key_revocation => 0x20, + subkey_revocation => 0x28, + certification_revocation => 0x30, + timestamp => 0x40, + thirdparty => 0x50, + }; + + +# see RFC 4880 section 5.2.3.1 +my $subpacket_types = { sig_creation_time => 2, + sig_expiration_time => 3, + exportable => 4, + trust_sig => 5, + regex => 6, + revocable => 7, + key_expiration_time => 9, + preferred_cipher => 11, + revocation_key => 12, + issuer => 16, + notation => 20, + preferred_digest => 21, + preferred_compression => 22, + keyserver_prefs => 23, + preferred_keyserver => 24, + primary_uid => 25, + policy_uri => 26, + usage_flags => 27, + signers_uid => 28, + revocation_reason => 29, + features => 30, + signature_target => 31, + embedded_signature => 32, + }; + +# bitstring (see RFC 4880 section 5.2.3.24) +my $features = { mdc => 0x01 + }; + +# bitstring (see RFC 4880 5.2.3.17) +my $keyserver_prefs = { nomodify => 0x80 + }; + +###### end lookup tables ###### + +# FIXME: if we want to be able to interpret openpgp data as well as +# produce it, we need to produce key/value-swapped lookup tables as well. + + +########### Math/Utility Functions ############## + + +# see the bottom of page 43 of RFC 4880 +sub simple_checksum { + my $bytes = shift; + + return unpack("%32W*",$bytes) % 65536; +} + +# calculate the multiplicative inverse of a mod b this is euclid's +# extended algorithm. For more information see: +# http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm the +# arguments here should be Crypt::OpenSSL::Bignum objects. $a should +# be the larger of the two values, and the two values should be +# coprime. + +sub modular_multi_inverse { + my $a = shift; + my $b = shift; + + + my $origdivisor = $b->copy(); + + my $ctx = Crypt::OpenSSL::Bignum::CTX->new(); + my $x = Crypt::OpenSSL::Bignum->zero(); + my $y = Crypt::OpenSSL::Bignum->one(); + my $lastx = Crypt::OpenSSL::Bignum->one(); + my $lasty = Crypt::OpenSSL::Bignum->zero(); + + my $finalquotient; + my $finalremainder; + + while (! $b->is_zero()) { + my ($quotient, $remainder) = $a->div($b, $ctx); + + $a = $b; + $b = $remainder; + + my $temp = $x; + $x = $lastx->sub($quotient->mul($x, $ctx)); + $lastx = $temp; + + $temp = $y; + $y = $lasty->sub($quotient->mul($y, $ctx)); + $lasty = $temp; + } + + if (!$a->is_one()) { + die "did this math wrong.\n"; + } + + # let's make sure that we return a positive value because RFC 4880, + # section 3.2 only allows unsigned values: + + ($finalquotient, $finalremainder) = $lastx->add($origdivisor)->div($origdivisor, $ctx); + + return $finalremainder; +} + + +############ OpenPGP formatting functions ############ + +# make an old-style packet out of the given packet type and body. +# old-style (see RFC 4880 section 4.2) +sub make_packet { + my $type = shift; + my $body = shift; + my $options = shift; + + my $len = length($body); + my $pseudolen = $len; + + # if the caller wants to use at least N octets of packet length, + # pretend that we're using that many. + if (defined $options && defined $options->{'packet_length'}) { + $pseudolen = 2**($options->{'packet_length'} * 8) - 1; + } + if ($pseudolen < $len) { + $pseudolen = $len; + } + + my $lenbytes; + my $lencode; + + if ($pseudolen < 2**8) { + $lenbytes = $old_format_packet_lengths->{one}; + $lencode = 'C'; + } elsif ($pseudolen < 2**16) { + $lenbytes = $old_format_packet_lengths->{two}; + $lencode = 'n'; + } elsif ($pseudolen < 2**31) { + ## not testing against full 32 bits because i don't want to deal + ## with potential overflow. + $lenbytes = $old_format_packet_lengths->{four}; + $lencode = 'N'; + } else { + ## what the hell do we do here? + $lenbytes = $old_format_packet_lengths->{indeterminate}; + $lencode = ''; + } + + return pack('C'.$lencode, 0x80 + ($type * 4) + $lenbytes, $len). + $body; +} + + +# takes a Crypt::OpenSSL::Bignum, returns it formatted as OpenPGP MPI +# (RFC 4880 section 3.2) +sub mpi_pack { + my $num = shift; + + my $val = $num->to_bin(); + my $mpilen = length($val)*8; + +# this is a kludgy way to get the number of significant bits in the +# first byte: + my $bitsinfirstbyte = length(sprintf("%b", ord($val))); + + $mpilen -= (8 - $bitsinfirstbyte); + + return pack('n', $mpilen).$val; +} + +# takes a Crypt::OpenSSL::Bignum, returns an MPI packed in preparation +# for an OpenSSH-style public key format. see: +# http://marc.info/?l=openssh-unix-dev&m=121866301718839&w=2 +sub openssh_mpi_pack { + my $num = shift; + + my $val = $num->to_bin(); + my $mpilen = length($val); + + my $ret = pack('N', $mpilen); + + # if the first bit of the leading byte is high, we should include a + # 0 byte: + if (ord($val) & 0x80) { + $ret = pack('NC', $mpilen+1, 0); + } + + return $ret.$val; +} + +sub openssh_pubkey_pack { + my $key = shift; + + my ($modulus, $exponent) = $key->get_key_parameters(); + + return openssh_mpi_pack(Crypt::OpenSSL::Bignum->new_from_bin("ssh-rsa")). + openssh_mpi_pack($exponent). + openssh_mpi_pack($modulus); +} + +# pull an OpenPGP-specified MPI off of a given stream, returning it as +# a Crypt::OpenSSL::Bignum. +sub read_mpi { + my $instr = shift; + my $readtally = shift; + + my $bitlen; + read($instr, $bitlen, 2) or die "could not read MPI length.\n"; + $bitlen = unpack('n', $bitlen); + $$readtally += 2; + + my $bytestoread = POSIX::floor(($bitlen + 7)/8); + my $ret; + read($instr, $ret, $bytestoread) or die "could not read MPI body.\n"; + $$readtally += $bytestoread; + return Crypt::OpenSSL::Bignum->new_from_bin($ret); +} + + +# FIXME: genericize these to accept either RSA or DSA keys: +sub make_rsa_pub_key_body { + my $key = shift; + my $timestamp = shift; + + my ($n, $e) = $key->get_key_parameters(); + + return + pack('CN', 4, $timestamp). + pack('C', $asym_algos->{rsa}). + mpi_pack($n). + mpi_pack($e); +} + +sub make_rsa_sec_key_body { + my $key = shift; + my $timestamp = shift; + + # we're not using $a and $b, but we need them to get to $c. + my ($n, $e, $d, $p, $q) = $key->get_key_parameters(); + + my $c3 = modular_multi_inverse($p, $q); + + my $secret_material = mpi_pack($d). + mpi_pack($p). + mpi_pack($q). + mpi_pack($c3); + + # according to Crypt::OpenSSL::RSA, the closest value we can get out + # of get_key_parameters is 1/q mod p; but according to sec 5.5.3 of + # RFC 4880, we're actually looking for u, the multiplicative inverse + # of p, mod q. This is why we're calculating the value directly + # with modular_multi_inverse. + + return + pack('CN', 4, $timestamp). + pack('C', $asym_algos->{rsa}). + mpi_pack($n). + mpi_pack($e). + pack('C', 0). # seckey material is not encrypted -- see RFC 4880 sec 5.5.3 + $secret_material. + pack('n', simple_checksum($secret_material)); +} + +# expects an RSA key (public or private) and a timestamp +sub fingerprint { + my $key = shift; + my $timestamp = shift; + + my $rsabody = make_rsa_pub_key_body($key, $timestamp); + + return Digest::SHA1::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); +} + + +# FIXME: handle DSA keys as well! +sub pem2openpgp { + my $rsa = shift; + my $uid = shift; + my $args = shift; + + $rsa->use_sha1_hash(); + + # see page 22 of RFC 4880 for why i think this is the right padding + # choice to use: + $rsa->use_pkcs1_padding(); + + if (! $rsa->check_key()) { + die "key does not check"; + } + + my $version = pack('C', 4); + # strong assertion of identity: + my $sigtype = pack('C', $sig_types->{positive_certification}); + # RSA + my $pubkey_algo = pack('C', $asym_algos->{rsa}); + # SHA1 + my $hash_algo = pack('C', $digests->{sha1}); + + # FIXME: i'm worried about generating a bazillion new OpenPGP + # certificates from the same key, which could easily happen if you run + # this script more than once against the same key (because the + # timestamps will differ). How can we prevent this? + + # this environment variable (if set) overrides the current time, to + # be able to create a standard key? If we read the key from a file + # instead of stdin, should we use the creation time on the file? + my $timestamp = 0; + if (defined $args->{timestamp}) { + $timestamp = ($args->{timestamp} + 0); + } else { + $timestamp = time(); + } + + my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); + + + my $flags = 0; + if (! defined $args->{usage_flags}) { + $flags = $usage_flags->{certify}; + } else { + my @ff = split(",", $args->{usage_flags}); + foreach my $f (@ff) { + if (! defined $usage_flags->{$f}) { + die "No such flag $f"; + } + $flags |= $usage_flags->{$f}; + } + } + + my $usage_packet = pack('CCC', 2, $subpacket_types->{usage_flags}, $flags); + + + # how should we determine how far off to set the expiration date? + # default is no expiration. Specify the timestamp in seconds from the + # key creation. + my $expiration_packet = ''; + if (defined $args->{expiration}) { + my $expires_in = $args->{expiration} + 0; + $expiration_packet = pack('CCN', 5, $subpacket_types->{key_expiration_time}, $expires_in); + } + + + # prefer AES-256, AES-192, AES-128, CAST5, 3DES: + my $pref_sym_algos = pack('CCCCCCC', 6, $subpacket_types->{preferred_cipher}, + $ciphers->{aes256}, + $ciphers->{aes192}, + $ciphers->{aes128}, + $ciphers->{cast5}, + $ciphers->{tripledes} + ); + + # prefer SHA-1, SHA-256, RIPE-MD/160 + my $pref_hash_algos = pack('CCCCC', 4, $subpacket_types->{preferred_digest}, + $digests->{sha1}, + $digests->{sha256}, + $digests->{ripemd160} + ); + + # prefer ZLIB, BZip2, ZIP + my $pref_zip_algos = pack('CCCCC', 4, $subpacket_types->{preferred_compression}, + $zips->{zlib}, + $zips->{bzip2}, + $zips->{zip} + ); + + # we support the MDC feature: + my $feature_subpacket = pack('CCC', 2, $subpacket_types->{features}, + $features->{mdc}); + + # keyserver preference: only owner modify (???): + my $keyserver_pref = pack('CCC', 2, $subpacket_types->{keyserver_prefs}, + $keyserver_prefs->{nomodify}); + + my $subpackets_to_be_hashed = + $creation_time_packet. + $usage_packet. + $expiration_packet. + $pref_sym_algos. + $pref_hash_algos. + $pref_zip_algos. + $feature_subpacket. + $keyserver_pref; + + my $subpacket_octets = pack('n', length($subpackets_to_be_hashed)); + + my $sig_data_to_be_hashed = + $version. + $sigtype. + $pubkey_algo. + $hash_algo. + $subpacket_octets. + $subpackets_to_be_hashed; + + my $pubkey = make_rsa_pub_key_body($rsa, $timestamp); + my $seckey = make_rsa_sec_key_body($rsa, $timestamp); + + # this is for signing. it needs to be an old-style header with a + # 2-packet octet count. + + my $key_data = make_packet($packet_types->{pubkey}, $pubkey, {'packet_length'=>2}); + + # take the last 8 bytes of the fingerprint as the keyid: + my $keyid = substr(fingerprint($rsa, $timestamp), 20 - 8, 8); + + # the v4 signature trailer is: + + # version number, literal 0xff, and then a 4-byte count of the + # signature data itself. + my $trailer = pack('CCN', 4, 0xff, length($sig_data_to_be_hashed)); + + my $uid_data = + pack('CN', 0xb4, length($uid)). + $uid; + + my $datatosign = + $key_data. + $uid_data. + $sig_data_to_be_hashed. + $trailer; + + my $data_hash = Digest::SHA1::sha1_hex($datatosign); + + my $issuer_packet = pack('CCa8', 9, $subpacket_types->{issuer}, $keyid); + + my $sig = Crypt::OpenSSL::Bignum->new_from_bin($rsa->sign($datatosign)); + + my $sig_body = + $sig_data_to_be_hashed. + pack('n', length($issuer_packet)). + $issuer_packet. + pack('n', hex(substr($data_hash, 0, 4))). + mpi_pack($sig); + + return + make_packet($packet_types->{seckey}, $seckey). + make_packet($packet_types->{uid}, $uid). + make_packet($packet_types->{sig}, $sig_body); +} + + +sub openpgp2ssh { + my $instr = shift; + my $fpr = shift; + + if (defined $fpr) { + if (length($fpr) < 8) { + die "We need at least 8 hex digits of fingerprint.\n"; + } + $fpr = uc($fpr); + } + + my $packettag; + my $dummy; + my $tag; + + my $key; + + while (! eof($instr)) { + read($instr, $packettag, 1); + $packettag = ord($packettag); + + my $packetlen; + if ( ! (0x80 & $packettag)) { + die "This is not an OpenPGP packet\n"; + } + if (0x40 & $packettag) { + $tag = (0x3f & $packettag); + my $nextlen = 0; + read($instr, $nextlen, 1); + $nextlen = ord($nextlen); + if ($nextlen < 192) { + $packetlen = $nextlen; + } elsif ($nextlen < 224) { + my $newoct; + read($instr, $newoct, 1); + $newoct = ord($newoct); + $packetlen = (($nextlen - 192) << 8) + ($newoct) + 192; + } elsif ($nextlen == 255) { + read($instr, $nextlen, 4); + $packetlen = unpack('N', $nextlen); + } else { + # packet length is undefined. + } + } else { + my $lentype; + $lentype = 0x03 & $packettag; + $tag = ( 0x3c & $packettag ) >> 2; + if ($lentype == 0) { + read($instr, $packetlen, 1) or die "could not read packet length\n"; + $packetlen = unpack('C', $packetlen); + } elsif ($lentype == 1) { + read($instr, $packetlen, 2) or die "could not read packet length\n"; + $packetlen = unpack('n', $packetlen); + } elsif ($lentype == 2) { + read($instr, $packetlen, 4) or die "could not read packet length\n"; + $packetlen = unpack('N', $packetlen); + } else { + # packet length is undefined. + } + } + + if (! defined($packetlen)) { + die "Undefined packet lengths are not supported.\n"; + } + + if ($tag == $packet_types->{pubkey} || + $tag == $packet_types->{pub_subkey} || + $tag == $packet_types->{seckey} || + $tag == $packet_types->{sec_subkey}) { + my $ver; + my $readbytes = 0; + read($instr, $ver, 1) or die "could not read key version\n"; + $readbytes += 1; + $ver = ord($ver); + + if ($ver != 4) { + printf(STDERR "We only work with version 4 keys. This key appears to be version %s.\n", $ver); + read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; + } else { + + my $timestamp; + read($instr, $timestamp, 4) or die "could not read key timestamp.\n"; + $readbytes += 4; + $timestamp = unpack('N', $timestamp); + + my $algo; + read($instr, $algo, 1) or die "could not read key algorithm.\n"; + $readbytes += 1; + $algo = ord($algo); + if ($algo != $asym_algos->{rsa}) { + printf(STDERR "We only support RSA keys (this key used algorithm %d).\n", $algo); + read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; + } else { + ## we have an RSA key. + my $modulus = read_mpi($instr, \$readbytes); + my $exponent = read_mpi($instr, \$readbytes); + + my $pubkey = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, $exponent); + my $foundfpr = fingerprint($pubkey, $timestamp); + + my $foundfprstr = Crypt::OpenSSL::Bignum->new_from_bin($foundfpr)->to_hex(); + + # is this a match? + if ((!defined($fpr)) || + (substr($foundfprstr, -1 * length($fpr)) eq $fpr)) { + if (defined($key)) { + die "Found two matching keys.\n"; + } + $key = $pubkey; + } + + if ($tag == $packet_types->{seckey} || + $tag == $packet_types->{sec_subkey}) { + if (!defined($key)) { # we don't think the public part of + # this key matches + read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; + } else { + my $s2k; + read($instr, $s2k, 1) or die "Could not read S2K octet.\n"; + $readbytes += 1; + $s2k = ord($s2k); + if ($s2k == 0) { + # secret material is unencrypted + # see http://tools.ietf.org/html/rfc4880#section-5.5.3 + my $d = read_mpi($instr, \$readbytes); + my $p = read_mpi($instr, \$readbytes); + my $q = read_mpi($instr, \$readbytes); + my $u = read_mpi($instr, \$readbytes); + + my $checksum; + read($instr, $checksum, 2) or die "Could not read checksum of secret key material.\n"; + $readbytes += 2; + $checksum = unpack('n', $checksum); + + # FIXME: compare with the checksum! how? the data is + # gone into the Crypt::OpenSSL::Bignum + + $key = Crypt::OpenSSL::RSA->new_key_from_parameters($modulus, + $exponent, + $d, + $p, + $q); + + $key->check_key() or die "Secret key is not a valid RSA key.\n"; + } else { + print(STDERR "We cannot handle encrypted secret keys. Skipping!\n") ; + read($instr, $dummy, $packetlen - $readbytes) or die "Could not skip past this packet.\n"; + } + } + } + + } + } + } else { + read($instr, $dummy, $packetlen) or die "Could not skip past this packet!\n"; + } + } + + return $key; +} + + +for (basename($0)) { + if (/^pem2openpgp$/) { + my $rsa; + my $stdin; + + my $uid = shift; + defined($uid) or die "You must specify a user ID string.\n"; + + # FIXME: fail if there is no given user ID; or should we default to + # hostname_long() from Sys::Hostname::Long ? + + + if (defined $ENV{PEM2OPENPGP_NEWKEY}) { + $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY}); + } else { + $stdin = do { + local $/; # slurp! + ; + }; + + $rsa = Crypt::OpenSSL::RSA->new_private_key($stdin); + } + + print pem2openpgp($rsa, + $uid, + { timestamp => $ENV{PEM2OPENPGP_TIMESTAMP}, + expiration => $ENV{PEM2OPENPGP_EXPIRATION}, + usage_flags => $ENV{PEM2OPENPGP_USAGE_FLAGS}, + } + ); + } + elsif (/^openpgp2ssh$/) { + my $fpr = shift; + my $instream; + open($instream,'-'); + binmode($instream, ":bytes"); + my $key = openpgp2ssh($instream, $fpr); + if (defined($key)) { + if ($key->is_private()) { + print $key->get_private_key_string(); + } else { + print "ssh-rsa ".encode_base64(openssh_pubkey_pack($key), '')."\n"; + } + } else { + die "No matching key found.\n"; + } + } + else { + die "Unrecognized keytrans call.\n"; + } +} +