diff options
author | Micah Anderson <micah@riseup.net> | 2009-01-31 16:39:40 -0500 |
---|---|---|
committer | Micah Anderson <micah@riseup.net> | 2009-01-31 16:39:40 -0500 |
commit | a693e9be61d02196e2583e9096cb049982a26668 (patch) | |
tree | 48a558a3d112106005f88314a184c57336ef4bb7 | |
parent | b024a2d3cacfe2fdb410daa830e04b2d9dfbcf4e (diff) | |
parent | 3ebaf05d01b7d4639980608feefeef7287000634 (diff) |
Merge commit 'jrollins/master'
Conflicts:
website/sidebar.mdwn
-rwxr-xr-x | Makefile | 3 | ||||
-rw-r--r-- | doc/george/changelog | 22 | ||||
-rw-r--r-- | man/man1/openpgp2ssh.1 | 1 | ||||
-rw-r--r-- | packaging/debian/changelog | 7 | ||||
-rw-r--r-- | packaging/freebsd/security/monkeysphere/Makefile | 2 | ||||
-rw-r--r-- | packaging/freebsd/security/monkeysphere/distinfo | 6 | ||||
-rw-r--r-- | packaging/freebsd/security/monkeysphere/files/patch-sharelocation | 11 | ||||
-rw-r--r-- | src/common | 7 | ||||
-rwxr-xr-x | src/keytrans/pem2openpgp | 469 | ||||
-rwxr-xr-x | src/monkeysphere | 159 | ||||
-rwxr-xr-x | src/monkeysphere-server | 302 | ||||
-rw-r--r-- | website/bugs/use_getopts_instead_of_getopt.mdwn | 9 | ||||
-rw-r--r-- | website/local.css | 20 | ||||
-rw-r--r-- | website/logo.simple.png | bin | 0 -> 5536 bytes | |||
-rw-r--r-- | website/logo.title.png | bin | 0 -> 4123 bytes | |||
-rw-r--r-- | website/news/Monkeysphere-in-Debian.mdwn | 6 | ||||
-rw-r--r-- | website/sidebar.mdwn | 19 | ||||
-rw-r--r-- | website/vision.mdwn | 31 |
18 files changed, 908 insertions, 166 deletions
@@ -29,6 +29,7 @@ tarball: clean debian-package: tarball tar xzf monkeysphere_$(MONKEYSPHERE_VERSION).orig.tar.gz + sed -i "s|__VERSION__|$(MONKEYSPHERE_VERSION)|g" monkeysphere-$(MONKEYSPHERE_VERSION)/src/common cp -a packaging/debian monkeysphere-$(MONKEYSPHERE_VERSION) (cd monkeysphere-$(MONKEYSPHERE_VERSION) && debuild -uc -us) rm -rf monkeysphere-$(MONKEYSPHERE_VERSION) @@ -49,7 +50,7 @@ install: all installman mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)$(PREFIX)/sbin $(DESTDIR)$(PREFIX)/share/monkeysphere mkdir -p $(DESTDIR)$(PREFIX)/share/doc/monkeysphere mkdir -p $(DESTDIR)$(ETCPREFIX)/etc/monkeysphere - install src/monkeysphere src/monkeysphere-ssh-proxycommand src/keytrans/openpgp2ssh $(DESTDIR)$(PREFIX)/bin + install src/monkeysphere src/monkeysphere-ssh-proxycommand src/keytrans/openpgp2ssh src/keytrans/pem2openpgp $(DESTDIR)$(PREFIX)/bin install src/monkeysphere-server $(DESTDIR)$(PREFIX)/sbin install -m 0644 src/common $(DESTDIR)$(PREFIX)/share/monkeysphere install doc/* $(DESTDIR)$(PREFIX)/share/doc/monkeysphere diff --git a/doc/george/changelog b/doc/george/changelog index 83d6843..7cd700c 100644 --- a/doc/george/changelog +++ b/doc/george/changelog @@ -7,6 +7,28 @@ * changes to this system (first command at top, last at bottom) * ****************************************************************************** +2009-01-31 - jrollins + * applied diff represented in commit + f75a5747a8b99e04c02c475791c476f1fbd2b674 to change log level for + unacceptable untranslatable keys. + +2009-01-30 - micah + * Replaced nullmailer with postfix, nullmailer doesn't handle aliases + and insisted either on constantly respooling mail when there was no + where to go. + +2009-01-24 - micah + * Configured /etc/aliases to have root go to mjgoins, micah, dkg, jrollins + * Configured /etc/nullmailer/remotes to have mail.riseup.net so remote delivery will work + * Removed the hundreds of queued cron emails that had resulted in 30gig of mail.err logs + * Rotated the giant logs out + +2009-01-11 - dkg + * extended the expiration date for george's key three months into + the future. + * aptitude update && aptitude full-upgrade (brings monkeysphere to + 0.22-1) + 2008-10-29 - dkg * aptitude update && aptitude full-upgrade * brought monkeysphere up to 0.19-1 diff --git a/man/man1/openpgp2ssh.1 b/man/man1/openpgp2ssh.1 index 89df047..8374a9f 100644 --- a/man/man1/openpgp2ssh.1 +++ b/man/man1/openpgp2ssh.1 @@ -90,6 +90,7 @@ only acts on keys associated with the first primary key passed in. If you send it more than one primary key, it will silently ignore later ones. .Sh SEE ALSO +.Xr pem2openpgp 1 , .Xr monkeysphere 1 , .Xr monkeysphere 7 , .Xr ssh 1 , diff --git a/packaging/debian/changelog b/packaging/debian/changelog index 2aaa9ca..a282c58 100644 --- a/packaging/debian/changelog +++ b/packaging/debian/changelog @@ -3,8 +3,13 @@ monkeysphere (0.23~pre-1) UNRELEASED; urgency=low * New upstream release: - added better checks for the existence of a host private key for functions that require it to be there. + - add checks for root users, for functions where it is required. + - get rid of getopts. + - added version output option + - check that existing authentication keys are valid in gen_key + function. - -- Jameson Graef Rollins <jrollins@finestructure.net> Sun, 30 Nov 2008 17:14:50 -0500 + -- Jameson Graef Rollins <jrollins@finestructure.net> Tue, 30 Dec 2008 20:21:16 -0500 monkeysphere (0.22-1) unstable; urgency=low diff --git a/packaging/freebsd/security/monkeysphere/Makefile b/packaging/freebsd/security/monkeysphere/Makefile index 976f543..24f9b2b 100644 --- a/packaging/freebsd/security/monkeysphere/Makefile +++ b/packaging/freebsd/security/monkeysphere/Makefile @@ -6,7 +6,7 @@ # PORTNAME= monkeysphere -PORTVERSION= 0.22~pre +PORTVERSION= 0.22 CATEGORIES= security MASTER_SITES= http://archive.monkeysphere.info/debian/pool/monkeysphere/m/monkeysphere/ # hack for debian orig tarballs diff --git a/packaging/freebsd/security/monkeysphere/distinfo b/packaging/freebsd/security/monkeysphere/distinfo index 51edfbe..d6c6e5e 100644 --- a/packaging/freebsd/security/monkeysphere/distinfo +++ b/packaging/freebsd/security/monkeysphere/distinfo @@ -1,3 +1,3 @@ -MD5 (monkeysphere_0.22~pre.orig.tar.gz) = fd19f09ed9a720f673d74c9cb58e9d6d -SHA256 (monkeysphere_0.22~pre.orig.tar.gz) = 337c7fdb93b697fba5a9e35cdff2b5faf0e4914fd8beab7994b456d58d19abb6 -SIZE (monkeysphere_0.22~pre.orig.tar.gz) = 69345 +MD5 (monkeysphere_0.22.orig.tar.gz) = 2bb00c86323409b98aff53f94d9ce0a6 +SHA256 (monkeysphere_0.22.orig.tar.gz) = 2566facda807a67a4d2d6de3833cccfa0b78b454909e8d25f47a235a9e621b24 +SIZE (monkeysphere_0.22.orig.tar.gz) = 70245 diff --git a/packaging/freebsd/security/monkeysphere/files/patch-sharelocation b/packaging/freebsd/security/monkeysphere/files/patch-sharelocation index 99c9604..e41c479 100644 --- a/packaging/freebsd/security/monkeysphere/files/patch-sharelocation +++ b/packaging/freebsd/security/monkeysphere/files/patch-sharelocation @@ -20,3 +20,14 @@ export SYSSHAREDIR . "${SYSSHAREDIR}/common" || exit 1 +--- src/monkeysphere-ssh-proxycommand.orig ++++ src/monkeysphere-ssh-proxycommand +@@ -16,7 +16,7 @@ + ######################################################################## + PGRM=$(basename $0) + +-SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/share/monkeysphere"} ++SYSSHAREDIR=${MONKEYSPHERE_SYSSHAREDIR:-"/usr/local/share/monkeysphere"} + export SYSSHAREDIR + . "${SYSSHAREDIR}/common" || exit 1 + @@ -19,6 +19,9 @@ SYSCONFIGDIR=${MONKEYSPHERE_SYSCONFIGDIR:-"/etc/monkeysphere"} export SYSCONFIGDIR +# monkeysphere version +VERSION=__VERSION__ + ######################################################################## ### UTILITY FUNCTIONS @@ -671,7 +674,7 @@ process_user_id() { else log debug " - unacceptable primary key." if [ -z "$sshKey" ] ; then - log error " ! primary key could not be translated (not RSA or DSA?)." + log debug " ! primary key could not be translated (not RSA or DSA?)." else echo "1:${sshKey}" fi @@ -729,7 +732,7 @@ process_user_id() { else log debug " - unacceptable sub key." if [ -z "$sshKey" ] ; then - log error " ! sub key could not be translated (not RSA or DSA?)." + log debug " ! sub key could not be translated (not RSA or DSA?)." else echo "1:${sshKey}" fi diff --git a/src/keytrans/pem2openpgp b/src/keytrans/pem2openpgp new file mode 100755 index 0000000..3d9f6f8 --- /dev/null +++ b/src/keytrans/pem2openpgp @@ -0,0 +1,469 @@ +#!/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 ? + + + +# 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 $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(); + + 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"; + } + + return $lastx; +} + + +############ 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 $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 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 $secret_material = mpi_pack($d). + mpi_pack($p). + mpi_pack($q). + mpi_pack(modular_multi_inverse($p, $q)); + + # 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); +} + +# 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(); + +# 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? + +# could an environment variable (if set) override 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 = time(); + +my $creation_time_packet = pack('CCN', 5, $subpacket_types->{sig_creation_time}, $timestamp); + + +# FIXME: HARDCODED: what if someone wants to select a different set of +# usage flags? For now, we do only authentication because that's what +# monkeysphere needs. +my $usage_packet = pack('CCC', 2, $subpacket_types->{usage_flags}, $usage_flags->{authenticate}); + + +# 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). The user ought to be able to decide +# this directly, rather than having to do "monkeysphere-server +# extend-key". +my $expires_in = 86400*2; +my $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); + +my $key_data = make_packet($packet_types->{pubkey}, $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, $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 523ddfe..463a1b1 100755 --- a/src/monkeysphere +++ b/src/monkeysphere @@ -41,37 +41,69 @@ Monkeysphere client tool. subcommands: update-known_hosts (k) [HOST]... update known_hosts file update-authorized_keys (a) update authorized_keys file + import-subkey (i) import existing ssh key as gpg subkey + --keyfile (-f) FILE key file to import + --expire (-e) EXPIRE date to expire gen-subkey (g) [KEYID] generate an authentication subkey --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 } -# generate a subkey with the 'a' usage flags set -gen_subkey(){ - local keyLength +# import an existing ssh key as a gpg subkey +import_subkey() { + local keyFile="~/.ssh/id_rsa" local keyExpire local keyID local gpgOut local userID - # set default key parameter values - keyLength= - 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?" + while true ; do + case "$1" in + -f|--keyfile) + keyFile="$2" + shift 2 + ;; + -e|--expire) + keyExpire="$2" + shift 2 + ;; + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi + break + ;; + esac + done - if [ $? != 0 ] ; then - exit 1 - fi + log verbose "importing ssh key..." + fifoDir=$(mktemp -d ${TMPDIR:-/tmp}/tmp.XXXXXXXXXX) + (umask 077 && mkfifo "$fifoDir/pass") + ssh2openpgp | gpg --passphrase-fd 3 3< "$fifoDir/pass" --expert --command-fd 0 --import & + + passphrase_prompt "Please enter your passphrase for $keyID: " "$fifoDir/pass" + + rm -rf "$fifoDir" + wait + log verbose "done." +} - # Note the quotes around `$TEMP': they are essential! - eval set -- "$TEMP" +# generate a subkey with the 'a' usage flags set +gen_subkey(){ + local keyLength + local keyExpire + local keyID + local gpgOut + local userID + # get options while true ; do case "$1" in -l|--length) @@ -82,51 +114,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 +201,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" @@ -365,6 +416,10 @@ case $COMMAND in RETURN="$?" ;; + 'import-subkey'|'i') + import_key "$@" + ;; + 'gen-subkey'|'g') gen_subkey "$@" ;; @@ -373,6 +428,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..96f5b56 100755 --- a/src/monkeysphere-server +++ b/src/monkeysphere-server @@ -46,13 +46,20 @@ Monkeysphere server admin tool. subcommands: update-users (u) [USER]... update user authorized_keys files - gen-key (g) [NAME[:PORT]] generate gpg key for the server + import-key (i) import existing ssh key to gpg + --hostname (-h) NAME[:PORT] hostname for key user ID + --keyfile (-f) FILE key file to import + --expire (-e) EXPIRE date to expire + gen-key (g) generate gpg key for the host + --hostname (-h) NAME[:PORT] hostname for key user ID --length (-l) BITS key length in bits (2048) --expire (-e) EXPIRE date to expire --revoker (-r) FINGERPRINT add a revoker - extend-key (e) EXPIRE extend expiration to EXPIRE - add-hostname (n+) NAME[:PORT] add hostname user ID to server key + extend-key (e) EXPIRE extend 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 (o) FINGERPRINT add a revoker to the host key + revoke-key (r) revoke host key show-key (s) output all server host key information publish-key (p) publish server host key to keyserver diagnostics (d) report on server monkeysphere status @@ -64,8 +71,10 @@ subcommands: remove-id-certifier (c-) KEYID remove a certification key list-id-certifiers (c) list certification keys - gpg-authentication-cmd CMD gnupg-authentication command + gpg-authentication-cmd CMD give a gpg command to the + authentication keyring + version (v) show version number help (h,?) this help EOF @@ -117,40 +126,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 @@ -291,37 +319,107 @@ update_users() { done } +# import an existing ssh key to a gpg key +import_key() { + local hostName=$(hostname -f) + local keyFile="/etc/ssh/ssh_host_rsa_key" + local keyExpire + local userID + + # check for presense of secret key + # FIXME: is this the proper test to be doing here? + fingerprint_server_key >/dev/null \ + && failure "An OpenPGP host key already exists." + + # get options + while true ; do + case "$1" in + -h|--hostname) + hostName="$2" + shift 2 + ;; + -f|--keyfile) + keyFile="$2" + shift 2 + ;; + -e|--expire) + keyExpire="$2" + shift 2 + ;; + *) + if [ "$(echo "$1" | cut -c 1)" = '-' ] ; then + failure "Unknown option '$1'. +Type '$PGRM help' for usage." + fi + break + ;; + esac + done + + if [ ! -f "$keyFile" ] ; then + failure "SSH secret key file '$keyFile' not found." + fi + + userID="ssh://${hostName}" + + # prompt about key expiration if not specified + keyExpire=$(get_gpg_expiration "$keyExpire") + + echo "The following key parameters will be used for the host private key:" + echo "Import: $keyFile" + echo "Name-Real: $userID" + echo "Expire-Date: $keyExpire" + + read -p "Import key? (Y/n) " OK; OK=${OK:=Y} + if [ ${OK/y/Y} != 'Y' ] ; then + failure "aborting." + fi + + log verbose "importing ssh key..." + # translate ssh key to a private key + (umask 077 && \ + pem2openpgp "$userID" "$keyExpire" < "$sshKey" | gpg_host --import) + + # find the key fingerprint of the newly converted key + fingerprint=$(fingerprint_server_key) + + # export host ownertrust to authentication keyring + log verbose "setting ultimate owner trust for host key..." + echo "${fingerprint}:6:" | gpg_host "--import-ownertrust" + echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust" + + # export public key to file + 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 +} + # generate server gpg key gen_key() { - local keyType - local keyLength - local keyUsage + local keyType="RSA" + local keyLength="2048" + local keyUsage="auth" local keyExpire local revoker - local hostName + local hostName=$(hostname -f) local userID local keyParameters local fingerprint - # set default key parameter values - keyType="RSA" - keyLength="2048" - keyUsage="auth" - keyExpire= - revoker= + # check for presense of secret key + # FIXME: is this the proper test to be doing here? + fingerprint_server_key >/dev/null \ + && failure "An OpenPGP host key already exists." # 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 + -h|--hostname) + hostName="$2" + shift 2 + ;; -l|--length) keyLength="$2" shift 2 @@ -334,45 +432,36 @@ 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 done - hostName=${1:-$(hostname -f)} userID="ssh://${hostName}" - # check for presense of key with user ID - # 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 - # 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,24 +473,21 @@ 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..." + log verbose "generating host 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) # export host ownertrust to authentication keyring - log verbose "setting ultimate owner trust for server key..." + log verbose "setting ultimate owner trust for host key..." echo "${fingerprint}:6:" | gpg_authentication "--import-ownertrust" # translate the private key to ssh format, and export to a file @@ -415,6 +501,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 +567,7 @@ $userID save EOF - ) +) # execute edit-key script if echo "$adduidCommand" | \ @@ -576,6 +665,18 @@ EOF fi } +# add a revoker to the host key +add_revoker() { + # FIXME: implement! + failure "not implemented yet!" +} + +# revoke the host key +revoke_key() { + # FIXME: implement! + failure "not implemented yet!" +} + # publish server key to keyserver publish_server_key() { read -p "Really publish host key to $KEYSERVER? (y/N) " OK; OK=${OK:=N} @@ -778,15 +879,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 +893,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 +1094,93 @@ shift case $COMMAND in 'update-users'|'update-user'|'u') + check_user check_host_keyring update_users "$@" ;; + 'import-key'|'i') + check_user + import_key "$@" + ;; + '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') + 'add-revoker'|'o') + check_user check_host_keyring + add_revoker "$@" + ;; + + 'revoke-key'|'r') + check_user + check_host_keyring + revoke_key "$@" + ;; + + 'show-key'|'show'|'s') 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 ;; diff --git a/website/bugs/use_getopts_instead_of_getopt.mdwn b/website/bugs/use_getopts_instead_of_getopt.mdwn index af4c17a..2ec68d6 100644 --- a/website/bugs/use_getopts_instead_of_getopt.mdwn +++ b/website/bugs/use_getopts_instead_of_getopt.mdwn @@ -8,3 +8,12 @@ better for portability. So it looks like the sh built-in getopts does not include long options (eg. "--expire"). Is it worth getting rid of the long options for this? + +--- + +Why not just get rid of getopts altogether and perform a simple +argument-processing loop with bash string tests? We're only invoking +getopt in three places, and each invocation is no more complex than +three arguments -- and most arguments take a separate parameter, which +means that handling tricky arg blobs like -aCxr are not gonna be +supported anyway. diff --git a/website/local.css b/website/local.css index c4b59e9..de0f196 100644 --- a/website/local.css +++ b/website/local.css @@ -58,31 +58,31 @@ pre { overflow: auto; } -table.sitenav { +table.sitenav { border-bottom: 2px solid black; padding: 0px; width: 100%; font-size: larger; } -table.sitenav img.logo { - margin: 0px; - padding: 0px; +table.sitenav img.logo { + margin: 0em; + padding: 0px; vertical-align: bottom; } +table.sitenav img.title { + margin: 0px; + padding: 0px; + vertical-align: top; +} + table.sitenav a { font-weight: bold; margin-right: 1em; font-size: smaller; } -/* trying to align the sitenav links roughly with the text in the monkeysphere logo */ -td#sitenav { - vertical-align: bottom; - padding-bottom: 30px; -} - table.sitenav span.selflink { font-weight: bold; text-decoration: underline; diff --git a/website/logo.simple.png b/website/logo.simple.png Binary files differnew file mode 100644 index 0000000..5cc69eb --- /dev/null +++ b/website/logo.simple.png diff --git a/website/logo.title.png b/website/logo.title.png Binary files differnew file mode 100644 index 0000000..a203f8b --- /dev/null +++ b/website/logo.title.png diff --git a/website/news/Monkeysphere-in-Debian.mdwn b/website/news/Monkeysphere-in-Debian.mdwn index acba904..edad432 100644 --- a/website/news/Monkeysphere-in-Debian.mdwn +++ b/website/news/Monkeysphere-in-Debian.mdwn @@ -3,9 +3,9 @@ [The Monkeysphere has made it into Debian!](http://packages.debian.org/sid/monkeysphere) -It is in Debian unstable (sid) now, which means it won't make it into -the next unstable release (lenny), but hopefully will be in the stable -release after that (squeeze). +It is in Debian unstable ("sid") now, which means it won't make it +into the next stable release ("lenny"), but hopefully will make it +into the stable release after that ("squeeze"). Congratulations to all the work by all the [monkeysphere developers](/community), and to Micah Anderson for being our Debian diff --git a/website/sidebar.mdwn b/website/sidebar.mdwn index 381dff7..4783d2a 100644 --- a/website/sidebar.mdwn +++ b/website/sidebar.mdwn @@ -1,12 +1,19 @@ <table class="sitenav" cellpadding="0" cellspacing="0"> -<tbody><tr><td> -<a class="logo" href="/"><img class="logo" src="/logo.png" alt="monkeysphere" width="343" height="85" /></a> -</td><td id="sitenav"> - +<colgroup span="1" width="120" /> +<tr> +<td rowspan="2"><a href="/"><img class="logo" src="/logo.simple.png" alt="monkeysphere" /></a></td> +<td><a href="/"><img class="title" src="/logo.title.png" alt="monkeysphere" /></a></td> +</tr><tr> +<td> [[WHY?|why]] [[DOWNLOAD|download]] [[DOCUMENTATION|doc]] [[NEWS|news]] [[COMMUNITY|community]] -[[BUGS|bugs]] -</td></tr></tbody></table> +<a href="https://labs.riseup.net/code/wiki/monkeysphere">WIKI</a> +<a href="https://labs.riseup.net/code/projects/monkeysphere/issues">BUGS</a> +[[VISION|vision]] +</td> +</tr> +</table> + diff --git a/website/vision.mdwn b/website/vision.mdwn new file mode 100644 index 0000000..281bc72 --- /dev/null +++ b/website/vision.mdwn @@ -0,0 +1,31 @@ +[[meta title="Our vision for the future of the monkeysphere"]] + +## External Validation Agent ## + +This is probably at the crux of the Monkeysphere vision for the future: + +* [Simon Josefsson proposed out-of-process certificate verification model in gnutls-devel](http://news.gmane.org/find-root.php?group=gmane.comp.encryption.gpg.gnutls.devel&article=3231) +* [Werner Koch's dirmngr](http://www.gnupg.org/documentation/manuals/dirmngr/) +* [GnuTLS wiki external validation](http://redmine.josefsson.org/wiki/gnutls/GnuTLSExternalValidation) +* [Pathfinder PKI validation](http://code.google.com/p/pathfinder-pki/) (includes validation plugins for OpenSSL and LibNSS). + +## TLS transition strategies ## + +While [RFC 5081](http://tools.ietf.org/html/rfc5081) is quite a while +off from widespread adoption, it would be good to have an interim +translation step. This is analogous to the SSH work we've done, where +the on-the-wire protocol remains the same, but the keys themselves are +looked up in the OpenPGP WoT. + +Firefox extensions that deal with certificate validation seem to be +the easiest path toward demonstrating this technique. We should look +at: + +* [SSL Blacklist](http://codefromthe70s.org/sslblacklist.aspx) +* [Perspectives](http://www.cs.cmu.edu/~perspectives/firefox.html) +* there is another firefox extension that basically disables all TLS certificate checking. The download page says things like "this is a bad idea" and "do not install this extension", but i'm unable to find it at the moment. + +## Related discussions ## + +* [Wandering Thoughts blog discussion about Web of Trust flaws](http://utcc.utoronto.ca/~cks/space/blog/tech/WebOfTrustFlaws?showcomments) +* [Wandering Thoughts blog discussion about certificate authorities](http://utcc.utoronto.ca/~cks/space/blog/web/SSLCANeed?showcomments) |