diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/common | 3 | ||||
-rwxr-xr-x | src/keytrans/pem2openpgp | 252 | ||||
-rwxr-xr-x | src/monkeysphere | 107 | ||||
-rwxr-xr-x | src/monkeysphere-server | 154 |
4 files changed, 400 insertions, 116 deletions
@@ -19,6 +19,9 @@ SYSCONFIGDIR=${MONKEYSPHERE_SYSCONFIGDIR:-"/etc/monkeysphere"} export SYSCONFIGDIR +# monkeysphere version +VERSION=__VERSION__ + ######################################################################## ### UTILITY FUNCTIONS diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp new file mode 100755 index 0000000..2fa221d --- /dev/null +++ b/src/keytrans/pem2openpgp @@ -0,0 +1,252 @@ +#!/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 certificate +# from it. + +# 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 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 ? + +# 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 $len = length($body); + + my $lenbytes; + my $lencode; + + if ($len < 2**8) { + $lenbytes = 0; + $lencode = 'C'; + } elsif ($len < 2**16) { + $lenbytes = 1; + $lencode = 'n'; + } elsif ($len < 2**31) { + ## not testing against full 32 bits because i don't want to deal + ## with potential overflow. + $lenbytes = 2; + $lencode = 'N'; + } else { + ## what the hell do we do here? + $lenbytes = 3; + $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 this 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', 1). # RSA + mpi_pack($n). + mpi_pack($e); + +} + +# 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: make tables of relevant identifiers: digest algorithms, +# ciphers, asymmetric crypto, packet types, subpacket types, signature +# types. As these are created, replace the opaque numbers below with +# semantically-meaningful code. + +# 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 + }; + + +# we're just not dealing with newline business right now. slurp in +# the whole file. +undef $/; +my $buf = <STDIN>; + + +my $rsa = Crypt::OpenSSL::RSA->new_private_key($buf); + +$rsa->use_sha1_hash(); +$rsa->use_no_padding(); + +if (! $rsa->check_key()) { + die "key does not check"; +} + +my $version = pack('C', 4); +# strong assertion of identity: +my $sigtype = pack('C', 0x13); +# RSA +my $pubkey_algo = pack('C', 1); +# SHA1 +my $hash_algo = pack('C', 2); + +# 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. How can we prevent +# this? + +# could an environment variable (if set) override the current time? +my $timestamp = time(); + +my $creation_time_packet = pack('CCN', 5, 2, $timestamp); + + +# FIXME: HARDCODED: what if someone wants to select a different set of +# usage flags? For now, we do only authentication. +my $flags = $usage_flags->{authenticate}; +my $usage_packet = pack('CCC', 2, 27, $flags); + + +# FIXME: HARDCODED: how should we determine how far off to set the +# expiration date? default is to expire in 2 days, which is insanely +# short (but good for testing). +my $expires_in = 86400*2; +my $expiration_packet = pack('CCN', 5, 9, $expires_in); + + +# prefer AES-256, AES-192, AES-128, CAST5, 3DES: +my $pref_sym_algos = pack('CCCCCCC', 6, 11, 9, 8, 7, 3, 2); + +# prefer SHA-1, SHA-256, RIPE-MD/160 +my $pref_hash_algos = pack('CCCCC', 4, 21, 2, 8, 3); + +# prefer ZLIB, BZip2, ZIP +my $pref_zip_algos = pack('CCCCC', 4, 22, 2, 3, 1); + +# we support the MDC feature: +my $features = pack('CCC', 2, 30, 1); + +# keyserver preference: only owner modify (???): +my $keyserver_pref = pack('CCC', 2, 23, 0x80); + +my $subpackets_to_be_hashed = + $creation_time_packet. + $usage_packet. + $expiration_packet. + $pref_sym_algos. + $pref_hash_algos. + $pref_zip_algos. + $features. + $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); + +#open(KEYFILE, "</home/wt215/gpg-test/key-data"); +my $key_data = make_packet(6, $pubkey); + +# 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, 16, $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(6, $pubkey). + make_packet(13, $uid). + make_packet(2, $sig_body); + + diff --git a/src/monkeysphere b/src/monkeysphere index 523ddfe..5444cb0 100755 --- a/src/monkeysphere +++ b/src/monkeysphere @@ -45,6 +45,7 @@ subcommands: --length (-l) BITS key length in bits (2048) --expire (-e) EXPIRE date to expire subkey-to-ssh-agent (s) store authentication subkey in ssh-agent + version (v) show version number help (h,?) this help EOF @@ -63,15 +64,6 @@ gen_subkey(){ keyExpire= # get options - TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o l:e: -l length:,expire: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?" - - if [ $? != 0 ] ; then - exit 1 - fi - - # Note the quotes around `$TEMP': they are essential! - eval set -- "$TEMP" - while true ; do case "$1" in -l|--length) @@ -82,51 +74,69 @@ gen_subkey(){ keyExpire="$2" shift 2 ;; - --) - shift - ;; - *) + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi break ;; esac done - if [ -z "$1" ] ; then - # find all secret keys - keyID=$(gpg --with-colons --list-secret-keys | grep ^sec | cut -f5 -d: | sort -u) - # if multiple sec keys exist, fail - if (( $(echo "$keyID" | wc -l) > 1 )) ; then - echo "Multiple secret keys found:" - echo "$keyID" + case "$#" in + 0) + gpgSecOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons 2>/dev/null | egrep '^sec:') + ;; + 1) + gpgSecOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons "$1" | egrep '^sec:') || failure + ;; + *) + failure "You must specify only a single primary key ID." + ;; + esac + + # check that only a single secret key was found + case $(echo "$gpgSecOut" | grep -c '^sec:') in + 0) + failure "No secret keys found. Create an OpenPGP key with the following command: + gpg --gen-key" + ;; + 1) + keyID=$(echo "$gpgSecOut" | cut -d: -f5) + ;; + *) + echo "Multiple primary secret keys found:" + echo "$gpgSecOut" | cut -d: -f5 failure "Please specify which primary key to use." + ;; + esac + + # check that a valid authentication key does not already exist + IFS=$'\n' + for line in $(gpg --quiet --fixed-list-mode --list-keys --with-colons "$keyID") ; do + type=$(echo "$line" | cut -d: -f1) + validity=$(echo "$line" | cut -d: -f2) + usage=$(echo "$line" | cut -d: -f12) + + # look at keys only + if [ "$type" != 'pub' -a "$type" != 'sub' ] ; then + continue fi - else - keyID="$1" - fi - if [ -z "$keyID" ] ; then - failure "You have no secret key available. You should create an OpenPGP -key before joining the monkeysphere. You can do this with: - gpg --gen-key" - fi - - # get key output, and fail if not found - gpgOut=$(gpg --quiet --fixed-list-mode --list-secret-keys --with-colons \ - "$keyID") || failure - - # fail if multiple sec lines are returned, which means the id - # given is not unique - if [ $(echo "$gpgOut" | grep -c '^sec:') -gt '1' ] ; then - failure "Key ID '$keyID' is not unique." - fi - - # prompt if an authentication subkey already exists - if echo "$gpgOut" | egrep "^(sec|ssb):" | cut -d: -f 12 | grep -q a ; then - echo "An authentication subkey already exists for key '$keyID'." - read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N} - if [ "${OK/y/Y}" != 'Y' ] ; then - failure "aborting." + # check for authentication capability + if ! check_capability "$usage" 'a' ; then + continue fi - fi + # if authentication key is valid, prompt to continue + if [ "$validity" = 'u' ] ; then + echo "A valid authentication key already exists for primary key '$keyID'." + read -p "Are you sure you would like to generate another one? (y/N) " OK; OK=${OK:N} + if [ "${OK/y/Y}" != 'Y' ] ; then + failure "aborting." + fi + break + fi + done # set subkey defaults # prompt about key expiration if not specified @@ -151,6 +161,7 @@ EOF (umask 077 && mkfifo "$fifoDir/pass") echo "$editCommands" | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --edit-key "$keyID" & + # FIXME: this needs to fail more gracefully if the passphrase is incorrect passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass" rm -rf "$fifoDir" @@ -373,6 +384,10 @@ case $COMMAND in subkey_to_ssh_agent "$@" ;; + 'version'|'v') + echo "$VERSION" + ;; + '--help'|'help'|'-h'|'h'|'?') usage ;; diff --git a/src/monkeysphere-server b/src/monkeysphere-server index c4f6985..ba3fa8d 100755 --- a/src/monkeysphere-server +++ b/src/monkeysphere-server @@ -66,6 +66,7 @@ subcommands: gpg-authentication-cmd CMD gnupg-authentication command + version (v) show version number help (h,?) this help EOF @@ -117,40 +118,59 @@ gpg_authentication() { su_monkeysphere_user "gpg $@" } -# function to check for host secret keys -# fails if host sec key exists, exits true otherwise -check_host_keyring() { - if ! gpg_host --list-secret-keys --fingerprint \ - --with-colons --fixed-list-mode 2>/dev/null | grep -q '^sec:' ; then +# check if user is root +is_root() { + [ $(id -u 2>/dev/null) = '0' ] +} - failure "You don't appear to have a Monkeysphere host key on this server. Please run 'monkeysphere-server gen-key' first." - fi +# check that user is root, for functions that require root access +check_user() { + is_root || failure "You must be root to run this command." } # output just key fingerprint fingerprint_server_key() { + # set the pipefail option so functions fails if can't read sec key + set -o pipefail + gpg_host --list-secret-keys --fingerprint \ --with-colons --fixed-list-mode 2> /dev/null | \ - grep '^fpr:' | head -1 | cut -d: -f10 + grep '^fpr:' | head -1 | cut -d: -f10 2>/dev/null +} + +# function to check for host secret key +check_host_keyring() { + fingerprint_server_key >/dev/null \ + || failure "You don't appear to have a Monkeysphere host key on this server. Please run 'monkeysphere-server gen-key' first." } # output key information show_server_key() { - local fingerprint - local tmpkey + local fingerprintPGP + local fingerprintSSH + local ret=0 - fingerprint=$(fingerprint_server_key) - gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprint" - - # do some crazy "Here Strings" redirection to get the key to - # ssh-keygen, since it doesn't read from stdin cleanly - echo -n "ssh fingerprint: " - ssh-keygen -l -f /dev/stdin \ - <<<$(gpg_authentication "--export $fingerprint" | \ - openpgp2ssh "$fingerprint" 2>/dev/null) | \ - awk '{ print $1, $2, $4 }' - echo -n "OpenPGP fingerprint: " - echo "$fingerprint" + # FIXME: you shouldn't have to be root to see the host key fingerprint + if is_root ; then + check_host_keyring + fingerprintPGP=$(fingerprint_server_key) + gpg_authentication "--fingerprint --list-key --list-options show-unusable-uids $fingerprintPGP" 2>/dev/null + echo "OpenPGP fingerprint: $fingerprintPGP" + else + log info "You must be root to see host OpenPGP fingerprint." + ret='1' + fi + + if [ -f "${SYSDATADIR}/ssh_host_rsa_key.pub" ] ; then + fingerprintSSH=$(ssh-keygen -l -f "${SYSDATADIR}/ssh_host_rsa_key.pub" | \ + awk '{ print $1, $2, $4 }') + echo "ssh fingerprint: $fingerprintSSH" + else + log info "SSH host key not found." + ret='1' + fi + + return $ret } # update authorized_keys for users @@ -311,15 +331,6 @@ gen_key() { revoker= # get options - TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o e:l:r -l expire:,length:,revoker: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?" - - if [ $? != 0 ] ; then - exit 1 - fi - - # Note the quotes around `$TEMP': they are essential! - eval set -- "$TEMP" - while true ; do case "$1" in -l|--length) @@ -334,10 +345,11 @@ gen_key() { revoker="$2" shift 2 ;; - --) - shift - ;; - *) + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi break ;; esac @@ -346,33 +358,29 @@ gen_key() { hostName=${1:-$(hostname -f)} userID="ssh://${hostName}" - # check for presense of key with user ID + # check for presense of secret key # FIXME: is this the proper test to be doing here? - if gpg_host --list-key ="$userID" > /dev/null 2>&1 ; then - failure "Key for '$userID' already exists" - fi + fingerprint_server_key >/dev/null \ + && failure "A key for this host already exists." # prompt about key expiration if not specified keyExpire=$(get_gpg_expiration "$keyExpire") # set key parameters - keyParameters=$(cat <<EOF -Key-Type: $keyType + keyParameters=\ +"Key-Type: $keyType Key-Length: $keyLength Key-Usage: $keyUsage Name-Real: $userID -Expire-Date: $keyExpire -EOF -) +Expire-Date: $keyExpire" # add the revoker field if specified # FIXME: the "1:" below assumes that $REVOKER's key is an RSA key. # FIXME: key is marked "sensitive"? is this appropriate? if [ "$revoker" ] ; then - keyParameters="${keyParameters}"$(cat <<EOF -Revoker: 1:$revoker sensitive -EOF -) + keyParameters=\ +"${keyParameters} +Revoker: 1:${revoker} sensitive" fi echo "The following key parameters will be used for the host private key:" @@ -384,19 +392,16 @@ EOF fi # add commit command - keyParameters="${keyParameters}"$(cat <<EOF + # must include blank line! + keyParameters=\ +"${keyParameters} %commit -%echo done -EOF -) +%echo done" log verbose "generating server key..." echo "$keyParameters" | gpg_host --batch --gen-key - # output the server fingerprint - fingerprint_server_key "=${userID}" - # find the key fingerprint of the newly generated key fingerprint=$(fingerprint_server_key) @@ -415,6 +420,9 @@ EOF log info "SSH host public key output to file: ${SYSDATADIR}/ssh_host_rsa_key.pub" gpg_authentication "--export-options export-minimal --armor --export 0x${fingerprint}\!" > "${SYSDATADIR}/ssh_host_rsa_key.pub.gpg" log info "SSH host public key in OpenPGP form: ${SYSDATADIR}/ssh_host_rsa_key.pub.gpg" + + # show info about new key + show_server_key } # extend the lifetime of a host key: @@ -478,7 +486,7 @@ $userID save EOF - ) +) # execute edit-key script if echo "$adduidCommand" | \ @@ -778,15 +786,6 @@ add_certifier() { depth=1 # get options - TEMP=$(PATH="/usr/local/bin:$PATH" getopt -o n:t:d: -l domain:,trust:,depth: -n "$PGRM" -- "$@") || failure "getopt failed! Does your getopt support GNU-style long options?" - - if [ $? != 0 ] ; then - exit 1 - fi - - # Note the quotes around `$TEMP': they are essential! - eval set -- "$TEMP" - while true ; do case "$1" in -n|--domain) @@ -801,10 +800,11 @@ add_certifier() { depth="$2" shift 2 ;; - --) - shift - ;; - *) + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi break ;; esac @@ -1001,62 +1001,76 @@ shift case $COMMAND in 'update-users'|'update-user'|'u') + check_user check_host_keyring update_users "$@" ;; 'gen-key'|'g') + check_user gen_key "$@" ;; 'extend-key'|'e') + check_user check_host_keyring extend_key "$@" ;; 'add-hostname'|'add-name'|'n+') + check_user check_host_keyring add_hostname "$@" ;; 'revoke-hostname'|'revoke-name'|'n-') + check_user check_host_keyring revoke_hostname "$@" ;; 'show-key'|'show'|'s') - check_host_keyring show_server_key ;; 'publish-key'|'publish'|'p') + check_user check_host_keyring publish_server_key ;; 'diagnostics'|'d') + check_user diagnostics ;; 'add-identity-certifier'|'add-id-certifier'|'add-certifier'|'c+') + check_user check_host_keyring add_certifier "$@" ;; 'remove-identity-certifier'|'remove-id-certifier'|'remove-certifier'|'c-') + check_user check_host_keyring remove_certifier "$@" ;; 'list-identity-certifiers'|'list-id-certifiers'|'list-certifiers'|'list-certifier'|'c') + check_user check_host_keyring list_certifiers "$@" ;; 'gpg-authentication-cmd') + check_user gpg_authentication_cmd "$@" ;; + 'version'|'v') + echo "$VERSION" + ;; + '--help'|'help'|'-h'|'h'|'?') usage ;; |