From e71c7bb4dff26178f714cd0fcdbb3058effa4066 Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sun, 22 Feb 2009 12:07:34 -0500 Subject: Fix how version number is saved/retrieved. Version is now stored in VERSION file, which is created in the tarball target. This is then installed at /usr/share/monkeysphere/VERSION, and cat'ed when the version number is requested by the front-end ui. No more manual setting of version number required (to avoid future problems, aka "0.23.1"). This system is also more flexible, as the VERSION file could potentially hold more info than just the release number. --- Makefile | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 71df92b..0284a8a 100755 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ tarball: clean mkdir -p monkeysphere-$(MONKEYSPHERE_VERSION)/doc ln -s ../../website/getting-started-user.mdwn ../../website/getting-started-admin.mdwn ../../doc/TODO ../../doc/MonkeySpec monkeysphere-$(MONKEYSPHERE_VERSION)/doc ln -s ../COPYING ../etc ../Makefile ../man ../src ../tests monkeysphere-$(MONKEYSPHERE_VERSION) + echo $(MONKEYSPHERE_VERSION) > monkeysphere-$(MONKEYSPHERE_VERSION)/VERSION tar -ch --exclude='*~' monkeysphere-$(MONKEYSPHERE_VERSION) | gzip -n > monkeysphere_$(MONKEYSPHERE_VERSION).orig.tar.gz rm -rf monkeysphere-$(MONKEYSPHERE_VERSION) @@ -50,6 +51,7 @@ install: all installman mkdir -p $(DESTDIR)$(PREFIX)/share/monkeysphere/m $(DESTDIR)$(PREFIX)/share/monkeysphere/mh $(DESTDIR)$(PREFIX)/share/monkeysphere/ma $(DESTDIR)$(PREFIX)/share/monkeysphere/transitions 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-host src/monkeysphere-authentication $(DESTDIR)$(PREFIX)/sbin install -m 0644 src/share/common $(DESTDIR)$(PREFIX)/share/monkeysphere -- cgit v1.2.3 From 2c427b22f6a780cbf0d4e22fce26071727e985a1 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 1 Mar 2009 11:45:38 -0500 Subject: transition to the perl-based keytrans implementation. --- Makefile | 12 +- src/keytrans/Makefile | 15 - src/keytrans/gnutls-helpers.c | 466 ------------------------- src/keytrans/gnutls-helpers.h | 89 ----- src/keytrans/openpgp2ssh.c | 507 --------------------------- src/keytrans/pem2openpgp | 775 ------------------------------------------ src/openpgp2ssh | 1 + src/pem2openpgp | 1 + src/share/keytrans | 775 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 783 insertions(+), 1858 deletions(-) delete mode 100644 src/keytrans/Makefile delete mode 100644 src/keytrans/gnutls-helpers.c delete mode 100644 src/keytrans/gnutls-helpers.h delete mode 100644 src/keytrans/openpgp2ssh.c delete mode 100755 src/keytrans/pem2openpgp create mode 120000 src/openpgp2ssh create mode 120000 src/pem2openpgp create mode 100755 src/share/keytrans (limited to 'Makefile') 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"; + } +} + -- cgit v1.2.3 From defa3f1f158ee1baccdfdcab7db970380b39dd26 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Sun, 1 Mar 2009 13:20:07 -0500 Subject: added "test" target for make --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 2c6077e..07e8fb9 100755 --- a/Makefile +++ b/Makefile @@ -76,4 +76,7 @@ installman: releasenote: ./utils/build-releasenote -.PHONY: all tarball debian-package freebsd-distinfo clean install installman releasenote +test: + ./tests/basic + +.PHONY: all tarball debian-package freebsd-distinfo clean install installman releasenote test -- cgit v1.2.3 From ebd776722e0fd6dfacc79146c368d148f0e266cb Mon Sep 17 00:00:00 2001 From: Jameson Graef Rollins Date: Sun, 1 Mar 2009 14:53:37 -0500 Subject: break out default variables into their own file: defaultenv this allows the common file to be sourced without reseting variables to their defaults, which was causing a problem with su_monkeysphere_user. also added some more debug messages. --- Makefile | 1 + src/monkeysphere | 3 ++- src/monkeysphere-authentication | 3 ++- src/monkeysphere-host | 3 ++- src/share/common | 34 +++++++++------------------------- src/share/ma/update_users | 1 + tests/basic | 1 - 7 files changed, 17 insertions(+), 29 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index 07e8fb9..9873d32 100755 --- a/Makefile +++ b/Makefile @@ -52,6 +52,7 @@ install: all installman 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 0644 src/share/defaultenv $(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 diff --git a/src/monkeysphere b/src/monkeysphere index 2d54376..8d59d08 100755 --- a/src/monkeysphere +++ b/src/monkeysphere @@ -18,7 +18,8 @@ PGRM=$(basename $0) SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"} export SYSSHAREDIR -. "${SYSSHAREDIR}/common" || exit 1 +. "${SYSSHAREDIR}/defaultenv" +. "${SYSSHAREDIR}/common" # sharedir for host functions MSHAREDIR="${SYSSHAREDIR}/m" diff --git a/src/monkeysphere-authentication b/src/monkeysphere-authentication index c5c48d5..3344f38 100755 --- a/src/monkeysphere-authentication +++ b/src/monkeysphere-authentication @@ -21,7 +21,8 @@ PGRM=$(basename $0) SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"} export SYSSHAREDIR -. "${SYSSHAREDIR}/common" || exit 1 +. "${SYSSHAREDIR}/defaultenv" +. "${SYSSHAREDIR}/common" SYSDATADIR=${MONKEYSPHERE_SYSDATADIR:-"/var/lib/monkeysphere"} export SYSDATADIR diff --git a/src/monkeysphere-host b/src/monkeysphere-host index 9e4a8c4..b9a15ae 100755 --- a/src/monkeysphere-host +++ b/src/monkeysphere-host @@ -21,7 +21,8 @@ PGRM=$(basename $0) SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"} export SYSSHAREDIR -. "${SYSSHAREDIR}/common" || exit 1 +. "${SYSSHAREDIR}/defaultenv" +. "${SYSSHAREDIR}/common" SYSDATADIR=${MONKEYSPHERE_SYSDATADIR:-"/var/lib/monkeysphere"} export SYSDATADIR diff --git a/src/share/common b/src/share/common index a9d23b2..1cdd549 100644 --- a/src/share/common +++ b/src/share/common @@ -13,28 +13,6 @@ # all-caps variables are meant to be user supplied (ie. from config # file) and are considered global -######################################################################## -### COMMON VARIABLES - -# managed directories -SYSCONFIGDIR=${MONKEYSPHERE_SYSCONFIGDIR:-"/etc/monkeysphere"} -export SYSCONFIGDIR - -# default log level -LOG_LEVEL="INFO" - -# default keyserver -KEYSERVER="pool.sks-keyservers.net" - -# whether or not to check keyservers by defaul -CHECK_KEYSERVER="true" - -# default monkeysphere user -MONKEYSPHERE_USER="monkeysphere" - -# default about whether or not to prompt -PROMPT="true" - ######################################################################## ### UTILITY FUNCTIONS @@ -461,6 +439,7 @@ check_key_file_permissions() { # return zero if all clear, or go to next path if [ "$path" = '/' ] ; then + log debug "path ok." return 0 else check_key_file_permissions "$uname" $(dirname "$path") @@ -926,7 +905,8 @@ process_known_hosts() { failure "known_hosts file '$KNOWN_HOSTS' does not exist." fi - log debug "processing known_hosts file..." + log debug "processing known_hosts file:" + log debug " $KNOWN_HOSTS" hosts=$(meat "$KNOWN_HOSTS" | cut -d ' ' -f 1 | grep -v '^|.*$' | tr , ' ' | tr '\n' ' ') @@ -1014,6 +994,9 @@ update_authorized_keys() { nIDsOK=0 nIDsBAD=0 + log debug "updating authorized_keys file:" + log debug " $AUTHORIZED_KEYS" + # check permissions on the authorized_keys file path check_key_file_permissions "$USER" "$AUTHORIZED_KEYS" || failure @@ -1087,11 +1070,12 @@ process_authorized_user_ids() { failure "authorized_user_ids file '$authorizedUserIDs' does not exist." fi + log debug "processing authorized_user_ids file:" + log debug " $authorizedUserIDs" + # check permissions on the authorized_user_ids file path check_key_file_permissions "$USER" "$authorizedUserIDs" || failure - log debug "processing authorized_user_ids file..." - if ! meat "$authorizedUserIDs" > /dev/null ; then log debug " no user IDs to process." return diff --git a/src/share/ma/update_users b/src/share/ma/update_users index c180b56..3a5c006 100644 --- a/src/share/ma/update_users +++ b/src/share/ma/update_users @@ -80,6 +80,7 @@ for uname in $unames ; do # translating ssh-style path variables authorizedUserIDs=$(translate_ssh_variables "$uname" "$AUTHORIZED_USER_IDS") if [ -s "$authorizedUserIDs" ] ; then + log debug "authorized_user_ids file found." # check permissions on the authorized_user_ids file path if check_key_file_permissions "$uname" "$authorizedUserIDs" ; then # copy user authorized_user_ids file to temporary diff --git a/tests/basic b/tests/basic index f6d1f3b..7277168 100755 --- a/tests/basic +++ b/tests/basic @@ -275,7 +275,6 @@ monkeysphere-authentication update-users $(whoami) # FIXME: this is maybe not failing properly for: # ms: improper group or other writability on path '/tmp'. - ###################################################################### ### TESTS -- cgit v1.2.3