diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | doc/MonkeySpec | 105 | ||||
-rw-r--r-- | doc/README | 5 | ||||
-rw-r--r-- | doc/git init | 128 | ||||
-rw-r--r-- | gnutls-helpers.c | 17 | ||||
-rw-r--r-- | gnutls-helpers.h | 8 | ||||
-rw-r--r-- | gpg2ssh.c | 2 | ||||
-rw-r--r-- | ssh2gpg.c | 171 |
9 files changed, 438 insertions, 2 deletions
@@ -2,3 +2,4 @@ *.[ao] monkeysphere gpg2ssh +ssh2gpg @@ -4,6 +4,9 @@ monkeysphere: main.c gnutls-helpers.o gpg2ssh: gpg2ssh.c gnutls-helpers.o gcc -g -Wall --pedantic -o gpg2ssh gpg2ssh.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o +ssh2gpg: ssh2gpg.c gnutls-helpers.o + gcc -g -Wall --pedantic -o ssh2gpg ssh2gpg.c `libgnutls-config --libs --cflags` -lgnutls-extra gnutls-helpers.o + %.o: %.c gcc -g -Wall --pedantic -o $@ -c $< diff --git a/doc/MonkeySpec b/doc/MonkeySpec new file mode 100644 index 0000000..7a19df0 --- /dev/null +++ b/doc/MonkeySpec @@ -0,0 +1,105 @@ +THE MONKEYSPHERE +================ + +AGENDA +====== +[x] clowning +[ ] work +[x] jrollins will talk and gesture - in progress + +COMPONENTS +========== +* client-side componants +** "Marmoset": update known_hosts file with public key of server(s): +*** be responsible for removing keys from the file as key revocation happens +*** be responsible for updating a key in the file where there is a key replacement +*** must result in a file that is parsable by the existing ssh client without errors +*** manual management must be allowed without stomping on it +*** provide a simple, intelligible, clear policy for key acceptance +*** questions: should this query keyserver & update known host files? (we already + have awesome tool that queries keyservers and updates a web of trust (gpg) +** "Howler": simple script that could be placed as a trigger function (in your .ssh/config) +*** runs on connection to a certain host +*** triggers update to known_hosts file then makes connection +*** proxy-command | pre-hook script | wrapper script +** "Langur": policy-editor for viewing/editing policies + +* server-side componants +** "Rhesus" updates a per-user authorized_keys file, instead of updating a + known_hosts file from a public key by matching a specified user-id (for given + user: update authkeys file with public keys derived from authorized_uids + file) +*** Needs to operate with the same principles that Marmoset client-side does +** "Tamarin" triggers Rhesus during an attempt to initiate a connection or a scheduler (or both) +** "Barbary" - policy editor / viewer + +* common componants +** Create a ssh keypair from a openpgp keypair + +from ssh_config(5): + LocalCommand + Specifies a command to execute on the local machine after suc‐ + cessfully connecting to the server. The command string extends + to the end of the line, and is executed with /bin/sh. This + directive is ignored unless PermitLocalCommand has been enabled. + + +NOTES +===== +* Daniel and Elliot lie. <check> +* We will use a distributed VCS, each developer will create their own git repository and publish it publically for others to pull from, mail out +* public project page doesn't perhaps make sense yet +* approximate goal - using the web of trust to authenticate ppl for SSH +* outline of various components of monkeysphere +* M: what does it mean to be in the monkeysphere? not necessarily a great coder. +* J: interested in seeing project happen, not in actually doing it. anybody can contribute as much as they want. +* J: if we put the structure in place to work on monkeysphere then we don't have to do anything +* D: we are not creating +* understand gpg's keyring better, understanding tools better, building scripts +* Some debian packages allow automated configuration of config files. + + +* GENERAL GOAL - use openpgp web-of-trust to authenticate ppl for SSH +* SPECIFIC GOAL - allow openssh to tie into pgp web-of-trust without modifying either openpgp and openssh +* DESIGN GOALS - authentication, use the existing generic OpenSSH client, the admin can make it default, although end-user should be decide to use monkeysphere or not +* DESIGN GOAL - use of monkeysphere should not radically change connecting-to-server experience +* GOAL - pick a monkey-related name for each component + +Dramatis Personae: http://en.wikipedia.org/wiki/Alice_and_Bob +Backstory: http://www.conceptlabs.co.uk/alicebob.html + +* Use Case: Bob wants to sign on to the computer "mangabey" via monkeysphere + framework. He doesn't have access to the machine, but he knows Alice, who is + the admin of magabey. Alice creates a user bob and puts bob's userid in the + auth_user_ids file for bob. Tamarin triggers which causes Rhesus to take all + the things in the auth_userids file, takes those users, look son a keyserver + finds the public keys for the users, converts the gpg public keys into ssh + public keys and inserts those into a user_authorized_keys file. Bob goes to + connect, bob's ssh client which is monkeysphere enbaled, howler is triggered + which triggers marmoset which looks out into the web of trust and find an + OpenPGP key that has a userid that matches the URI of magabey. Marmoset checks + to see if this key for mangabey has been signed by any keys that you trust + (based on your policy). Has this key been signed by somebody that you trust? + If yes, connect, if no: abort or fail-through or whatever. Alice has signed + this uid, so Marmoset says "OK, this server has been verified" it then + converts the gpg public key into a ssh public key and then adds this gpg key + to the known_host file. ssh says, "you" are about to connect to magabey and + you know this is magabey because alice says so and you trust alice". The gpg + private key of bob has to be converted (somehow, via agent or something) into + a ssh private_key. SSH connection happens. + +Host identity piece of monkeysphere could be used without buying into the +authorization component. + +Monkeysphere is authentication layer that allows the sysadmin to perform +authorization on user identities instead of on keys, it additionally allows the +sysadmin also to authenticate the server to the end-user. + +git clone http://git.mlcastle.net/monkeysphere.git/ monkeysphere + +Fix gpgkey2ssh so that the entire key fingerprint will work, accept full fingerprint, or accept a pipe and do the conversion +Write manpage for gpgkey2ssh +gpg private key (start with passwordless) to PEM encoded private key: perl libraries, libopencdk / gnutls, gpgme +setup remote git repo +think through / plan merging of known_hosts (& auth_keys?) +think about policies and their representation
\ No newline at end of file diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..4c70d1d --- /dev/null +++ b/doc/README @@ -0,0 +1,5 @@ + Monkeysphere + ------------ + + +This is the README! diff --git a/doc/git init b/doc/git init new file mode 100644 index 0000000..7ba5071 --- /dev/null +++ b/doc/git init @@ -0,0 +1,128 @@ +remote$ mkdir public_html/git +(etch) +remote$ GIT_DIR=~/public_html/git/monkeysphere.git git init-db +remote$ cd ~/public_html/git/monkeysphere.git +remote$ chmod a+x hooks/post-update +# NOT SURE IF THIS IS NEEDED: remote$ git-update-server-info +fetch = +refs/heads/*:refs/remotes/dkg/* + +(newer) +remote$ mkdir -p public_html/git/monkey.git +remote$ cd public_html/git/monkey.git +remote$ git --bare init +remote$ chmod a+x hooks/post-update +remote$ git-update-server-info + +(new way! no origin/) +$ cd ~/src +$ mkdir monkeysphere +$ cd monkeysphere +$ git init +$ git remote add -f mlcastle http://git.mlcastle.net/monkeysphere.git/ +$ git remote add grunt grunt:/whatever +$ git config remote.grunt.push "+refs/heads/*" +$ git merge mlcastle/master +$ git push grunt + +(old way!) +(in ~/src or wherever) +local$ git clone http://git.mlcastle.net/monkeysphere.git/ monkeysphere +local$ cd monkeysphere + +.git/config: + +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + +## THIS ONE NEEDS TO BE CHANGED TO YOUR REMOTE URI +[remote "post"] + url = YOUR-REMOTE-URL/git/monkeysphere.git + push = +refs/heads/* +### THE ABOVE ONE NEEDS TO BE CHANGED + +[remote "mlcastle"] + url = http://git.mlcastle.net/monkeysphere.git/ + fetch = +refs/heads/*:refs/remotes/mlcastle/* + +[remote "jrollins"] + url = http://lair.fifthhorseman.net/~jrollins/git/monkeysphere.git/ + fetch = +refs/heads/*:refs/remotes/jrollins/* + +[remote "dkg"] + url = http://lair.fifthhorseman.net/~dkg/git/monkeysphere.git/ + fetch = +refs/heads/*:refs/remotes/dkg/* + +[remote "mjgoins"] SEE: dkg, jrollins, etc. + +[remote "micah"] + url = http://micah.riseup.net/git/monkeysphere.git + fetch = +refs/heads/*:refs/remotes/micah/* + +[remote "enw"] + url = http://lair.fifthhorseman.net/~enw/git/monkeysphere.git/ + fetch = +refs/heads/*:refs/remotes/enw/* + +[remote "rossg"] + url = http://lair.fifthhorseman.net/~rossg/git/monkeysphere.git/ + fetch = +refs/heads/*:refs/remotes/rossg/* + +[remote "greg"] + url = http://lair.fifthhorseman.net/~greg/git/monkeysphere.git/ + fetch = +refs/heads/*:refs/remotes/greg/* + blood type = + +----------------- +[remote "upload"] + url = ssh://z.mlcastle.net/var/www/git/monkeysphere.git/ + push = +refs/heads/* + + +$ git fetch dkg +$ git checkout master +$ git merge remotes/dkg/master +$ git push post + + + + + + + +grunt's fingerprint: be:43:9c:03:9c:04:1a:97:7a:61:8a:fe:71:9d:6c:67 +(grunt is lair.fifthhorseman.net) + +for foo in $(git remote); do git fetch $foo; done + + + +set mainfont {Arial 12} +set textfont { Courier 12} +set uifont {Arial 10 bold} +set tabstop 8 +set findmergefiles 0 +set maxgraphpct 50 +set maxwidth 16 +set cmitmode patch +set wrapcomment none +set showneartags 1 +set showlocalchanges 1 +set datetimeformat {%Y-%m-%d %H:%M:%S} +set limitdiffs 1 +set bgcolor white +set fgcolor black +set colors {green red blue magenta darkgrey brown orange} +set diffcolors {red "#00a000" blue} +set diffcontext 3 +set selectbgcolor gray85 +set geometry(main) 1280x936+14+28 +set geometry(topwidth) 1278 +set geometry(topheight) 286 +set geometry(pwsash0) "638 1" +set geometry(pwsash1) "903 1" +set geometry(botwidth) 1001 +set geometry(botheight) 638 +set permviews {} + diff --git a/gnutls-helpers.c b/gnutls-helpers.c index 5a567e2..6eae29e 100644 --- a/gnutls-helpers.c +++ b/gnutls-helpers.c @@ -345,3 +345,20 @@ int validate_ssh_host_userid(const char* userid) { setlocale(LC_ALL, oldlocale); return 1; } + +/* http://tools.ietf.org/html/rfc4880#section-5.5.2 */ +size_t get_openpgp_mpi_size(gnutls_datum_t* d) { + return 2 + d->size; +} + +int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d) { + uint16_t x; + + x = d->size * 8; + x = htons(x); + + write(fd, &x, sizeof(x)); + write(fd, d->data, d->size); + + return 0; +} diff --git a/gnutls-helpers.h b/gnutls-helpers.h index 398413f..9ea22a3 100644 --- a/gnutls-helpers.h +++ b/gnutls-helpers.h @@ -54,7 +54,7 @@ int set_datum_string(gnutls_datum_t* d, const char* s); datum */ int set_datum_fd(gnutls_datum_t* d, int fd); -/* read the file indicated (by na1me) in the fname parameter. store +/* read the file indicated (by name) in the fname parameter. store its entire contents in a single datum. */ int set_datum_file(gnutls_datum_t* d, const char* fname); @@ -64,3 +64,9 @@ int create_writing_pipe(pid_t* pid, const char* path, char* const argv[]); /* return 0 if userid matches the monkeysphere spec for ssh host user IDs */ int validate_ssh_host_userid(const char* userid); + +/* how many bytes will it take to write out this datum in OpenPGP MPI form? */ +size_t get_openpgp_mpi_size(gnutls_datum_t* d); + +/* write the MPI stored in gnutls_datum_t to file descriptor fd: */ +int write_openpgp_mpi_to_fd(int fd, gnutls_datum_t* d); @@ -146,7 +146,7 @@ int main(int argc, char* argv[]) { } else { err("primary key is only good for: 0x%08x. Trying subkeys...\n", usage); - if (ret = gnutls_openpgp_crt_get_auth_subkey(openpgp_crt, keyid), ret) { + if (ret = gnutls_openpgp_crt_get_auth_subkey(openpgp_crt, keyid, 0), ret) { err("failed to find a subkey capable of authentication (error: %d)\n", ret); return ret; } diff --git a/ssh2gpg.c b/ssh2gpg.c new file mode 100644 index 0000000..b14a540 --- /dev/null +++ b/ssh2gpg.c @@ -0,0 +1,171 @@ +#include "gnutls-helpers.h" + +#include <gnutls/openpgp.h> +#include <gnutls/x509.h> + +/* for waitpid() */ +#include <sys/types.h> +#include <sys/wait.h> + +/* for time() */ +#include <time.h> + +/* for htons() */ +#include <arpa/inet.h> + + +/* + Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> + Date: Sun, 2008-04-20 + License: GPL v3 or later + + monkeysphere public key translator: execute this with an ssh + private key on stdin. It currently only works with RSA keys. + + it should eventually work with OpenSSH-style public keys instead of + the full private key, but it was easier to do this way. + + It shoud spit out a version of the public key suitable for acting + as an OpenPGP public sub key packet. + + */ + +int main(int argc, char* argv[]) { + gnutls_datum_t data; + int ret; + gnutls_x509_privkey_t x509_privkey; + gnutls_openpgp_crt_t openpgp_crt; + gnutls_openpgp_keyid_t keyid; + printable_keyid p_keyid; + unsigned int keyidx; + unsigned int usage, bits; + gnutls_pk_algorithm_t algo; + + unsigned char packettag; + unsigned char openpgpversion; + time_t timestamp; + uint32_t clunkytime; + unsigned char openpgpalgo; + unsigned int packetlen; + uint16_t plen; + + gnutls_datum_t m, e, d, p, q, u, g, y; + gnutls_datum_t algolabel; + + char output_data[10240]; + char userid[10240]; + size_t uidsz = sizeof(userid); + + const gnutls_datum_t* all[5]; + int pipefd; + pid_t child_pid; + char* const args[] = {"/usr/bin/base64", "--wrap=0", NULL}; + const char* algoname; + int mpicount; + int pipestatus; + + init_gnutls(); + + init_datum(&data); + + init_datum(&m); + init_datum(&e); + init_datum(&d); + init_datum(&p); + init_datum(&q); + init_datum(&u); + init_datum(&g); + init_datum(&y); + + init_datum(&algolabel); + + init_keyid(keyid); + + /* slurp in the private key from stdin */ + if (ret = set_datum_fd(&data, 0), ret) { + err("didn't read file descriptor 0\n"); + return 1; + } + + + if (ret = gnutls_x509_privkey_init(&x509_privkey), ret) { + err("Failed to initialize private key structure (error: %d)\n", ret); + return 1; + } + + err("assuming PEM formatted private key\n"); + if (ret = gnutls_x509_privkey_import(x509_privkey, &data, GNUTLS_X509_FMT_PEM), ret) { + err("failed to import the PEM-encoded private key (error: %d)\n", ret); + return ret; + } + + algo = gnutls_x509_privkey_get_pk_algorithm(x509_privkey); + if (algo < 0) { + err("failed to get the algorithm of the PEM-encoded public key (error: %d)\n", algo); + return algo; + } else if (algo == GNUTLS_PK_RSA) { + err("RSA private key\n"); + ret = gnutls_x509_privkey_export_rsa_raw(x509_privkey, &m, &e, &d, &p, &q, &u); + if (GNUTLS_E_SUCCESS != ret) { + err ("failed to export RSA key parameters (error: %d)\n", ret); + return 1; + } + err("Modulus size %d, exponent size %d\n", m.size, e.size); + } else if (algo == GNUTLS_PK_DSA) { + err("DSA Key, not implemented!!\n", bits); + return 1; + } else { + err("Key was not RSA or DSA -- can't deal! (actual algorithm was: %d)\n", algo); + return 1; + } + + /* now we have algo, and the various MPI data are set. Can we + export them as a public subkey packet? */ + + /* this packet should be tagged 14, and should contain: + + 1 octet: version (4) + 4 octets: time of generation (seconds since 1970) + 1 octet: algo (http://tools.ietf.org/html/rfc4880#section-5.5.2 implies 1 for RSA) + + MPI: modulus + MPI: exponent + */ + + packetlen = 1 + 4 + 1; + /* FIXME: this is RSA only. for DSA, there'll be more: */ + packetlen += get_openpgp_mpi_size(&m) + get_openpgp_mpi_size(&e); + + /* FIXME: we should generate this bound more cleanly -- i just + happen to know that 65535 is 2^16-1: */ + if (packetlen > 65535) { + err("packet length is too long (%d)\n", packetlen); + return 1; + } + + /* we're going to emit an old-style packet, with tag 14 (public + subkey), with a two-octet packet length */ + packettag = 0x80 | (14 << 2) | 1; + + write(1, &packettag, sizeof(packettag)); + plen = htons(packetlen); + write(1, &plen, sizeof(plen)); + + openpgpversion = 4; + write(1, &openpgpversion, 1); + + timestamp = time(NULL); + clunkytime = htonl(timestamp); + write(1, &clunkytime, 4); + + /* FIXME: handle things other than RSA */ + openpgpalgo = 1; + write(1, &openpgpalgo, 1); + + write_openpgp_mpi_to_fd(1, &m); + write_openpgp_mpi_to_fd(1, &e); + + gnutls_x509_privkey_deinit(x509_privkey); + gnutls_global_deinit(); + return 0; +} |