diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/keytrans/Makefile | 15 | ||||
-rw-r--r-- | src/keytrans/gnutls-helpers.c | 466 | ||||
-rw-r--r-- | src/keytrans/gnutls-helpers.h | 89 | ||||
-rw-r--r-- | src/keytrans/openpgp2ssh.c | 507 | ||||
-rwxr-xr-x | src/keytrans/pem2openpgp | 519 | ||||
-rwxr-xr-x | src/monkeysphere | 16 | ||||
-rwxr-xr-x | src/monkeysphere-authentication | 31 | ||||
-rwxr-xr-x | src/monkeysphere-host | 17 | ||||
l--------- | src/openpgp2ssh | 1 | ||||
l--------- | src/pem2openpgp | 1 | ||||
-rw-r--r-- | src/share/common | 124 | ||||
-rw-r--r-- | src/share/defaultenv | 29 | ||||
-rwxr-xr-x | src/share/keytrans | 802 | ||||
-rw-r--r-- | src/share/m/subkey_to_ssh_agent | 63 | ||||
-rw-r--r-- | src/share/ma/add_certifier | 2 | ||||
-rw-r--r-- | src/share/ma/diagnostics | 2 | ||||
-rw-r--r-- | src/share/ma/setup | 6 | ||||
-rw-r--r-- | src/share/ma/update_users | 10 | ||||
-rw-r--r-- | src/share/mh/diagnostics | 4 | ||||
-rwxr-xr-x | src/transitions/0.23 | 57 |
20 files changed, 990 insertions, 1771 deletions
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 <dkg@fifthhorseman.net> */ -/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ -/* License: GPL v3 or later */ - -#include "gnutls-helpers.h" -/* for htonl() */ -#include <arpa/inet.h> - -/* for setlocale() */ -#include <locale.h> - -/* for isalnum() */ -#include <ctype.h> - -/* for exit() */ -#include <unistd.h> - -#include <assert.h> - -/* 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 <dkg@fifthhorseman.net> */ -/* Date: Fri, 04 Apr 2008 19:31:16 -0400 */ -/* License: GPL v3 or later */ - - -#include <gnutls/gnutls.h> -#include <gnutls/openpgp.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <unistd.h> -#include <stdarg.h> - -/* 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 <gnutls/openpgp.h> -#include <gnutls/x509.h> - -/* for waitpid() */ -#include <sys/types.h> -#include <sys/wait.h> - -/* - Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> - 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 2631da6..0000000 --- a/src/keytrans/pem2openpgp +++ /dev/null @@ -1,519 +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 <jrollins@finestructure.net> -# Daniel Kahn Gillmor <dkg@fifthhorseman.net> - -# 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 Crypt::OpenSSL::RSA; -use Crypt::OpenSSL::Bignum; -use Crypt::OpenSSL::Bignum::CTX; -use Digest::SHA1; -use MIME::Base64; - -## make sure all length() and substr() calls use bytes only: -use bytes; - -my $uid = shift; - -# FIXME: fail if there is no given user ID; or should we default to -# hostname_long() from Sys::Hostname::Long ? - - -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; -} - -# 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); -} - - -my $rsa; -if (defined $ENV{PEM2OPENPGP_NEWKEY}) { - $rsa = Crypt::OpenSSL::RSA->generate_key($ENV{PEM2OPENPGP_NEWKEY}); -} else { - # we're just not dealing with newline business right now. slurp in - # the whole file. - undef $/; - $rsa = Crypt::OpenSSL::RSA->new_private_key(<STDIN>); -} - -$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 $ENV{PEM2OPENPGP_TIMESTAMP}) { - $timestamp = ($ENV{PEM2OPENPGP_TIMESTAMP} + 0); -} else { - $timestamp = time(); -} - -my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); - - -my $flags = 0; -if (! defined $ENV{PEM2OPENPGP_USAGE_FLAGS}) { - $flags = $usage_flags->{certify}; -} else { - my @ff = split(",", $ENV{PEM2OPENPGP_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 $ENV{PEM2OPENPGP_EXPIRATION}) { - my $expires_in = $ENV{PEM2OPENPGP_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); - -print - make_packet($packet_types->{seckey}, $seckey). - make_packet($packet_types->{uid}, $uid). - make_packet($packet_types->{sig}, $sig_body); - - diff --git a/src/monkeysphere b/src/monkeysphere index 371983f..f721108 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" @@ -69,7 +70,7 @@ check_gpg_sec_key_id() { gpgSecOut=$(gpg_user --fixed-list-mode --list-secret-keys --with-colons 2>/dev/null | egrep '^sec:') ;; 1) - gpgSecOut=$(gpg_user --fixed-list-mode --list-secret-keys --with-colons "$keyID" | egrep '^sec:') || failure + gpgSecOut=$(gpg_user --fixed-list-mode --list-secret-keys --with-colons "$1" | egrep '^sec:') || failure ;; *) failure "You must specify only a single primary key ID." @@ -86,10 +87,10 @@ check_gpg_sec_key_id() { echo "$gpgSecOut" | cut -d: -f5 ;; *) - echo "Multiple primary secret keys found:" - echo "$gpgSecOut" | cut -d: -f5 - echo "Please specify which primary key to use." - failure + local seckeys=$(echo "$gpgSecOut" | cut -d: -f5) + failure "Multiple primary secret keys found: +$seckeys +Please specify which primary key to use." ;; esac } @@ -181,6 +182,7 @@ AUTHORIZED_KEYS=${MONKEYSPHERE_AUTHORIZED_KEYS:=$AUTHORIZED_KEYS} AUTHORIZED_USER_IDS=${MONKEYSPHERE_AUTHORIZED_USER_IDS:="${MONKEYSPHERE_HOME}/authorized_user_ids"} REQUIRED_HOST_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_HOST_KEY_CAPABILITY:="a"} REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"} +LOG_PREFIX=${MONKEYSPHERE_LOG_PREFIX:='ms: '} # export GNUPGHOME and make sure gpg home exists with proper # permissions @@ -239,7 +241,7 @@ case $COMMAND in ;; 'version'|'v') - echo "$VERSION" + version ;; '--help'|'help'|'-h'|'h'|'?') diff --git a/src/monkeysphere-authentication b/src/monkeysphere-authentication index 497470d..85ff04f 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 @@ -42,9 +43,6 @@ DATE=$(date -u '+%FT%T') # unset some environment variables that could screw things up unset GREP_OPTIONS -# default return code -RETURN=0 - ######################################################################## # FUNCTIONS ######################################################################## @@ -55,17 +53,17 @@ usage: $PGRM <subcommand> [options] [args] Monkeysphere authentication admin tool. subcommands: - update-users (u) [USER]... update user authorized_keys files + update-users (u) [USER]... update user authorized_keys files - add-id-certifier (c+) [KEYID|FILE] import and tsign a certification key - --domain (-n) DOMAIN limit ID certifications to DOMAIN - --trust (-t) TRUST trust level of certifier (full) - --depth (-d) DEPTH trust depth for certifier (1) - remove-id-certifier (c-) KEYID remove a certification key - list-id-certifiers (c) list certification keys + add-id-certifier (c+) KEYID|FILE import and tsign a certification key + [--domain (-n) DOMAIN] limit ID certifications to DOMAIN + [--trust (-t) TRUST] trust level of certifier (default: full) + [--depth (-d) DEPTH] trust depth for certifier (default: 1) + remove-id-certifier (c-) KEYID remove a certification key + list-id-certifiers (c) list certification keys - version (v) show version number - help (h,?) this help + version (v) show version number + help (h,?) this help See ${PGRM}(8) for more info. EOF @@ -102,7 +100,7 @@ core_fingerprint() { gpg_core_sphere_sig_transfer() { log debug "exporting core local sigs to sphere..." gpg_core --export-options export-local-sigs --export | \ - gpg_sphere "--import-options import-local-sigs --import" + gpg_sphere "--import-options import-local-sigs --import" 2>&1 | log debug } ######################################################################## @@ -131,6 +129,7 @@ REQUIRED_USER_KEY_CAPABILITY=${MONKEYSPHERE_REQUIRED_USER_KEY_CAPABILITY:="a"} GNUPGHOME_CORE=${MONKEYSPHERE_GNUPGHOME_CORE:="${MADATADIR}/core"} GNUPGHOME_SPHERE=${MONKEYSPHERE_GNUPGHOME_SPHERE:="${MADATADIR}/sphere"} CORE_KEYLENGTH=${MONKEYSPHERE_CORE_KEYLENGTH:="2048"} +LOG_PREFIX=${MONKEYSPHERE_LOG_PREFIX:='ms: '} # export variables needed in su invocation export DATE @@ -199,7 +198,7 @@ case $COMMAND in ;; 'version'|'v') - echo "$VERSION" + version ;; '--help'|'help'|'-h'|'h'|'?') @@ -211,5 +210,3 @@ case $COMMAND in Type '$PGRM help' for usage." ;; esac - -exit "$RETURN" diff --git a/src/monkeysphere-host b/src/monkeysphere-host index 1b0de0c..6136399 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 @@ -41,9 +42,6 @@ DATE=$(date -u '+%FT%T') # unset some environment variables that could screw things up unset GREP_OPTIONS -# default return code -RETURN=0 - ######################################################################## # FUNCTIONS ######################################################################## @@ -60,7 +58,7 @@ subcommands: set-expire (e) [EXPIRE] set host key expiration add-hostname (n+) NAME[:PORT] add hostname user ID to host key revoke-hostname (n-) NAME[:PORT] revoke hostname user ID - add-revoker (r+) [KEYID|FILE] add a revoker to the host key + add-revoker (r+) KEYID|FILE add a revoker to the host key revoke-key generate and/or publish revocation certificate for host key @@ -105,7 +103,7 @@ update_gpg_pub_file() { load_fingerprint() { if [ -f "$HOST_KEY_FILE" ] ; then HOST_FINGERPRINT=$( \ - (FUBAR=$(mktemp -d) && export GNUPGHOME="$FUBAR" \ + (FUBAR=$(msmktempdir) && export GNUPGHOME="$FUBAR" \ && gpg --quiet --import \ && gpg --quiet --list-keys --with-colons --with-fingerprint \ && rm -rf "$FUBAR") <"$HOST_KEY_FILE" \ @@ -177,7 +175,7 @@ show_key() { # create the ssh key TMPSSH="$GNUPGHOME"/ssh_host_key_rsa_pub - openpgp2ssh <"$HOST_KEY_FILE" 2>/dev/null >"$TMPSSH" + gpg --export | openpgp2ssh 2>/dev/null >"$TMPSSH" # get the gpg fingerprint HOST_FINGERPRINT=$(gpg --quiet --list-keys --with-colons --with-fingerprint \ @@ -232,6 +230,7 @@ PROMPT=${MONKEYSPHERE_PROMPT:=$PROMPT} # other variables GNUPGHOME_HOST=${MONKEYSPHERE_GNUPGHOME_HOST:="${MHDATADIR}"} +LOG_PREFIX=${MONKEYSPHERE_LOG_PREFIX:='ms: '} # export variables needed in su invocation export DATE @@ -315,7 +314,7 @@ case $COMMAND in ;; 'version'|'v') - echo "$VERSION" + version ;; '--help'|'help'|'-h'|'h'|'?') @@ -327,5 +326,3 @@ case $COMMAND in Type '$PGRM help' for usage." ;; esac - -exit "$RETURN" 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/common b/src/share/common index 5e0cb6a..ea872ba 100644 --- a/src/share/common +++ b/src/share/common @@ -14,33 +14,13 @@ # file) and are considered global ######################################################################## -### COMMON VARIABLES - -# managed directories -SYSCONFIGDIR=${MONKEYSPHERE_SYSCONFIGDIR:-"/etc/monkeysphere"} -export SYSCONFIGDIR - -# monkeysphere version -VERSION=0.23~pre - -# 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 +# output version info +version() { + cat "${SYSSHAREDIR}/VERSION" +} + # failure function. exits with code 255, unless specified otherwise. failure() { [ "$1" ] && echo "$1" >&2 @@ -96,11 +76,10 @@ log() { fi if [ "$priority" = "$level" -a "$output" = 'true' ] ; then if [ "$1" ] ; then - echo -n "ms: " >&2 - echo "$@" >&2 + echo "$@" else - cat >&2 - fi + cat + fi | sed 's/^/'"${LOG_PREFIX}"'/' >&2 fi done } @@ -334,52 +313,6 @@ passphrase_prompt() { fi } -test_gnu_dummy_s2k_extension() { - -# this block contains a demonstration private key that has had the -# primary key stripped out using the GNU S2K extension known as -# "gnu-dummy" (see /usr/share/doc/gnupg/DETAILS.gz). The subkey is -# present in cleartext, however. - -# openpgp2ssh will be able to deal with this based on whether the -# local copy of GnuTLS contains read_s2k support that can handle it. - -# read up on that here: - -# http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html - -echo " ------BEGIN PGP PRIVATE KEY BLOCK----- -Version: GnuPG v1.4.9 (GNU/Linux) - -lQCVBEO3YdABBACRqqEnucag4+vyZny2M67Pai5+5suIRRvY+Ly8Ms5MvgCi3EVV -xT05O/+0ShiRaf+QicCOFrhbU9PZzzU+seEvkeW2UCu4dQfILkmj+HBEIltGnHr3 -G0yegHj5pnqrcezERURf2e17gGFWX91cXB9Cm721FPXczuKraphKwCA9PwARAQAB -/gNlAkdOVQG0OURlbW9uc3RyYXRpb24gS2V5IGZvciBTMksgR05VIGV4dGVuc2lv -biAxMDAxIC0tIGdudS1kdW1teYi8BBMBAgAmBQJDt2HQAhsDBQkB4TOABgsJCAcD -AgQVAggDBBYCAwECHgECF4AACgkQQZUwSa4UDezTOQP/TMQXUVrWzHYZGopoPZ2+ -ZS3qddiznBHsgb7MGYg1KlTiVJSroDUBCHIUJvdQKZV9zrzrFl47D07x6hGyUPHV -aZXvuITW8t1o5MMHkCy3pmJ2KgfDvdUxrBvLfgPMICA4c6zA0mWquee43syEW9NY -g3q61iPlQwD1J1kX1wlimLCdAdgEQ7dh0AEEANAwa63zlQbuy1Meliy8otwiOa+a -mH6pxxUgUNggjyjO5qx+rl25mMjvGIRX4/L1QwIBXJBVi3SgvJW1COZxZqBYqj9U -8HVT07mWKFEDf0rZLeUE2jTm16cF9fcW4DQhW+sfYm+hi2sY3HeMuwlUBK9KHfW2 -+bGeDzVZ4pqfUEudABEBAAEAA/0bemib+wxub9IyVFUp7nPobjQC83qxLSNzrGI/ -RHzgu/5CQi4tfLOnwbcQsLELfker2hYnjsLrT9PURqK4F7udrWEoZ1I1LymOtLG/ -4tNZ7Mnul3wRC2tCn7FKx8sGJwGh/3li8vZ6ALVJAyOia5TZ/buX0+QZzt6+hPKk -7MU1WQIA4bUBjtrsqDwro94DvPj3/jBnMZbXr6WZIItLNeVDUcM8oHL807Am97K1 -ueO/f6v1sGAHG6lVPTmtekqPSTWBfwIA7CGFvEyvSALfB8NUa6jtk27NCiw0csql -kuhCmwXGMVOiryKEfegkIahf2bAd/gnWHPrpWp7bUE20v8YoW22I4wIAhnm5Wr5Q -Sy7EHDUxmJm5TzadFp9gq08qNzHBpXSYXXJ3JuWcL1/awUqp3tE1I6zZ0hZ38Ia6 -SdBMN88idnhDPqPoiKUEGAECAA8FAkO3YdACGyAFCQHhM4AACgkQQZUwSa4UDezm -vQP/ZhK+2ly9oI2z7ZcNC/BJRch0/ybQ3haahII8pXXmOThpZohr/LUgoWgCZdXg -vP6yiszNk2tIs8KphCAw7Lw/qzDC2hEORjWO4f46qk73RAgSqG/GyzI4ltWiDhqn -vnQCFl3+QFSe4zinqykHnLwGPMXv428d/ZjkIc2ju8dRsn4= -=CR5w ------END PGP PRIVATE KEY BLOCK----- -" | openpgp2ssh 4129E89D17C1D591 >/dev/null 2>/dev/null - -} - # remove all lines with specified string from specified file remove_line() { local file @@ -420,12 +353,15 @@ remove_monkeysphere_lines() { file="$1" - if [ -z "$file" ] ; then + # return error if file does not exist + if [ ! -e "$file" ] ; then return 1 fi - if [ ! -e "$file" ] ; then - return 1 + # just return ok if the file is empty, since there aren't any + # lines to remove + if [ ! -s "$file" ] ; then + return 0 fi tempfile=$(mktemp "${file}.XXXXXXX") || \ @@ -493,18 +429,21 @@ check_key_file_permissions() { # return 1 if path has invalid owner if [ "$owner" != "$uname" -a "$owner" != 'root' ] ; then - log error "improper ownership on path '$path'." + log error "improper ownership on path '$path':" + log error " $owner != ($uname|root)" return 1 fi # return 2 if path has group or other writability if is_write "$gAccess" || is_write "$oAccess" ; then - log error "improper group or other writability on path '$path'." + log error "improper group or other writability on path '$path':" + log error " group: $gAccess, other: $oAcess" return 2 fi # 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") @@ -732,14 +671,14 @@ process_user_id() { if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then log verbose " * acceptable primary key." if [ -z "$sshKey" ] ; then - log error " ! primary key could not be translated (not RSA or DSA?)." + log error " ! primary key could not be translated (not RSA?)." else echo "0:${sshKey}" fi else log debug " - unacceptable primary key." if [ -z "$sshKey" ] ; then - log debug " ! primary key could not be translated (not RSA or DSA?)." + log debug " ! primary key could not be translated (not RSA?)." else echo "1:${sshKey}" fi @@ -790,14 +729,14 @@ process_user_id() { if [ "$keyOK" -a "$uidOK" -a "$lastKeyOK" ] ; then log verbose " * acceptable sub key." if [ -z "$sshKey" ] ; then - log error " ! sub key could not be translated (not RSA or DSA?)." + log error " ! sub key could not be translated (not RSA?)." else echo "0:${sshKey}" fi else log debug " - unacceptable sub key." if [ -z "$sshKey" ] ; then - log debug " ! sub key could not be translated (not RSA or DSA?)." + log debug " ! sub key could not be translated (not RSA?)." else echo "1:${sshKey}" fi @@ -909,7 +848,7 @@ update_known_hosts() { (umask 0022 && touch "$KNOWN_HOSTS") # check permissions on the known_hosts file path - check_key_file_permissions "$USER" "$KNOWN_HOSTS" || failure + check_key_file_permissions $(whoami) "$KNOWN_HOSTS" || failure # create a lockfile on known_hosts: lock create "$KNOWN_HOSTS" @@ -970,7 +909,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' ' ') @@ -1058,8 +998,11 @@ 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 + check_key_file_permissions $(whoami) "$AUTHORIZED_KEYS" || failure # create a lockfile on authorized_keys lock create "$AUTHORIZED_KEYS" @@ -1131,10 +1074,11 @@ process_authorized_user_ids() { failure "authorized_user_ids file '$authorizedUserIDs' does not exist." fi - # check permissions on the authorized_user_ids file path - check_key_file_permissions "$USER" "$authorizedUserIDs" || failure + log debug "processing authorized_user_ids file:" + log debug " $authorizedUserIDs" - log debug "processing authorized_user_ids file..." + # check permissions on the authorized_user_ids file path + check_key_file_permissions $(whoami) "$authorizedUserIDs" || failure if ! meat "$authorizedUserIDs" > /dev/null ; then log debug " no user IDs to process." diff --git a/src/share/defaultenv b/src/share/defaultenv new file mode 100644 index 0000000..b54a518 --- /dev/null +++ b/src/share/defaultenv @@ -0,0 +1,29 @@ +# -*-shell-script-*- +# This should be sourced by bash (though we welcome changes to make it POSIX sh compliant) + +# Shared sh variables for the monkeysphere +# +# Written by +# Jameson Rollins <jrollins@finestructure.net> +# +# Copyright 2009, released under the GPL, version 3 or later + +# 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" + diff --git a/src/share/keytrans b/src/share/keytrans new file mode 100755 index 0000000..8b2e2ea --- /dev/null +++ b/src/share/keytrans @@ -0,0 +1,802 @@ +#!/usr/bin/perl -w -T + +# keytrans: this is an RSA key translation utility; it is capable of +# transforming RSA keys (both public keys and secret keys) between +# several popular representations, including OpenPGP, PEM-encoded +# PKCS#1 DER, and OpenSSH-style public key lines. + +# How it behaves depends on the name under which it is invoked. The +# two implementations currently are: pem2openpgp and openpgp2ssh. + + + +# 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 + + + + +# openpgp2ssh: take a stream of OpenPGP packets containing public or +# secret key material on standard input, and a Key ID (or fingerprint) +# as the first argument. Find the matching key in the input stream, +# and emit it on stdout in an OpenSSH-compatible format. If the input +# key is an OpenPGP public key (either primary or subkey), the output +# will be an OpenSSH single-line public key. If the input key is an +# OpenPGP secret key, the output will be a PEM-encoded RSA key. + +# Example usage: + +# gpg --export-secret-subkeys --export-options export-reset-subkey-passwd $KEYID | \ +# openpgp2ssh $KEYID | ssh-add /dev/stdin + + +# Authors: +# Jameson Rollins <jrollins@finestructure.net> +# Daniel Kahn Gillmor <dkg@fifthhorseman.net> + +# 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! + <STDIN>; + }; + + $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/share/m/subkey_to_ssh_agent b/src/share/m/subkey_to_ssh_agent index 4ce14f8..aa647a6 100644 --- a/src/share/m/subkey_to_ssh_agent +++ b/src/share/m/subkey_to_ssh_agent @@ -26,14 +26,6 @@ subkey_to_ssh_agent() { local publine local kname - if ! test_gnu_dummy_s2k_extension ; then - failure "Your version of GnuTLS does not seem capable of using with gpg's exported subkeys. -You may want to consider patching or upgrading to GnuTLS 2.6 or later. - -For more details, see: - http://lists.gnu.org/archive/html/gnutls-devel/2008-08/msg00005.html" - fi - # if there's no agent running, don't bother: if [ -z "$SSH_AUTH_SOCK" ] || ! which ssh-add >/dev/null ; then failure "No ssh-agent available." @@ -45,26 +37,34 @@ For more details, see: if [ "$sshaddresponse" = "2" ]; then failure "Could not connect to ssh-agent" fi - - # get list of secret keys (to work around bug - # https://bugs.g10code.com/gnupg/issue945): - secretkeys=$(gpg_user --list-secret-keys --with-colons --fixed-list-mode \ - --fingerprint | \ - grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }') - - if [ -z "$secretkeys" ]; then - failure "You have no secret keys in your keyring! + + # if the MONKEYSPHERE_SUBKEYS_FOR_AGENT variable is set, use the + # keys specified there + if [ "$MONKEYSPHERE_SUBKEYS_FOR_AGENT" ] ; then + authsubkeys="$MONKEYSPHERE_SUBKEYS_FOR_AGENT" + + # otherwise find all authentication-capable subkeys and use those + else + # get list of secret keys + # (to work around bug https://bugs.g10code.com/gnupg/issue945): + secretkeys=$(gpg_user --list-secret-keys --with-colons --fixed-list-mode \ + --fingerprint | \ + grep '^fpr:' | cut -f10 -d: | awk '{ print "0x" $1 "!" }') + + if [ -z "$secretkeys" ]; then + failure "You have no secret keys in your keyring! You might want to run 'gpg --gen-key'." - fi + fi - authsubkeys=$(gpg_user --list-secret-keys --with-colons --fixed-list-mode \ - --fingerprint --fingerprint $secretkeys | \ - cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \ - grep '^fpr::' | cut -f3 -d: | sort -u) - - if [ -z "$authsubkeys" ]; then - failure "no authentication-capable subkeys available. -You might want to 'monkeysphere gen-subkey'" + authsubkeys=$(gpg_user --list-secret-keys --with-colons --fixed-list-mode \ + --fingerprint --fingerprint $secretkeys | \ + cut -f1,5,10,12 -d: | grep -A1 '^ssb:[^:]*::[^:]*a[^:]*$' | \ + grep '^fpr::' | cut -f3 -d: | sort -u) + + if [ -z "$authsubkeys" ]; then + failure "no authentication-capable subkeys available. +You might want to run 'monkeysphere gen-subkey'." + fi fi workingdir=$(msmktempdir) @@ -76,7 +76,16 @@ You might want to 'monkeysphere gen-subkey'" # through to ssh-add. should we limit it to known ones? For # example: -d or -c and/or -t <lifetime> - for subkey in $authsubkeys; do + for subkey in $authsubkeys; do + # test that the subkey has proper capability + capability=$(gpg_user --list-secret-keys --with-colons --fixed-list-mode \ + --fingerprint --fingerprint "0x${subkey}!" \ + | egrep -B 1 "^fpr:::::::::${subkey}:$" | grep "^ssb:" | cut -d: -f12) + if ! check_capability "$capability" 'a' ; then + log error "Did not find authentication-capable subkey with key ID '$subkey'." + continue + fi + # choose a label by which this key will be known in the agent: # we are labelling the key by User ID instead of by # fingerprint, but filtering out all / characters to make sure diff --git a/src/share/ma/add_certifier b/src/share/ma/add_certifier index 6f85ecf..544a3f0 100644 --- a/src/share/ma/add_certifier +++ b/src/share/ma/add_certifier @@ -108,7 +108,7 @@ if [ -f "$keyID" -o "$keyID" = '-' ] ; then fi # load the key - gpg_sphere "--import" <"$keyID" \ + gpg_sphere "--import" <"$keyID" 2>/dev/null \ || failure "could not read key from '$keyID'" # else, get the key from the keyserver diff --git a/src/share/ma/diagnostics b/src/share/ma/diagnostics index 8fc4b31..8eca586 100644 --- a/src/share/ma/diagnostics +++ b/src/share/ma/diagnostics @@ -103,7 +103,7 @@ fi # make sure that at least one identity certifier exists echo echo "Checking for Identity Certifiers..." -if ! monkeysphere-authentication list-identity-certifiers | egrep -q '^[A-F0-9]{40}:' then +if ! ( monkeysphere-authentication list-identity-certifiers | egrep '^[A-F0-9]{40}:' >/dev/null ) ; then echo "! No Identity Certifiers found!" echo " - Recommendation: once you know who should be able to certify the identities of connecting users, you should add their key, with: diff --git a/src/share/ma/setup b/src/share/ma/setup index e77afff..b453f3c 100644 --- a/src/share/ma/setup +++ b/src/share/ma/setup @@ -57,7 +57,7 @@ EOF if [ -z "$CORE_FPR" ] ; then log info "setting up Monkeysphere authentication trust core..." - local CORE_UID=$(printf "Monkeysphere authentication trust core UID (random string: %s)" $(head -c21 </dev/urandom | base64)) + local CORE_UID=$(printf "Monkeysphere authentication trust core UID (random string: %s)" $(head -c21 </dev/urandom | perl -MMIME::Base64 -ne 'print encode_base64($_)')) log debug "generating monkeysphere authentication trust core key ($CORE_KEYLENGTH bits)..." PEM2OPENPGP_USAGE_FLAGS=certify \ @@ -82,7 +82,7 @@ EOF # ensure that the authentication sphere checker has absolute ownertrust on the expected key. log debug "setting ultimate owner trust on core key in gpg_sphere..." - printf "%s:6:\n" "$CORE_FPR" | gpg_sphere "--import-ownertrust" + printf "%s:6:\n" "$CORE_FPR" | gpg_sphere "--import-ownertrust" 2>&1 | log verbose gpg_sphere "--export-ownertrust" 2>&1 | log debug # check the owner trust @@ -101,7 +101,7 @@ EOF # our preferences are reasonable (i.e. 3 marginal OR 1 fully # trusted certifications are sufficient to grant full validity. log debug "checking trust model for authentication ..." - local TRUST_MODEL=$(gpg_sphere "--with-colons --fixed-list-mode --list-keys" \ + local TRUST_MODEL=$(gpg_sphere "--with-colons --fixed-list-mode --list-keys" 2>/dev/null \ | head -n1 | grep "^tru:" | cut -d: -f3,6,7) log debug "sphere trust model: $TRUST_MODEL" if [ "$TRUST_MODEL" != '1:3:1' ] ; then diff --git a/src/share/ma/update_users b/src/share/ma/update_users index bfefc31..3a5c006 100644 --- a/src/share/ma/update_users +++ b/src/share/ma/update_users @@ -13,6 +13,7 @@ update_users() { +local returnCode=0 local unames local uname local authorizedKeysDir @@ -26,8 +27,6 @@ else unames=$(getent passwd | cut -d: -f1) fi -RETURN=0 - # set mode MODE="authorized_keys" @@ -81,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 @@ -94,7 +94,7 @@ for uname in $unames ; do # process authorized_user_ids file, as monkeysphere user su_monkeysphere_user \ ". ${SYSSHAREDIR}/common; process_authorized_user_ids $TMP_AUTHORIZED_USER_IDS" \ - || RETURN="$?" + || returnCode="$?" else log debug "not processing authorized_user_ids." fi @@ -141,7 +141,7 @@ for uname in $unames ; do log error "Failed to install authorized_keys for '$uname'!" rm -f "${authorizedKeysDir}/${uname}" # indicate that there has been a failure: - RETURN=1 + returnCode=1 } else rm -f "${authorizedKeysDir}/${uname}" @@ -154,5 +154,5 @@ for uname in $unames ; do rm -rf "$TMPLOC" done -return $RETURN +return $returnCode } diff --git a/src/share/mh/diagnostics b/src/share/mh/diagnostics index 2f65f89..3746020 100644 --- a/src/share/mh/diagnostics +++ b/src/share/mh/diagnostics @@ -63,11 +63,11 @@ else if [ "$expire" ]; then if (( "$expire" < "$curdate" )); then echo "! Host key is expired." - echo " - Recommendation: extend lifetime of key with 'monkeysphere-host extend-key'" + echo " - Recommendation: extend lifetime of key with 'monkeysphere-host set-expire'" problemsfound=$(($problemsfound+1)) elif (( "$expire" < "$warndate" )); then echo "! Host key expires in less than $warnwindow:" $(advance_date $(( $expire - $curdate )) seconds +%F) - echo " - Recommendation: extend lifetime of key with 'monkeysphere-host extend-key'" + echo " - Recommendation: extend lifetime of key with 'monkeysphere-host set-expire'" problemsfound=$(($problemsfound+1)) fi fi diff --git a/src/transitions/0.23 b/src/transitions/0.23 index f09dfff..3964558 100755 --- a/src/transitions/0.23 +++ b/src/transitions/0.23 @@ -21,6 +21,7 @@ set -e SYSDATADIR=${MONKEYSPHERE_SYSDATADIR:-"/var/lib/monkeysphere"} +SYSCONFIGDIR=${MONKEYSPHERE_SYSCONFIGDIR:-"/etc/monkeysphere"} MADATADIR="${SYSDATADIR}/authentication" MHDATADIR="${SYSDATADIR}/host" @@ -43,6 +44,13 @@ is_domain_name() { printf "%s" "$1" | egrep -q '^[[:alnum:]][[:alnum:]-.]*[[:alnum:]]$' } + +# move the old server conf file to be the authentication conf file +if [ -f "$SYSCONFIGDIR"/monkeysphere-server.conf -a \ + ! -f "$SYSCONFIGDIR"/monkeysphere-authentication.conf ] ; then + mv "$SYSCONFIGDIR"/monkeysphere-server.conf "$SYSCONFIGDIR"/monkeysphere-authentication.conf +fi + # run the authentication setup (this is also the first chance to bail # if 0.23 is not fully-installed, because m-a did not exist before # 0.23) @@ -63,7 +71,7 @@ if [ -d "$SYSDATADIR"/gnupg-host ] ; then # get the old host keygrip (don't know why there would be more # than one, but we'll transfer all tsigs made by any key that # had been given ultimate ownertrust): - for authgrip in $(GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --no-permission-warning --export-ownertrust | \ + for authgrip in $(GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --export-ownertrust | \ grep ':6:$' | \ sed -r 's/^[A-F0-9]{24}([A-F0-9]{16}):6:$/\1/') ; do @@ -79,7 +87,7 @@ if [ -d "$SYSDATADIR"/gnupg-host ] ; then # one of those certifications (even if later # certifications had different parameters). - GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --no-permission-warning --fingerprint --with-colons --fixed-list-mode --check-sigs | \ + GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --fingerprint --with-colons --fixed-list-mode --check-sigs | \ cut -f 1,2,5,8,9,10 -d: | \ egrep '^(fpr:::::|sig:!:'"$authgrip"':[[:digit:]]+ [[:digit:]]+:)' | \ while IFS=: read -r type validity grip trustparams trustdomain fpr ; do @@ -121,7 +129,7 @@ if [ -d "$SYSDATADIR"/gnupg-host ] ; then CERTKEY=$(mktemp ${TMPDIR:-/tmp}/mstransition.XXXXXXXX) log "Adding identity certifier with fingerprint %s\n" "$keyfpr" - GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --no-permission-warning --export "0x$keyfpr" --export-options export-clean >"$CERTKEY" + GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --export "0x$keyfpr" --export-options export-clean >"$CERTKEY" MONKEYSPHERE_PROMPT=false monkeysphere-authentication add-identity-certifier $finaldomain --trust "$truststring" --depth "$trustdepth" "$CERTKEY" rm -f "$CERTKEY" # clear the fingerprint so that we don't @@ -141,16 +149,39 @@ if [ -d "$SYSDATADIR"/gnupg-host ] ; then log "Not transferring host key info because host directory already exists.\n" else if [ -s "$SYSDATADIR"/ssh_host_rsa_key ] || \ - GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --no-permission-warning --with-colons --list-secret-keys | grep -q '^sec:' ; then + GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --with-colons --list-secret-keys | grep -q '^sec:' ; then + + FPR=$(GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --with-colons --fixed-list-mode --list-secret-keys --fingerprint | awk -F: '/^fpr:/{ print $10 }' ) # create host home - mkdir -p "${MHDATADIR}" - chmod 0700 "${MHDATADIR}" + mkdir -p $(dirname "$MHDATADIR") + NEWDATADIR=$(mktemp -d "${MHDATADIR}.XXXXXX") + chmod 0700 "${NEWDATADIR}" log "importing host key from old monkeysphere installation\n" - GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --no-permission-warning --export-secret-keys | \ - GNUPGHOME="$MHDATADIR" gpg --quiet --no-tty --import + +# export from the pubring as well as the that new (non-expired) +# self-sigs are available, otherwise the secret key import may fail + +# FIXME: turns out the secret key import fails anyway, stupidly :( + +# FIXME: if all self-sigs are expired, then the secret key import may +# fail anyway. How should we deal with that? + if (GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --export-secret-keys && \ + GNUPGHOME="$SYSDATADIR"/gnupg-host gpg --quiet --no-tty --no-permission-warning --export "$FPR") | \ + GNUPGHOME="$NEWDATADIR" gpg --quiet --no-tty --import ; then + : we are in good shape! + else + if ! GNUPGHOME="$NEWDATADIR" gpg --quiet --no-tty --list-secret-key >/dev/null ; then + log "The old host key (%s) was not imported properly.\n" "$FPR" + exit 1 + fi + fi + + # if we get here cleanly, then we're OK to move forward: + mv "$NEWDATADIR" "$MHDATADIR" + monkeysphere-host update-gpg-pub-file else log "No host key found in old monkeysphere install; not importing any host key.\n" @@ -162,7 +193,7 @@ if [ -d "$SYSDATADIR"/gnupg-host ] ; then mkdir -p "$STASHDIR" chmod 0700 "$STASHDIR" - mv "${SYSDATADIR}/gnupg-host" "$STASHDIR" + mv "${SYSDATADIR}/gnupg-host" "$STASHDIR"/gnupg-host.$(date '+%F_%T%z') fi @@ -171,10 +202,12 @@ fi # the new authentication keyring. if [ -d "${SYSDATADIR}/gnupg-authentication" ] ; then - GNUPGHOME="${SYSDATADIR}/gnupg-authentication" gpg --no-permission-warning --export | \ - monkeysphere-authentication gpg-cmd --import + GNUPGHOME="${SYSDATADIR}/gnupg-authentication" \ + gpg --quiet --no-tty --no-permission-warning --export 2>/dev/null | \ + monkeysphere-authentication gpg-cmd --import 2>/dev/null || \ + log "No OpenPGP certificates imported into monkeysphere-authentication trust sphere.\n" mkdir -p "$STASHDIR" chmod 0700 "$STASHDIR" - mv "${SYSDATADIR}/gnupg-authentication" "$STASHDIR" + mv "${SYSDATADIR}/gnupg-authentication" "$STASHDIR"/gnupg-authentication.$(date '+%F_%T%z') fi |