Package: release.debian.org Severity: normal User: release.debian.org@packages.debian.org Usertags: unblock Please unblock package msva-perl: unblock msva-perl/0.9-1 the new upstream version 0.9 fixes 3 RC bugs: 2 critical bugs: #682353 crashes my X11 session on upgrade #682518 login impossible with msva-perl installed and 1 grave bug: #677565 Insecure dependency in socket while running with -T switch I understand that this is a larger change than is usually asked for at this stage of the freeze, and apologize for the size of it. As mitigating factors, i offer the following observations: * 0.9 fails more gracefully than 0.8 did -- an agent that fails (for whatever reason) should now simply stop working instead of crashing the user's X11 session. * the wheezy versions of perl core and the modules depended on by msva-perl actively break msva-perl 0.8 in several ways. 0.9 works with these newer versions. * msva-perl has no reverse dependencies in debian, and only a single recommends, xul-ext-monkeysphere. debian/changelog: msva-perl (0.9-1) unstable; urgency=low * New Upstream version - tighter dependencies - daemon crash should no longer kill X11 session (Closes: #682353, #682518) - cleanup for newer versions of perl and modules (Closes: #677565, #642304) - binds explicitly to IPv4 loopback (Closes: #661939) - scanning for changes and prompting to reload off by default (Closes: #614313) * bumped Standards-Version to 3.9.3 (no changes needed) -- Daniel Kahn Gillmor <dkg@fifthhorseman.net> Wed, 25 Jul 2012 13:20:08 -0400 Full debdiff attached. Please let me know if you have any questions. Thanks for your work on the release-team! Regards, --dkg
diff -Nru msva-perl-0.8/Changelog msva-perl-0.9/Changelog --- msva-perl-0.8/Changelog 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Changelog 2012-07-28 16:43:34.000000000 -0400 @@ -1,3 +1,35 @@ +msva-perl (0.9) upstream; + + [ Jameson Rollins ] + * Add "e-mail" context (checks for signing capability instead of + authentication) (closes MS #2688) + * Add "openpgp4fpr" pkc type for providing OpenPGP v4 fingerprint + * Add --version option to msva-query-agent + + [ David Bremner ] + * Code refactoring: + - Crypt::Monkeysphere::MSVA::Logger into Crypt::Monkeysphere::Logger + - new Crypt::Monkeysphere::Validator + - unit tests and unit test harness + + [ Daniel Kahn Gillmor ] + * Now depending on Crypt::X509 0.50 for pubkey components directly. + * Crypt::Monkeysphere::OpenPGP for helper functions in + packet generation and parsing. + * Parse and make use of X.509 PGPExtension if present in X.509 public + key carrier. + * Fix HUP server restart when used with Net::Server >= 0.99 + * Crypt::Monkeysphere::Keytrans has the start of some key/certificate + conversion routines. + * Fix socket detection when used with Net::Server >= 2.00, which + can bind to multiple sockets + * depend on Net::Server >= 2.00 + * change launcher approach -- daemon is now child process, so that + daemon failures won't kill X11 session + * scanning and prompting for changes is now optional (defaults to off) + + -- Daniel Kahn Gillmor <dkg@fifthhorseman.net> Wed, 25 Jul 2012 13:12:55 -0400 + msva-perl (0.8) upstream; * Minor bugfix release! diff -Nru msva-perl-0.8/Crypt/Monkeysphere/Keyserver.pm msva-perl-0.9/Crypt/Monkeysphere/Keyserver.pm --- msva-perl-0.8/Crypt/Monkeysphere/Keyserver.pm 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/Keyserver.pm 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,156 @@ +package Crypt::Monkeysphere::Keyserver; +use IO::File; +use GnuPG::Handles; +use GnuPG::Interface; +use File::HomeDir; +use Config::General; +use Regexp::Common qw /net/; +use POSIX; + +use strict; +use warnings; +use parent qw(Crypt::Monkeysphere::Logger); +use Crypt::Monkeysphere::Util qw(untaint); + +our $default_keyserver='hkp://pool.sks-keyservers.net'; + +=pod + +=head2 new + +Create a new Crypt::Monkeysphere::Keyserver instance + +Arguments + Param hash, all optional. + + keyserver => URL + gnupg => GnuPG::Interface object + + (plus arguments for Crypt::Monkeysphere::Logger::new) + +=cut +sub new { + my $class=shift; + my %opts=@_; + + my $self=$class->SUPER::new($opts{loglevel} || 'info'); + + # gnupg should be initialized first, before figuring out + # what keyserver to use. + + $self->{gnupg} = $opts{gnupg} || new GnuPG::Interface(); + + $self->{keyserver} = $opts{keyserver} || $self->_get_keyserver(); + return $self; +} + +sub _get_keyserver{ + + my $self=shift; + + my $gpghome=$self->{gnupg}->options->homedir; + + if (!defined($gpghome)) { + if (exists $ENV{GNUPGHOME} and $ENV{GNUPGHOME} ne '') { + + # We might be running in taint mode, but we assume that is about + # data coming from the network, and that the local environment + # is generally trustworthy. + + $gpghome = untaint($ENV{GNUPGHOME}); + } else { + my $userhome=File::HomeDir->my_home; + if (defined($userhome)) { + $gpghome = File::Spec->catfile($userhome, '.gnupg'); + } + } + } + + if (defined $gpghome) { + return $self->_read_keyserver_from_gpg_conf($gpghome) || $default_keyserver; + } else { + return $default_keyserver; + } + +} + +sub _read_keyserver_from_gpg_conf() { + my $self=shift; + my $gpghome=shift; + + my $gpgconf = File::Spec->catfile($gpghome, 'gpg.conf'); + if (-f $gpgconf) { + if (-r $gpgconf) { + my %gpgconfig = Config::General::ParseConfig($gpgconf); + if ($gpgconfig{keyserver} =~ /^(((hkps?|hkpms|finger|ldap):\/\/)?$RE{net}{domain})$/) { + $self->log('debug', "Using keyserver %s from the GnuPG configuration file (%s)\n", $1, $gpgconf); + return $1; + } else { + $self->log('error', "Not a valid keyserver (from gpg config %s):\n %s\n", $gpgconf, $gpgconfig{keyserver}); + } + } else { + $self->log('error', "The GnuPG configuration file (%s) is not readable\n", $gpgconf); + } + } else { + $self->log('info', "Did not find GnuPG configuration file while looking for keyserver '%s'\n", $gpgconf); + } + return undef; +} + + +sub fetch_uid { + my $self= shift; + my $uid = shift || croak("uid argument mandatory"); + + my $ks=$self->{keyserver}; + my $gnupg=$self->{gnupg}; + + my $cmd = IO::Handle::->new(); + my $out = IO::Handle::->new(); + my $nul = IO::File::->new("< /dev/null"); + + $self->log('debug', "start ks query to %s for UserID: %s\n", $ks, $uid); + my $pid = $gnupg->wrap_call + ( handles => GnuPG::Handles::->new( command => $cmd, stdout => $out, stderr => $nul ), + command_args => [ '='.$uid ], + commands => [ '--keyserver', + $ks, + qw( --no-tty --with-colons --search ) ] + ); + while (my $line = $out->getline()) { + $self->log('debug', "from ks query: (%d) %s", $cmd->fileno, $line); + if ($line =~ /^info:(\d+):(\d+)/ ) { + $cmd->print(join(' ', ($1..$2))."\n"); + $self->log('debug', 'to ks query: '.join(' ', ($1..$2))."\n"); + last; + } + } + # FIXME: can we do something to avoid hanging forever? + waitpid($pid, 0); + $self->log('debug', "ks query returns %d\n", POSIX::WEXITSTATUS($?)); + } + +sub fetch_fpr { + my $self = shift; + my $fpr = shift || croak("fpr argument mandatory"); + + my $ks=$self->{keyserver}; + my $gnupg=$self->{gnupg}; + + my $cmd = IO::Handle::->new(); + my $nul = IO::File::->new("< /dev/null"); + + $self->log('debug', "start ks query to %s for fingerprint: %s\n", $ks, $fpr); + my $pid = $gnupg->wrap_call + ( handles => GnuPG::Handles::->new( command => $cmd, stdout => $nul, stderr => $nul ), + command_args => [ '0x'.$fpr ], + commands => [ '--keyserver', + $ks, + qw( --no-tty --recv-keys ) ] + ); + # FIXME: can we do something to avoid hanging forever? + waitpid($pid, 0); + $self->log('debug', "ks query returns %d\n", POSIX::WEXITSTATUS($?)); +} + +1; diff -Nru msva-perl-0.8/Crypt/Monkeysphere/Keytrans.pm msva-perl-0.9/Crypt/Monkeysphere/Keytrans.pm --- msva-perl-0.8/Crypt/Monkeysphere/Keytrans.pm 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/Keytrans.pm 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,112 @@ +package Crypt::Monkeysphere::Keytrans; + +use strict; +use warnings; +use Math::BigInt; +use Carp; +use MIME::Base64; + +use Exporter qw(import); +our @EXPORT_OK=qw(GnuPGKey_to_OpenSSH_pub GnuPGKey_to_OpenSSH_fpr); + + +# takes a Math::BigInt and returns it properly packed for openssh output. + +sub openssh_mpi_pack { + my $num = shift; + + my $val = $num->as_hex(); + $val =~ s/^0x//; + # ensure we've got an even multiple of 2 nybbles here. + $val = '0'.$val + if (length($val) % 2); + $val = pack('H*', $val); + # packed binary ones-complement representation of the value. + + 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; +} + +# this output is not base64-encoded yet. Pass it through +# encode_base64($output, '') if you want to make a file. + +sub openssh_rsa_pubkey_pack { + my ($modulus, $exponent) = @_; + + return openssh_mpi_pack(Math::BigInt->new('0x'.unpack('H*', "ssh-rsa"))). + openssh_mpi_pack($exponent). + openssh_mpi_pack($modulus); +} + +# calculate/print the fingerprint of an openssh-style keyblob: + +sub sshfpr { + my $keyblob = shift; + use Digest::MD5; + return join(':', map({unpack("H*", $_)} split(//, Digest::MD5::md5($keyblob)))); +} + +=pod + +=head2 GnuPGKey_to_OpenSSH_fpr + +Find the openssh compatible fingerprint of an (RSA) GnuPG::Key + +B<Note> you will need to add add bits and (RSA) to the string to +exactly match the output of ssh-keygen -l. + +=head3 Arguments + +key - GnuPG::Key object + +=cut + +sub GnuPGKey_to_OpenSSH_fpr { + my $key = shift; + + croak("not a GnuPG::Key!") + unless($key->isa('GnuPG::Key')); + + croak("Not an RSA key!") + unless $key->algo_num == 1; + + return sshfpr(openssh_rsa_pubkey_pack(@{$key->pubkey_data}), ''); +} + +=pod + +=head2 GnuPGKey_to_OpenSSH_pub + +Translate a GnuPG::Key to a string suitable for an OpenSSH .pub file + +B<Note> you will need to add "ssh-rsa " to the front to make OpenSSH +recognize it. + +=head3 Arguments + +key - GnuPG::Key object + +=cut + +sub GnuPGKey_to_OpenSSH_pub { + my $key = shift; + + croak("not a GnuPG::Key!") + unless($key->isa('GnuPG::Key')); + + croak("Not an RSA key!") + unless $key->algo_num == 1; + + return encode_base64(openssh_rsa_pubkey_pack(@{$key->pubkey_data}), ''); +} + +1; diff -Nru msva-perl-0.8/Crypt/Monkeysphere/Logger.pm msva-perl-0.9/Crypt/Monkeysphere/Logger.pm --- msva-perl-0.8/Crypt/Monkeysphere/Logger.pm 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/Logger.pm 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,102 @@ +#---------------------------------------------------------------------- +# Monkeysphere Validation Agent, Perl version +# Marginal User Interface for reasonable prompting +# Copyright © 2010 Daniel Kahn Gillmor <dkg@fifthhorseman.net>, +# Matthew James Goins <mjgoins@openflows.com>, +# Jameson Graef Rollins <jrollins@finestructure.net>, +# Elliot Winard <enw@caveteen.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +#---------------------------------------------------------------------- + +{ package Crypt::Monkeysphere::Logger; + + use strict; + use warnings; + + # Net::Server log_level goes from 0 to 4 + # this is scaled to match. + my %loglevels = ( + 'silent' => 0, + 'quiet' => 0.25, + 'fatal' => 0.5, + 'error' => 1, + 'info' => 2, + 'verbose' => 3, + 'debug' => 4, + 'debug1' => 4, + 'debug2' => 5, + 'debug3' => 6, + ); + + sub log { + my $self = shift; + my $msglevel = shift; + + $msglevel = 'error' + if (! defined($msglevel)); + if ($loglevels{lc($msglevel)} <= $self->{loglevel}) { + printf STDERR @_; + } + }; + + sub get_log_level { + my $self = shift; + + return $self->{loglevel}; + } + sub set_log_level { + my $self = shift; + my $loglevel = shift; + my $logval = $loglevels{lc($loglevel)}; + + if (defined($logval)) { + $self->{loglevel} = $logval; + } else { + $self->log('error', "Invalid log level: '%s' (log level not changed)\n", $loglevel); + } + } + sub more_verbose { + my $self = shift; + my $increment = shift; + + $increment = 1 + if (!defined $increment); + $self->{loglevel} += $increment; + } + + # let the user test to see if we're noisier than this level + # directly: + sub is_logging_at { + my $self = shift; + my $qlevel = shift; + + return ($loglevels{lc($qlevel)} <= $self->{loglevel}); + } + + sub new { + my $class = shift; + my $loglevel = shift; + + my $self = {loglevel => $loglevels{defined($loglevel) ? lc($loglevel) : 'error'}}; + $self->{loglevel} = $loglevels{error} + if (!defined $self->{loglevel}); + + bless ($self, $class); + return $self; + } + + 1; +} diff -Nru msva-perl-0.8/Crypt/Monkeysphere/MSVA/Client.pm msva-perl-0.9/Crypt/Monkeysphere/MSVA/Client.pm --- msva-perl-0.8/Crypt/Monkeysphere/MSVA/Client.pm 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/MSVA/Client.pm 2012-07-28 16:43:34.000000000 -0400 @@ -26,7 +26,7 @@ use strict; use warnings; use JSON; - use Crypt::Monkeysphere::MSVA::Logger; + use Crypt::Monkeysphere::Logger; use LWP::UserAgent; use HTTP::Request; use Module::Load::Conditional; @@ -36,6 +36,20 @@ $self->{logger}->log(@_); } + sub agent_info { + my $self = shift; + my $requesturl = $self->{socket} . '/'; + my $request = HTTP::Request->new('GET', $requesturl); + $self->log('debug', "Contacting MSVA at %s\n", $requesturl); + my $response = $self->{ua}->request($request); + my $status = $response->status_line; + my $ret; + if ($status eq '200 OK') { + $ret = from_json($response->content); + } + return $status, $ret; + } + sub query_agent { my $self = shift; my $context = shift; @@ -91,35 +105,12 @@ $self->log('debug', "pkctype: %s\n", $pkctype); my $transformed_data; - if ($pkctype eq 'x509der') { - if ($self->{logger}->is_logging_at('verbose')) { - if (Module::Load::Conditional::can_load('modules' => { 'Crypt::X509' => undef })) { - require Crypt::X509; - my $cert = Crypt::X509->new(cert => $pkcdata); - if ($cert->error) { - $self->log('error', "failed to parse this X.509 cert before sending it to the agent\n"); - } else { - $self->log('info', "x509der certificate loaded.\n"); - $self->log('verbose', "cert subject: %s\n", $cert->subject_cn()); - $self->log('verbose', "cert issuer: %s\n", $cert->issuer_cn()); - $self->log('verbose', "cert pubkey algo: %s\n", $cert->PubKeyAlg()); - $self->log('verbose', "cert pubkey: %s\n", unpack('H*', $cert->pubkey())); - } - } else { - $self->log('verbose', "X.509 cert going to agent but we cannot inspect it without Crypt::X509\n"); - } - } - # remap raw pkc data into numeric array + # remap raw der data into numeric array $transformed_data = [map(ord, split(//,$pkcdata))]; - } elsif ($pkctype eq 'x509pem' || - $pkctype eq 'opensshpubkey' || - $pkctype eq 'rfc4716' - ) { - $transformed_data = $pkcdata; } else { - $self->log('error', "unknown pkc type '%s'.\n", $pkctype); - }; + $transformed_data = $pkcdata; + } my $ret = { context => $context, @@ -143,9 +134,9 @@ my %args = @_; my $self = {}; - $self->{logger} = Crypt::Monkeysphere::MSVA::Logger->new($args{log_level}); + $self->{logger} = Crypt::Monkeysphere::Logger->new($args{log_level}); $self->{socket} = $args{socket}; - $self->{socket} = 'http://localhost:8901' + $self->{socket} = 'http://127.0.0.1:8901' if (! defined $self->{socket} or $self->{socket} eq ''); # create the user agent diff -Nru msva-perl-0.8/Crypt/Monkeysphere/MSVA/Logger.pm msva-perl-0.9/Crypt/Monkeysphere/MSVA/Logger.pm --- msva-perl-0.8/Crypt/Monkeysphere/MSVA/Logger.pm 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/MSVA/Logger.pm 1969-12-31 19:00:00.000000000 -0500 @@ -1,100 +0,0 @@ -#---------------------------------------------------------------------- -# Monkeysphere Validation Agent, Perl version -# Marginal User Interface for reasonable prompting -# Copyright © 2010 Daniel Kahn Gillmor <dkg@fifthhorseman.net>, -# Matthew James Goins <mjgoins@openflows.com>, -# Jameson Graef Rollins <jrollins@finestructure.net>, -# Elliot Winard <enw@caveteen.com> -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see <http://www.gnu.org/licenses/>. -# -#---------------------------------------------------------------------- - -{ package Crypt::Monkeysphere::MSVA::Logger; - - use strict; - use warnings; - - # Net::Server log_level goes from 0 to 4 - # this is scaled to match. - my %loglevels = ( - 'silent' => 0, - 'quiet' => 0.25, - 'fatal' => 0.5, - 'error' => 1, - 'info' => 2, - 'verbose' => 3, - 'debug' => 4, - 'debug1' => 4, - 'debug2' => 5, - 'debug3' => 6, - ); - - sub log { - my $self = shift; - my $msglevel = shift; - - if ($loglevels{lc($msglevel)} <= $self->{loglevel}) { - printf STDERR @_; - } - }; - - sub get_log_level { - my $self = shift; - - return $self->{loglevel}; - } - sub set_log_level { - my $self = shift; - my $loglevel = shift; - my $logval = $loglevels{lc($loglevel)}; - - if (defined($logval)) { - $self->{loglevel} = $logval; - } else { - $self->log('error', "Invalid log level: '%s' (log level not changed)\n", $loglevel); - } - } - sub more_verbose { - my $self = shift; - my $increment = shift; - - $increment = 1 - if (!defined $increment); - $self->{loglevel} += $increment; - } - - # let the user test to see if we're noisier than this level - # directly: - sub is_logging_at { - my $self = shift; - my $qlevel = shift; - - return ($loglevels{lc($qlevel)} <= $self->{loglevel}); - } - - sub new { - my $class = shift; - my $loglevel = shift; - - my $self = {loglevel => $loglevels{lc($loglevel)}}; - $self->{loglevel} = $loglevels{error} - if (!defined $self->{loglevel}); - - bless ($self, $class); - return $self; - } - - 1; -} diff -Nru msva-perl-0.8/Crypt/Monkeysphere/MSVA/MarginalUI.pm msva-perl-0.9/Crypt/Monkeysphere/MSVA/MarginalUI.pm --- msva-perl-0.8/Crypt/Monkeysphere/MSVA/MarginalUI.pm 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/MSVA/MarginalUI.pm 2012-07-28 16:43:34.000000000 -0400 @@ -46,7 +46,8 @@ } foreach my $keyfpr (@subvalid_key_fprs) { - my $fprx = sprintf('0x%.40s', $keyfpr->{fpr}->as_hex_string()); + $keyfpr->{fingerprint}->as_hex_string() =~ /([[:xdigit:]]{0,40})/; + my $fprx = '0x' . $1; $logger->log('debug', "checking on %s\n", $fprx); foreach my $gpgkey ($gnupg->get_public_keys_with_sigs($fprx)) { $logger->log('debug', "found key %.40s\n", $gpgkey->fingerprint->as_hex_string); @@ -72,7 +73,7 @@ if ($cert->hex_id =~ /^([A-Fa-f0-9]{16})$/) { my $certid = $1; # disregard self-certifications (see MS # 2569): - if (lc($certid) eq lc(substr($keyfpr->{fpr}->as_hex_string(), -16))) { + if (lc($certid) eq lc(substr($keyfpr->{fingerprint}->as_hex_string(), -16))) { $logger->log('debug', "found self-sig 0x%.16s\n", $certid); next; } @@ -159,7 +160,7 @@ Peer's OpenPGP key fingerprint: 0x%.40s GnuPG calculated validity for the peer: %s", $uid, - $keyfpr->{fpr}->as_hex_string, + $keyfpr->{fingerprint}->as_hex_string, $keyfpr->{val}, ); # FIXME: what about revoked certifications? diff -Nru msva-perl-0.8/Crypt/Monkeysphere/MSVA/Monitor.pm msva-perl-0.9/Crypt/Monkeysphere/MSVA/Monitor.pm --- msva-perl-0.8/Crypt/Monkeysphere/MSVA/Monitor.pm 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/MSVA/Monitor.pm 2012-07-28 16:43:34.000000000 -0400 @@ -23,6 +23,7 @@ { package Crypt::Monkeysphere::MSVA::Monitor; + use Module::Load::Conditional; use strict; use warnings; diff -Nru msva-perl-0.8/Crypt/Monkeysphere/MSVA.pm msva-perl-0.9/Crypt/Monkeysphere/MSVA.pm --- msva-perl-0.8/Crypt/Monkeysphere/MSVA.pm 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/MSVA.pm 2012-07-28 16:43:34.000000000 -0400 @@ -22,9 +22,11 @@ use vars qw($VERSION); use parent qw(HTTP::Server::Simple::CGI); - require Crypt::X509; + + use Crypt::Monkeysphere::Validator; + + use Crypt::X509 0.50; use Regexp::Common qw /net/; - use Convert::ASN1; use MIME::Base64; use IO::Socket; use IO::File; @@ -33,15 +35,17 @@ use File::HomeDir; use Config::General; use Crypt::Monkeysphere::MSVA::MarginalUI; - use Crypt::Monkeysphere::MSVA::Logger; + use Crypt::Monkeysphere::Logger; + use Crypt::Monkeysphere::Util qw(untaint); use Crypt::Monkeysphere::MSVA::Monitor; + use Crypt::Monkeysphere::OpenPGP; use JSON; use POSIX qw(strftime); # we need the version of GnuPG::Interface that knows about pubkey_data, etc: - use GnuPG::Interface 0.42.02; + use GnuPG::Interface 0.43; - $VERSION = '0.8'; + $VERSION = '0.9~pre'; my $gnupg = GnuPG::Interface::->new(); $gnupg->options->quiet(1); @@ -59,23 +63,13 @@ }, ); - my $default_keyserver = 'hkp://pool.sks-keyservers.net'; my $default_keyserver_policy = 'unlessvalid'; - my $logger = Crypt::Monkeysphere::MSVA::Logger::->new($ENV{MSVA_LOG_LEVEL}); + my $logger = Crypt::Monkeysphere::Logger->new($ENV{MSVA_LOG_LEVEL}); sub logger { return $logger; } - my $rsa_decoder = Convert::ASN1::->new(); - $rsa_decoder->prepare(q< - - SEQUENCE { - modulus INTEGER, - exponent INTEGER - } - >); - sub net_server { return 'Net::Server::MSVA'; }; @@ -137,81 +131,6 @@ }; } - sub opensshpubkey2key { - my $data = shift; - # FIXME: do we care that the label matches the type of key? - my ($label, $prop) = split(/ +/, $data); - - my $out = parse_rfc4716body($prop); - - return $out; - } - - sub rfc47162key { - my $data = shift; - - my @goodlines; - my $continuation = ''; - my $state = 'outside'; - foreach my $line (split(/\n/, $data)) { - last if ($state eq 'body' && $line eq '---- END SSH2 PUBLIC KEY ----'); - if ($state eq 'outside' && $line eq '---- BEGIN SSH2 PUBLIC KEY ----') { - $state = 'header'; - next; - } - if ($state eq 'header') { - $line = $continuation.$line; - $continuation = ''; - if ($line =~ /^(.*)\\$/) { - $continuation = $1; - next; - } - if (! ($line =~ /:/)) { - $state = 'body'; - } - } - push(@goodlines, $line) if ($state eq 'body'); - } - - msvalog('debug', "Found %d lines of RFC4716 body:\n%s\n", - scalar(@goodlines), - join("\n", @goodlines)); - my $out = parse_rfc4716body(join('', @goodlines)); - - return $out; - } - - sub parse_rfc4716body { - my $data = shift; - - return undef - unless defined($data); - $data = decode_base64($data) or return undef; - - msvalog('debug', "key properties: %s\n", unpack('H*', $data)); - my $out = [ ]; - while (length($data) > 4) { - my $size = unpack('N', substr($data, 0, 4)); - msvalog('debug', "size: 0x%08x\n", $size); - return undef if (length($data) < $size + 4); - push(@{$out}, substr($data, 4, $size)); - $data = substr($data, 4 + $size); - } - - if ($out->[0] ne "ssh-rsa") { - return {error => 'Not an RSA key'}; - } - - if (scalar(@{$out}) != 3) { - return {error => 'Does not contain the right number of bigints for RSA'}; - } - - return { exponent => Math::BigInt->from_hex('0x'.unpack('H*', $out->[1])), - modulus => Math::BigInt->from_hex('0x'.unpack('H*', $out->[2])), - } ; - } - - # return an arrayref of processes which we can detect that have the # given socket open (the socket is specified with its inode) sub getpidswithsocketinode { @@ -376,7 +295,7 @@ # This is part of a spawned child process. We don't want the # child process to destroy the update monitor when it terminates. - $self->{updatemonitor}->forget(); + $self->{updatemonitor}->forget() if exists $self->{updatemonitor} && defined $self->{updatemonitor}; my $clientinfo = get_client_info(select); my $clientuid = $clientinfo->{uid}; @@ -435,37 +354,69 @@ } } - sub keycomp { - my $rsakey = shift; - my $gpgkey = shift; - - if ($gpgkey->algo_num != 1) { - msvalog('verbose', "Monkeysphere only does RSA keys. This key is algorithm #%d\n", $gpgkey->algo_num); - } else { - if ($rsakey->{exponent}->bcmp($gpgkey->pubkey_data->[1]) == 0 && - $rsakey->{modulus}->bcmp($gpgkey->pubkey_data->[0]) == 0) { - return 1; + sub get_keyserver_policy { + if (exists $ENV{MSVA_KEYSERVER_POLICY} and $ENV{MSVA_KEYSERVER_POLICY} ne '') { + if ($ENV{MSVA_KEYSERVER_POLICY} =~ /^(always|never|unlessvalid)$/) { + return $1; } + msvalog('error', "Not a valid MSVA_KEYSERVER_POLICY):\n %s\n", $ENV{MSVA_KEYSERVER_POLICY}); } - return 0; + return $default_keyserver_policy; } - sub pem2der { - my $pem = shift; - my @lines = split(/\n/, $pem); - my @goodlines = (); - my $ready = 0; - foreach my $line (@lines) { - if ($line eq '-----END CERTIFICATE-----') { - last; - } elsif ($ready) { - push @goodlines, $line; - } elsif ($line eq '-----BEGIN CERTIFICATE-----') { - $ready = 1; + sub get_keyserver { + # We should read from (first hit wins): + # the environment + if (exists $ENV{MSVA_KEYSERVER} and $ENV{MSVA_KEYSERVER} ne '') { + if ($ENV{MSVA_KEYSERVER} =~ /^(((hkps?|hkpms|finger|ldap):\/\/)?$RE{net}{domain})$/) { + return $1; } + msvalog('error', "Not a valid keyserver (from MSVA_KEYSERVER):\n %s\n", $ENV{MSVA_KEYSERVER}); } - msvalog('debug', "%d lines of base64:\n%s\n", $#goodlines + 1, join("\n", @goodlines)); - return decode_base64(join('', @goodlines)); + + # FIXME: some msva.conf or monkeysphere.conf file (system and user?) + + # let the keyserver routines choose. + return undef; + } + + +################################################## +## PKC KEY EXTRACTION ############################ + + sub pkcextractkey { + my $data = shift; + my $key; + + if (lc($data->{pkc}->{type}) eq 'x509der') { + $key = der2key(join('', map(chr, @{$data->{pkc}->{data}}))); + } elsif (lc($data->{pkc}->{type}) eq 'x509pem') { + $key = der2key(pem2der($data->{pkc}->{data})); + } elsif (lc($data->{pkc}->{type}) eq 'opensshpubkey') { + $key = opensshpubkey2key($data->{pkc}->{data}); + } elsif (lc($data->{pkc}->{type}) eq 'rfc4716') { + $key = rfc47162key($data->{pkc}->{data}); + } else { + $key->{error} = sprintf("Don't know this public key carrier type: %s", $data->{pkc}->{type}); + } + + if (exists $key->{error}) { + return $key; + } + + # make sure that the returned integers are Math::BigInts: + $key->{exponent} = Math::BigInt::->new($key->{exponent}) unless (ref($key->{exponent})); + $key->{modulus} = Math::BigInt::->new($key->{modulus}) unless (ref($key->{modulus})); + msvalog('debug', "pubkey info:\nmodulus: %s\nexponent: %s\n", + $key->{modulus}->as_hex(), + $key->{exponent}->as_hex(), + ); + + if ($key->{modulus}->copy()->blog(2) < 1000) { + $key->{error} = sprintf('Public key size is less than 1000 bits (was: %d bits)', $key->{modulus}->copy()->blog(2)); + } + + return $key; } sub der2key { @@ -479,7 +430,7 @@ $key->{error} = sprintf("Error decoding X.509 certificate: %s", $cert->error); } else { msvalog('verbose', "cert subject: %s\n", $cert->subject_cn()); - msvalog('verbose', "cert issuer: %s\n", $cert->issuer_cn()); + msvalog('verbose', "cert issuer: %s\n", (defined $cert->issuer_cn() ? $cert->issuer_cn() : '<none>')); msvalog('verbose', "cert pubkey algo: %s\n", $cert->PubKeyAlg()); msvalog('verbose', "cert pubkey: %s\n", unpack('H*', $cert->pubkey())); @@ -488,95 +439,121 @@ $cert->PubKeyAlg(), $cert->pubkey_algorithm); } else { msvalog('debug', "decoding ASN.1 pubkey\n"); - $key = $rsa_decoder->decode($cert->pubkey()); + $key = $cert->pubkey_components(); if (! defined $key) { msvalog('verbose', "failed to decode %s\n", unpack('H*', $cert->pubkey())); $key = {error => 'failed to decode the public key'}; + } else { + # ensure these are Math::BigInts! + $key->{exponent} = Math::BigInt::->new($key->{exponent}) unless (ref($key->{exponent})); + $key->{modulus} = Math::BigInt::->new($key->{modulus}) unless (ref($key->{modulus})); + + my $pgpext = $cert->PGPExtension(); + if (defined $pgpext) { + $key->{openpgp4fpr} = Crypt::Monkeysphere::OpenPGP::fingerprint($key, $pgpext); + msvalog('verbose', "OpenPGP Fingerprint (derived from X.509 cert): 0x%s\n", uc(unpack("H*", $key->{openpgp4fpr}))); + } } } } return $key; } - sub get_keyserver_policy { - if (exists $ENV{MSVA_KEYSERVER_POLICY} and $ENV{MSVA_KEYSERVER_POLICY} ne '') { - if ($ENV{MSVA_KEYSERVER_POLICY} =~ /^(always|never|unlessvalid)$/) { - return $1; + sub pem2der { + my $pem = shift; + my @lines = split(/\n/, $pem); + my @goodlines = (); + my $ready = 0; + foreach my $line (@lines) { + if ($line eq '-----END CERTIFICATE-----') { + last; + } elsif ($ready) { + push @goodlines, $line; + } elsif ($line eq '-----BEGIN CERTIFICATE-----') { + $ready = 1; } - msvalog('error', "Not a valid MSVA_KEYSERVER_POLICY):\n %s\n", $ENV{MSVA_KEYSERVER_POLICY}); } - return $default_keyserver_policy; + msvalog('debug', "%d lines of base64:\n%s\n", $#goodlines + 1, join("\n", @goodlines)); + return decode_base64(join('', @goodlines)); } - sub get_keyserver { - # We should read from (first hit wins): - # the environment - if (exists $ENV{MSVA_KEYSERVER} and $ENV{MSVA_KEYSERVER} ne '') { - if ($ENV{MSVA_KEYSERVER} =~ /^(((hkps?|hkpms|finger|ldap):\/\/)?$RE{net}{domain})$/) { - return $1; - } - msvalog('error', "Not a valid keyserver (from MSVA_KEYSERVER):\n %s\n", $ENV{MSVA_KEYSERVER}); - } + sub opensshpubkey2key { + my $data = shift; + # FIXME: do we care that the label matches the type of key? + my ($label, $prop) = split(/ +/, $data); - # FIXME: some msva.conf or monkeysphere.conf file (system and user?) + my $out = parse_rfc4716body($prop); - # or else read from the relevant gnupg.conf: - my $gpghome; - if (exists $ENV{GNUPGHOME} and $ENV{GNUPGHOME} ne '') { - $gpghome = untaint($ENV{GNUPGHOME}); - } else { - $gpghome = File::Spec->catfile(File::HomeDir->my_home, '.gnupg'); - } - my $gpgconf = File::Spec->catfile($gpghome, 'gpg.conf'); - if (-f $gpgconf) { - if (-r $gpgconf) { - my %gpgconfig = Config::General::ParseConfig($gpgconf); - if ($gpgconfig{keyserver} =~ /^(((hkps?|hkpms|finger|ldap):\/\/)?$RE{net}{domain})$/) { - msvalog('debug', "Using keyserver %s from the GnuPG configuration file (%s)\n", $1, $gpgconf); - return $1; - } else { - msvalog('error', "Not a valid keyserver (from gpg config %s):\n %s\n", $gpgconf, $gpgconfig{keyserver}); + return $out; + } + + sub rfc47162key { + my $data = shift; + + my @goodlines; + my $continuation = ''; + my $state = 'outside'; + foreach my $line (split(/\n/, $data)) { + last if ($state eq 'body' && $line eq '---- END SSH2 PUBLIC KEY ----'); + if ($state eq 'outside' && $line eq '---- BEGIN SSH2 PUBLIC KEY ----') { + $state = 'header'; + next; + } + if ($state eq 'header') { + $line = $continuation.$line; + $continuation = ''; + if ($line =~ /^(.*)\\$/) { + $continuation = $1; + next; + } + if (! ($line =~ /:/)) { + $state = 'body'; } - } else { - msvalog('error', "The GnuPG configuration file (%s) is not readable\n", $gpgconf); } - } else { - msvalog('info', "Did not find GnuPG configuration file while looking for keyserver '%s'\n", $gpgconf); + push(@goodlines, $line) if ($state eq 'body'); } - # the default_keyserver - return $default_keyserver; + msvalog('debug', "Found %d lines of RFC4716 body:\n%s\n", + scalar(@goodlines), + join("\n", @goodlines)); + my $out = parse_rfc4716body(join('', @goodlines)); + + return $out; } - sub fetch_uid_from_keyserver { - my $uid = shift; + sub parse_rfc4716body { + my $data = shift; - my $cmd = IO::Handle::->new(); - my $out = IO::Handle::->new(); - my $nul = IO::File::->new("< /dev/null"); + return undef + unless defined($data); + $data = decode_base64($data) or return undef; - my $ks = get_keyserver(); - msvalog('debug', "start ks query to %s for UserID: %s\n", $ks, $uid); - my $pid = $gnupg->wrap_call - ( handles => GnuPG::Handles::->new( command => $cmd, stdout => $out, stderr => $nul ), - command_args => [ '='.$uid ], - commands => [ '--keyserver', - $ks, - qw( --no-tty --with-colons --search ) ] - ); - while (my $line = $out->getline()) { - msvalog('debug', "from ks query: (%d) %s", $cmd->fileno, $line); - if ($line =~ /^info:(\d+):(\d+)/ ) { - $cmd->print(join(' ', ($1..$2))."\n"); - msvalog('debug', 'to ks query: '.join(' ', ($1..$2))."\n"); - last; - } + msvalog('debug', "key properties: %s\n", unpack('H*', $data)); + my $out = [ ]; + while (length($data) > 4) { + my $size = unpack('N', substr($data, 0, 4)); + msvalog('debug', "size: 0x%08x\n", $size); + return undef if (length($data) < $size + 4); + push(@{$out}, substr($data, 4, $size)); + $data = substr($data, 4 + $size); } - # FIXME: can we do something to avoid hanging forever? - waitpid($pid, 0); - msvalog('debug', "ks query returns %d\n", POSIX::WEXITSTATUS($?)); + + if ($out->[0] ne "ssh-rsa") { + return {error => 'Not an RSA key'}; + } + + if (scalar(@{$out}) != 3) { + return {error => 'Does not contain the right number of bigints for RSA'}; + } + + return { exponent => Math::BigInt->from_hex('0x'.unpack('H*', $out->[1])), + modulus => Math::BigInt->from_hex('0x'.unpack('H*', $out->[2])), + } ; } +## PKC KEY EXTRACTION ############################ +################################################## + sub reviewcert { my $data = shift; my $clientinfo = shift; @@ -589,8 +566,14 @@ message => 'Unknown failure', }; + # check that there actually is key data + if ($data->{pkc}->{data} eq '') { + $ret->{message} = sprintf("Key data empty."); + return $status,$ret; + } + # check context string - if ($data->{context} =~ /^(https|ssh|smtp|ike|postgresql|imaps|imap|submission)$/) { + if ($data->{context} =~ /^(https|ssh|smtp|ike|postgresql|imaps|imap|submission|e-mail)$/) { $data->{context} = $1; } else { msvalog('error', "invalid context: %s\n", $data->{context}); @@ -616,11 +599,12 @@ } my $prefix = $data->{context}.'://'; - if (defined $data->{peer}->{type} && + if ($data->{context} eq 'e-mail' || + (defined $data->{peer}->{type} && $data->{peer}->{type} eq 'client' && # ike and smtp clients are effectively other servers, so we'll # exclude them: - $data->{context} !~ /^(ike|smtp)$/) { + $data->{context} !~ /^(ike|smtp)$/)) { $prefix = ''; # clients can have any one-line User ID without NULL characters # and leading or trailing whitespace @@ -629,7 +613,7 @@ } else { msvalog('error', "invalid client peer name string: %s\n", $data->{peer}->{name}); $ret->{message} = sprintf("Invalid client peer name string: %s", $data->{peer}->{name}); - return $status, $ret; + return $status,$ret; } } elsif ($data->{peer}->{name} =~ /^($RE{net}{domain}(:[[:digit:]]+)?)$/) { $data->{peer}->{name} = $1; @@ -645,131 +629,95 @@ my $uid = $prefix.$data->{peer}->{name}; msvalog('verbose', "user ID: %s\n", $uid); - # check pkc type + # extract key or openpgp fingerprint from PKC + my $fpr; my $key; - if (lc($data->{pkc}->{type}) eq 'x509der') { - $key = der2key(join('', map(chr, @{$data->{pkc}->{data}}))); - } elsif (lc($data->{pkc}->{type}) eq 'x509pem') { - $key = der2key(pem2der($data->{pkc}->{data})); - } elsif (lc($data->{pkc}->{type}) eq 'opensshpubkey') { - $key = opensshpubkey2key($data->{pkc}->{data}); - } elsif (lc($data->{pkc}->{type}) eq 'rfc4716') { - $key = rfc47162key($data->{pkc}->{data}); - } else { - $ret->{message} = sprintf("Don't know this public key carrier type: %s", $data->{pkc}->{type}); - return $status,$ret; - } - - if (exists $key->{error}) { - $ret->{message} = $key->{error}; - return $status,$ret; - } - - # make sure that the returned integers are Math::BigInts: - $key->{exponent} = Math::BigInt::->new($key->{exponent}) unless (ref($key->{exponent})); - $key->{modulus} = Math::BigInt::->new($key->{modulus}) unless (ref($key->{modulus})); - msvalog('debug', "pubkey info:\nmodulus: %s\nexponent: %s\n", - $key->{modulus}->as_hex(), - $key->{exponent}->as_hex(), - ); - - if ($key->{modulus}->copy()->blog(2) < 1000) { - $ret->{message} = sprintf('Public key size is less than 1000 bits (was: %d bits)', $key->{modulus}->copy()->blog(2)); - } else { - $ret->{message} = sprintf('Failed to validate "%s" through the OpenPGP Web of Trust.', $uid); - my $lastloop = 0; - my $kspolicy; - if (defined $data->{keyserverpolicy} && - $data->{keyserverpolicy} =~ /^(always|never|unlessvalid)$/) { - $kspolicy = $1; - msvalog("verbose", "using requested keyserver policy: %s\n", $1); + if (lc($data->{pkc}->{type}) eq 'openpgp4fpr') { + if ($data->{pkc}->{data} =~ /^(0x)?([[:xdigit:]]{40})$/) { + $data->{pkc}->{data} = uc($2); + $fpr = $data->{pkc}->{data}; } else { - $kspolicy = get_keyserver_policy(); + msvalog('error', "invalid OpenPGP v4 fingerprint: %s\n",$data->{pkc}->{data}); + $ret->{message} = sprintf("Invalid OpenPGP v4 fingerprint."); + return $status,$ret; } - msvalog('debug', "keyserver policy: %s\n", $kspolicy); - # needed because $gnupg spawns child processes - $ENV{PATH} = '/usr/local/bin:/usr/bin:/bin'; - if ($kspolicy eq 'always') { - fetch_uid_from_keyserver($uid); - $lastloop = 1; - } elsif ($kspolicy eq 'never') { - $lastloop = 1; - } - my $foundvalid = 0; - - # fingerprints of keys that are not fully-valid for this User ID, but match - # the key from the queried certificate: - my @subvalid_key_fprs; - - while (1) { - foreach my $gpgkey ($gnupg->get_public_keys('='.$uid)) { - my $validity = '-'; - foreach my $tryuid ($gpgkey->user_ids) { - if ($tryuid->as_string eq $uid) { - $validity = $tryuid->validity; - } - } - # treat primary keys just like subkeys: - foreach my $subkey ($gpgkey, @{$gpgkey->subkeys}) { - my $primarymatch = keycomp($key, $subkey); - if ($primarymatch) { - if ($subkey->usage_flags =~ /a/) { - msvalog('verbose', "key matches, and 0x%s is authentication-capable\n", $subkey->hex_id); - if ($validity =~ /^[fu]$/) { - $foundvalid = 1; - msvalog('verbose', "...and it matches!\n"); - $ret->{valid} = JSON::true; - $ret->{message} = sprintf('Successfully validated "%s" through the OpenPGP Web of Trust.', $uid); - } else { - push(@subvalid_key_fprs, { fpr => $subkey->fingerprint, val => $validity }) if $lastloop; - } - } else { - msvalog('verbose', "key matches, but 0x%s is not authentication-capable\n", $subkey->hex_id); - } - } - } - } - if ($lastloop) { - last; - } else { - fetch_uid_from_keyserver($uid) if (!$foundvalid); - $lastloop = 1; - } + } else { + # extract key from PKC + $key = pkcextractkey($data); + if (exists $key->{error}) { + $ret->{message} = $key->{error}; + return $status,$ret; } + $fpr = uc(unpack('H*', $key->{openpgp4fpr})) + if (exists $key->{openpgp4fpr}); + } + msvalog('verbose', "OpenPGP v4 fingerprint: %s\n",$fpr) + if defined $fpr; - # only show the marginal UI if the UID of the corresponding - # key is not fully valid. - if (!$foundvalid) { - my $resp = Crypt::Monkeysphere::MSVA::MarginalUI->ask_the_user($gnupg, - $uid, - \@subvalid_key_fprs, - getpidswithsocketinode($clientinfo->{inode}), - $logger); - msvalog('info', "response: %s\n", $resp); - if ($resp) { - $ret->{valid} = JSON::true; - $ret->{message} = sprintf('Manually validated "%s" through the OpenPGP Web of Trust.', $uid); - } + # determine keyserver policy + my $kspolicy; + if (defined $data->{keyserverpolicy} && + $data->{keyserverpolicy} =~ /^(always|never|unlessvalid)$/) { + $kspolicy = $1; + msvalog("verbose", "using requested keyserver policy: %s\n", $1); + } else { + $kspolicy = get_keyserver_policy(); + } + msvalog('debug', "keyserver policy: %s\n", $kspolicy); + # needed because $gnupg spawns child processes + $ENV{PATH} = '/usr/local/bin:/usr/bin:/bin'; + + $ret->{message} = sprintf('Failed to validate "%s" through the OpenPGP Web of Trust.', $uid); + + my $validator=new Crypt::Monkeysphere::Validator(kspolicy=>$kspolicy, + context=>$data->{context}, + keyserver=>get_keyserver(), + gnupg=>$gnupg, + logger=>$logger); + + my $uid_query=$validator->lookup(uid=>$uid,fpr=>$fpr,key=>$key); + + # only show the marginal UI if the UID of the corresponding + # key is not fully valid. + if (defined($uid_query->{valid_key})) { + $ret->{valid} = JSON::true; + $ret->{message} = sprintf('Successfully validated "%s" through the OpenPGP Web of Trust.', $uid); + } else { + my $resp = Crypt::Monkeysphere::MSVA::MarginalUI->ask_the_user($gnupg, + $uid, + $uid_query->{subvalid_keys}, + getpidswithsocketinode($clientinfo->{inode}), + $logger); + msvalog('info', "response: %s\n", $resp); + if ($resp) { + $ret->{valid} = JSON::true; + $ret->{message} = sprintf('Manually validated "%s" through the OpenPGP Web of Trust.', $uid); } } - return $status, $ret; + + return $status,$ret; } sub pre_loop_hook { my $self = shift; my $server = shift; - $self->spawn_master_subproc($server); + $self->spawn_as_child($server); + } + + sub pre_accept_hook { + my $self = shift; + my $server = shift; + + $self->parent_changed($server) if (getppid() != $self->{parent_pid}); } - sub master_subprocess_died { + sub parent_changed { my $self = shift; my $server = shift; - my $subproc_return = shift; - my $exitstatus = POSIX::WEXITSTATUS($subproc_return); - msvalog('verbose', "Subprocess %d terminated; exiting %d.\n", $self->{child_pid}, $exitstatus); - $server->set_exit_status($exitstatus); + msvalog('verbose', "parent %d went away; exiting.\n", $self->{parent_pid}); + $server->set_exit_status(0); $server->server_close(); } @@ -802,22 +750,9 @@ # instead, we'll just avoid trying to kill the next process with this PID: $self->{updatemonitor}->forget(); } - } elsif (exists $self->{child_pid} && - ($self->{child_pid} == 0 || - $self->{child_pid} == $pid)) { - $self->master_subprocess_died($server, $?); } } - # use sparingly! We want to keep taint mode around for the data we - # get over the network. this is only here because we want to treat - # the command line arguments differently for the subprocess. - sub untaint { - my $x = shift; - $x =~ /^(.*)$/ ; - return $1; - } - sub post_bind_hook { my $self = shift; my $server = shift; @@ -825,36 +760,41 @@ $server->{server}->{leave_children_open_on_hup} = 1; my $socketcount = @{ $server->{server}->{sock} }; - if ( $socketcount != 1 ) { - msvalog('error', "%d sockets open; should have been 1.\n", $socketcount); + # note: we're assuming here that if there are more than one socket + # open (e.g. IPv6 and IPv4, or multiple IP addresses of the same + # family), they all share the same port number as socket 0. + if ( $socketcount < 1 ) { + msvalog('error', "%d sockets open; should have been at least 1.\n", $socketcount); $server->set_exit_status(10); $server->server_close(); } - my $port = @{ $server->{server}->{sock} }[0]->sockport(); - if ((! defined $port) || ($port < 1) || ($port >= 65536)) { - msvalog('error', "got nonsense port: %d.\n", $port); - $server->set_exit_status(11); - $server->server_close(); - } - if ((exists $ENV{MSVA_PORT}) && (($ENV{MSVA_PORT} + 0) != $port)) { - msvalog('error', "Explicitly requested port %d, but got port: %d.", ($ENV{MSVA_PORT}+0), $port); - $server->set_exit_status(13); - $server->server_close(); + if (!defined($self->port) || $self->port == 0) { + my $port = @{ $server->{server}->{sock} }[0]->sockport(); + if (! defined($port)) { + msvalog('error', "got undefined port.\nRecording as 0.\n", $port); + $port = 0; + } elsif (($port < 1) || ($port >= 65536)) { + msvalog('error', "got nonsense port: %d.\nRecording as 0.\n", $port); + $port = 0; + } elsif ((exists $ENV{MSVA_PORT}) && (($ENV{MSVA_PORT} + 0) != $port)) { + msvalog('error', "Explicitly requested port %d, but got port: %d.", ($ENV{MSVA_PORT}+0), $port); + $server->set_exit_status(13); + $server->server_close(); + } + $self->port($port); } - $self->port($port); - $self->{updatemonitor} = Crypt::Monkeysphere::MSVA::Monitor::->new($logger); } - sub spawn_master_subproc { + sub spawn_as_child { my $self = shift; my $server = shift; - if ((exists $ENV{MSVA_CHILD_PID}) && ($ENV{MSVA_CHILD_PID} ne '')) { + if ((exists $ENV{MSVA_PARENT_PID}) && ($ENV{MSVA_PARENT_PID} ne '')) { # this is most likely a re-exec. - msvalog('info', "This appears to be a re-exec, continuing with child pid %d\n", $ENV{MSVA_CHILD_PID}); - $self->{child_pid} = $ENV{MSVA_CHILD_PID} + 0; - } elsif ($#ARGV >= 0) { - $self->{child_pid} = 0; # indicate that we are planning to fork. + msvalog('info', "This appears to be a re-exec, continuing with parent pid %d\n", $ENV{MSVA_PARENT_PID}); + $self->{parent_pid} = $ENV{MSVA_PARENT_PID} + 0; + } elsif ($#ARGV >= 0) { + $self->{parent_pid} = 0; # indicate that we are planning to fork. # avoid ignoring SIGCHLD right before we fork. $SIG{CHLD} = sub { my $val; @@ -862,20 +802,26 @@ $self->child_dies($val, $server); } }; + my $pid = $$; my $fork = fork(); if (! defined $fork) { msvalog('error', "could not fork\n"); } else { - if ($fork) { - msvalog('debug', "Child process has PID %d\n", $fork); - $self->{child_pid} = $fork; - $ENV{MSVA_CHILD_PID} = $fork; + if (! $fork) { + msvalog('debug', "daemon has PID %d, parent has PID %d\n", $$, $pid); + $self->{parent_pid} = $pid; + # ppid is set in Net::Server::Fork's post_configure; we're + # past post_configure by here, and we're about to change + # process IDs before assuming the role of a forking server, + # so we should set it properly: + $server->{server}->{ppid} = $$; + $ENV{MSVA_PARENT_PID} = $pid; } else { msvalog('verbose', "PID %d executing: \n", $$); for my $arg (@ARGV) { msvalog('verbose', " %s\n", $arg); } - # untaint the environment for the subprocess + # untaint the environment for the parent process # see: https://labs.riseup.net/code/issues/2461 foreach my $e (keys %ENV) { $ENV{$e} = untaint($ENV{$e}); @@ -886,16 +832,22 @@ } # restore default SIGCHLD handling: $SIG{CHLD} = 'DEFAULT'; - $ENV{MONKEYSPHERE_VALIDATION_AGENT_SOCKET} = sprintf('http://localhost:%d', $self->port); + $ENV{MONKEYSPHERE_VALIDATION_AGENT_SOCKET} = sprintf('http://127.0.0.1:%d', $self->port); exec(@args) or exit 111; } } } else { - printf("MONKEYSPHERE_VALIDATION_AGENT_SOCKET=http://localhost:%d;\nexport MONKEYSPHERE_VALIDATION_AGENT_SOCKET;\n", $self->port); + printf("MONKEYSPHERE_VALIDATION_AGENT_SOCKET=http://127.0.0.1:%d;\nexport MONKEYSPHERE_VALIDATION_AGENT_SOCKET;\n", $self->port); # FIXME: consider daemonizing here to behave more like # ssh-agent. maybe avoid backgrounding by setting # MSVA_NO_BACKGROUND. }; + if (exists $ENV{MSVA_MONITOR_CHANGES} && + $ENV{MSVA_MONITOR_CHANGES} eq 'true') { + $self->{updatemonitor} = Crypt::Monkeysphere::MSVA::Monitor::->new($logger); + } else { + msvalog('verbose', "Not monitoring for changes\n"); + } } sub extracerts { diff -Nru msva-perl-0.8/Crypt/Monkeysphere/OpenPGP.pm msva-perl-0.9/Crypt/Monkeysphere/OpenPGP.pm --- msva-perl-0.8/Crypt/Monkeysphere/OpenPGP.pm 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/OpenPGP.pm 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,186 @@ +package Crypt::Monkeysphere::OpenPGP; + +use strict; +use warnings; + +use Math::BigInt; +use Digest::SHA; + +## WARNING! This entire module has an unstable API at the moment. +## Please do not rely on it, as it may change in the near future. + + +my $tables = { + # see RFC 4880 section 9.1 (ignoring deprecated algorithms for now) + asym_algos => { rsa => 1, + elgamal => 16, + dsa => 17, + }, + + # see RFC 4880 section 9.2 + 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 + compression => { uncompressed => 0, + zip => 1, + zlib => 2, + bzip2 => 3, + }, + + # see RFC 4880 section 9.4 + digests => { md5 => 1, + sha1 => 2, + ripemd160 => 3, + sha256 => 8, + sha384 => 9, + sha512 => 10, + sha224 => 11, + }, + + # see RFC 4880 section 5.2.3.21 + 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 + 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 + 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.23 + revocation_reasons => { no_reason_specified => 0, + key_superseded => 1, + key_compromised => 2, + key_retired => 3, + user_id_no_longer_valid => 32, + }, + + # see RFC 4880 section 5.2.3.1 + 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) + features => { mdc => 0x01 + }, + + # bitstring (see RFC 4880 5.2.3.17) + keyserver_prefs => { nomodify => 0x80 + }, + }; + + +# takes a Math::BigInt, returns it formatted as OpenPGP MPI +# (RFC 4880 section 3.2) +sub mpi_pack { + my $num = shift; + + my $hex = $num->as_hex(); + $hex =~ s/^0x//; + # ensure we've got an even multiple of 2 nybbles here. + $hex = '0'.$hex + if (length($hex) % 2); + + my $val = pack('H*', $hex); + 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; +} + +sub make_rsa_pub_key_body { + my $key = shift; + my $key_timestamp = shift; + + return + pack('CN', 4, $key_timestamp). + pack('C', $tables->{asym_algos}->{rsa}). + mpi_pack($key->{modulus}). + mpi_pack($key->{exponent}); +} + +sub fingerprint { + my $key = shift; + my $key_timestamp = shift; + + my $rsabody = make_rsa_pub_key_body($key, $key_timestamp); + + return Digest::SHA::sha1(pack('Cn', 0x99, length($rsabody)).$rsabody); +} + + +1; diff -Nru msva-perl-0.8/Crypt/Monkeysphere/Util.pm msva-perl-0.9/Crypt/Monkeysphere/Util.pm --- msva-perl-0.8/Crypt/Monkeysphere/Util.pm 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/Util.pm 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,18 @@ +package Crypt::Monkeysphere::Util; + +use strict; +use warnings; + +use Exporter qw(import); +our @EXPORT_OK=qw(untaint); + + +# use sparingly! We want to keep taint mode around for the data we +# get over the network. +sub untaint { + my $x = shift; + $x =~ /^(.*)$/ ; + return $1; +} + +1; diff -Nru msva-perl-0.8/Crypt/Monkeysphere/Validator.pm msva-perl-0.9/Crypt/Monkeysphere/Validator.pm --- msva-perl-0.8/Crypt/Monkeysphere/Validator.pm 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/Crypt/Monkeysphere/Validator.pm 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,227 @@ +package Crypt::Monkeysphere::Validator; +use Carp; +use strict; +use warnings; + +use parent 'Crypt::Monkeysphere::Keyserver'; + +=pod + +=head2 new + +Create a new Crypt::Monkeysphere::Validator instance + +Arguments + + Param hash, all optional. + + context => 'e-mail|https|ssh|...' + control what counts as suitable user IDs and key capabilities. + + kspolicy => 'always|never|unlessvalid' + when to fetch keys and key updates from keyserver. + + (plus arguments for Crypt::Monkeysphere::{Keyserver,Logger}::new ) + +=head2 lookup + +Arguments + + Param hash. + + uid => (mandatory) OpenPGP User ID desired. + + fpr => fingerprint of the key to compare + + key => hash of pubkey parameters as Math::BigInt values + +one of either fpr or key must be supplied. + +Return Value + + Returns a hashref + + If the lookup succeeded, then the hashref has a key named + valid_key that points to a hashref { fingerprint => $fpr, val => + $validity }. + + If no fully-valid keys+userid were found, but some keys matched + with less-than-valid user IDs, then the hashref has a key named + subvalid_keys that points to an arrayref of { fingerprint => $fpr, + val => $validity } hashrefs. + +=cut + +sub new { + my $class=shift; + my %opts=@_; + + my $self=$class->SUPER::new(%opts); + + $self->{context}=$opts{context} || 'ssh'; + $self->{kspolicy}=$opts{kspolicy} || 'unlessvalid'; + return $self; +} + +sub test_capable { + my $self=shift; + my $subkey=shift; + + if ($self->{context} eq 'e-mail') { + if ($subkey->usage_flags =~ /s/) { + $self->log('verbose', "...and is signing-capable...\n"); + return 1; + } else { + $self->log('verbose', "...but is not signing-capable (%s).\n",$subkey->usage_flags); + } + } else { + if ($subkey->usage_flags =~ /a/) { + $self->log('verbose', "...and is authentication-capable...\n"); + return 1; + } else { + $self->log('verbose', "...but is not authentication-capable (%s).\n",$subkey->usage_flags); + } + } + return 0; +} + +sub _tryquery { + my $self=shift; + my %args=@_; + + my $uid=$args{uid} || croak "uid argument is mandatory"; + my $fpr=$args{fpr}; + my $key=$args{key}; + defined($fpr) || defined($key) || croak "Must supply either a fingerprint or a key"; + + my $subvalid_keys = []; + + my $gpgquery = defined($fpr) ? '0x'.$fpr : '='.$uid; + + foreach my $gpgkey ($self->{gnupg}->get_public_keys($gpgquery)) { + my $validity = '-'; + foreach my $tryuid ($gpgkey->user_ids) { + if ($tryuid->as_string eq $uid) { + $validity = $tryuid->validity; + } + } + # treat primary keys just like subkeys: + foreach my $subkey ($gpgkey, @{$gpgkey->subkeys}) { + if ((defined($key) && $self->keycomp($key, $subkey)) || + (defined($fpr) && ($subkey->fingerprint->as_hex_string eq $fpr))) { + $self->log('verbose', "key 0x%s matches...\n",$subkey->hex_id); + if ($self->test_capable($subkey) ) { + if ($validity =~ /^[fu]$/) { + $self->log('verbose', "...and is fully valid!\n"); + # we have a key that matches with a valid userid -- no need to look further. + return {valid_key => { fingerprint => $subkey->fingerprint, val => $validity }}; + } else { + $self->log('verbose', "...but is not fully valid (%s).\n",$validity); + push(@{$subvalid_keys}, + {fingerprint => $subkey->fingerprint, val => $validity }); + } + } + } + } + } + return { subvalid_keys => $subvalid_keys }; +} + +sub lookup { + my $self=shift; + my %opts=@_; + + if ($self->{kspolicy} eq 'unlessvalid') { + my $ret = $self->_tryquery(uid => $opts{uid}, fpr => $opts{fpr}, key => $opts{key}); + return $ret + if exists($ret->{valid_key}); + }; + + if ($self->{kspolicy} ne 'never') { + if (defined($opts{fpr})) { + $self->fetch_fpr($opts{fpr}); + } else { + $self->fetch_uid($opts{uid}); + } + } + return $self->_tryquery(uid => $opts{uid}, fpr => $opts{fpr}, key => $opts{key}); +} + +sub valid_binding { + my $self = shift; + my $uid = shift; + my $gpgkey = shift; + + my $validity = '-'; + foreach my $tryuid ($gpgkey->user_ids) { + if ($tryuid->as_string eq $uid) { + return 1 + if $tryuid->validity =~ /^[fu]$/; + } + } + return 0; +} + +=pod + +=head2 findall + +Find all keys with appropriate capabilities and valid bindings to the given uid. + +=cut + +sub findall{ + my $self=shift; + my $uid=shift; + + $self->fetch_uid($uid) if ($self->{kspolicy} eq 'always'); + + my @keys = $self->_findall($uid); + + if (scalar(@keys) == 0 and $self->{kspolicy} eq 'unlessvalid'){ + $self->fetch_uid($uid); + @keys=$self->_findall($uid); + } + + return @keys; +} + +sub _findall { + my $self=shift; + my $uid=shift; + + my @keys; + my $x = 0; + + foreach my $gpgkey ($self->{gnupg}->get_public_keys('='.$uid)) { + if ($self->valid_binding($uid, $gpgkey)) { + foreach my $subkey ($gpgkey, @{$gpgkey->subkeys()}) { + if ($self->test_capable($subkey) ) { + $self->log('verbose', "key 0x%s is capable...\n",$subkey->hex_id); + + push(@keys, $subkey); + } + } + } + } + return @keys; +} + + +sub keycomp { + my $self=shift; + my $rsakey = shift; + my $gpgkey = shift; + + if ($gpgkey->algo_num != 1) { + my $self->log('verbose', "Monkeysphere only does RSA keys. This key is algorithm #%d\n", $gpgkey->algo_num); + } else { + if ($rsakey->{exponent}->bcmp($gpgkey->pubkey_data->[1]) == 0 && + $rsakey->{modulus}->bcmp($gpgkey->pubkey_data->[0]) == 0) { + return 1; + } + } + return 0; + } + +1; diff -Nru msva-perl-0.8/debian/changelog msva-perl-0.9/debian/changelog --- msva-perl-0.8/debian/changelog 2011-02-13 19:49:54.000000000 -0500 +++ msva-perl-0.9/debian/changelog 2012-07-28 16:44:56.000000000 -0400 @@ -1,3 +1,19 @@ +msva-perl (0.9-1) unstable; urgency=low + + * New Upstream version + - tighter dependencies + - daemon crash should no longer kill X11 session + (Closes: #682353, #682518) + - cleanup for newer versions of perl and modules + (Closes: #677565, #642304) + - binds explicitly to IPv4 loopback + (Closes: #661939) + - scanning for changes and prompting to reload off by default + (Closes: #614313) + * bumped Standards-Version to 3.9.3 (no changes needed) + + -- Daniel Kahn Gillmor <dkg@fifthhorseman.net> Wed, 25 Jul 2012 13:20:08 -0400 + msva-perl (0.8-2) unstable; urgency=low * Release into unstable. diff -Nru msva-perl-0.8/debian/control msva-perl-0.9/debian/control --- msva-perl-0.8/debian/control 2011-02-13 19:44:29.000000000 -0500 +++ msva-perl-0.9/debian/control 2012-07-28 16:44:36.000000000 -0400 @@ -6,20 +6,20 @@ Build-Depends: debhelper (>= 7.0), perl -Standards-Version: 3.9.1 +Standards-Version: 3.9.3 Homepage: http://web.monkeysphere.info/ Vcs-Git: git://git.monkeysphere.info/msva-perl Package: msva-perl Architecture: all Depends: - libcrypt-x509-perl, + libcrypt-x509-perl (>= 0.50), libconvert-asn1-perl, - libnet-server-perl, + libnet-server-perl (>= 2.00), libhttp-server-simple-perl, libjson-perl, libparent-perl, - libgnupg-interface-perl (>= 0.42.02), + libgnupg-interface-perl (>= 0.43), libregexp-common-perl, libfile-homedir-perl, libconfig-general-perl, @@ -30,7 +30,7 @@ liblinux-inotify2-perl, liburi-perl, libnet-ssleay-perl, - libio-socket-ssl-perl (>=1.37), + libio-socket-ssl-perl (>= 1.37), libwww-perl Suggests: liblwp-protocol-socks-perl diff -Nru msva-perl-0.8/debian/msva-perl.install msva-perl-0.9/debian/msva-perl.install --- msva-perl-0.8/debian/msva-perl.install 2011-02-13 19:44:29.000000000 -0500 +++ msva-perl-0.9/debian/msva-perl.install 2012-07-28 16:43:54.000000000 -0400 @@ -2,10 +2,7 @@ msva-query-agent usr/bin gpgkeys_hkpms usr/lib/gnupg Net/Server/MSVA.pm usr/share/perl5/Net/Server -Crypt/Monkeysphere/MSVA.pm usr/share/perl5/Crypt/Monkeysphere -Crypt/Monkeysphere/MSVA/Logger.pm usr/share/perl5/Crypt/Monkeysphere/MSVA -Crypt/Monkeysphere/MSVA/Monitor.pm usr/share/perl5/Crypt/Monkeysphere/MSVA -Crypt/Monkeysphere/MSVA/MarginalUI.pm usr/share/perl5/Crypt/Monkeysphere/MSVA -Crypt/Monkeysphere/MSVA/Client.pm usr/share/perl5/Crypt/Monkeysphere/MSVA +Crypt/Monkeysphere/*.pm usr/share/perl5/Crypt/Monkeysphere +Crypt/Monkeysphere/MSVA/*.pm usr/share/perl5/Crypt/Monkeysphere/MSVA monkeysphere-icon.png usr/share/pixmaps debian/70monkeysphere_use-validation-agent etc/X11/Xsession.d diff -Nru msva-perl-0.8/gpgkeys_hkpms msva-perl-0.9/gpgkeys_hkpms --- msva-perl-0.8/gpgkeys_hkpms 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/gpgkeys_hkpms 2012-07-28 16:43:34.000000000 -0400 @@ -17,7 +17,7 @@ { package Crypt::Monkeysphere::MSVA::HKPMS; use POSIX; - use Crypt::Monkeysphere::MSVA::Logger; + use Crypt::Monkeysphere::Logger; use Crypt::Monkeysphere::MSVA::Client; use Regexp::Common qw /net/; use Module::Load::Conditional; @@ -117,7 +117,9 @@ if (JSON::is_bool($ret->{valid}) && ($ret->{valid} eq 1)) { $self->{logger}->log('verbose', "Monkeysphere HKPMS Certificate validation succeeded:\n %s\n", $ret->{message}); } else { - $self->{logger}->log('error', "Monkeysphere HKPMS Certificate validation failed:\n %s\n", $ret->{message}); + my $m = '[undefined]'; + $m = $ret->{message} if (defined($ret->{message})); + $self->{logger}->log('error', "Monkeysphere HKPMS Certificate validation failed:\n %s\n", $m); } } @@ -262,7 +264,7 @@ } my $self = { config => { }, args => [ ], - logger => (defined($client) ? $client->{logger} : Crypt::Monkeysphere::MSVA::Logger::->new($default_log_level)), + logger => (defined($client) ? $client->{logger} : Crypt::Monkeysphere::Logger::->new($default_log_level)), cache => { }, client => $client, actually_check => 1, diff -Nru msva-perl-0.8/msva-perl msva-perl-0.9/msva-perl --- msva-perl-0.8/msva-perl 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/msva-perl 2012-07-28 16:43:34.000000000 -0400 @@ -22,7 +22,7 @@ use Crypt::Monkeysphere::MSVA; my $server = Crypt::Monkeysphere::MSVA->new(); -$server->run(host=>'localhost', +$server->run(host=>'127.0.0.1', log_level=> $server->logger->get_log_level(), user => POSIX::geteuid(), # explicitly choose regular user and group (avoids spew) group => POSIX::getegid(), @@ -114,6 +114,14 @@ specific query if no keys are already locally known to be valid for the requested peer. Default is 'unlessvalid'. +=item MSVA_MONITOR_CHANGES + +Under graphical environments such as X11, msva-perl is capable of +monitoring for changes in its underlying code and can prompt the user +to restart the daemon when some of the underlying code changes. +Setting this environmnt variable to 'true' enables this monitoring and +prompting behavior. Default is 'false'. + =back =head1 COMMUNICATION PROTOCOL DETAILS @@ -126,11 +134,11 @@ =head1 SECURITY CONSIDERATIONS -msva-perl deliberately binds to the loopback adapter (via named lookup -of "localhost") so that remote users do not get access to the daemon. -On systems (like Linux) which report ownership of TCP sockets in -/proc/net/tcp, msva-perl will refuse access from random users (see -MSVA_ALLOWED_USERS above). +msva-perl deliberately binds to the IPv4 loopback (on 127.0.0.1) so +that remote users do not get access to the daemon. On systems (like +Linux) which report ownership of TCP sockets in /proc/net/tcp, +msva-perl will refuse access from random users (see MSVA_ALLOWED_USERS +above). =head1 SEE ALSO diff -Nru msva-perl-0.8/msva-query-agent msva-perl-0.9/msva-query-agent --- msva-perl-0.8/msva-query-agent 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/msva-query-agent 2012-07-28 16:43:34.000000000 -0400 @@ -21,16 +21,46 @@ use Crypt::Monkeysphere::MSVA::Client; +sub usage { + my $name = shift; + + printf STDERR ("Usage: %s CONTEXT PEER PKC_TYPE [PEER_TYPE] < PKC_DATA + %s CONTEXT PEER PKC_TYPE PEER_TYPE PKC_DATA + %s --version +", $name, $name, $name); +} + my $context = shift; +if ((!defined($context)) || + $context eq '--help') { + usage($0); + exit (defined($context) ? 0 : 1); +} elsif ($context eq '--version') { + my $client = Crypt::Monkeysphere::MSVA::Client->new( + socket => $ENV{MONKEYSPHERE_VALIDATION_AGENT_SOCKET}, + log_level => $ENV{MSVA_LOG_LEVEL}, + ); + my ($status,$ret) = $client->agent_info(); + $client->log('verbose', "status: %s\n", $status); + if (defined $ret) { + printf("%s", $ret->{server}); + exit 0; + } + exit 1; +} + my $peer = shift; my $pkctype = shift; my $peertype = shift; +my $pkcdata = shift; -# load raw pkc data from stdin -my $pkcdata = do { - local $/; # slurp! - <STDIN>; -}; +if (!defined $pkcdata) { + # load raw pkc data from stdin + $pkcdata = do { + local $/; # slurp! + <STDIN>; + }; +} my $client = Crypt::Monkeysphere::MSVA::Client->new( socket => $ENV{MONKEYSPHERE_VALIDATION_AGENT_SOCKET}, @@ -39,15 +69,20 @@ my ($status,$ret) = $client->query_agent($context,$peer,$peertype,$pkctype,$pkcdata); -$client->log('info', "status: %s\n", $status); +$client->log('verbose', "status: %s\n", $status); if (defined $ret) { $client->log('info', "valid: %s\n", $ret->{valid}); $client->log('info', "server: %s\n", $ret->{server}); - printf("%s\n", $ret->{message}); - exit 0 - if ($ret->{valid}); + printf("%s", $ret->{message}); + if ($ret->{valid}) { + exit 0 + } else { + exit 1; + } +} else { + printf("%s", $status); + exit 100; } -exit 1; __END__ @@ -59,6 +94,10 @@ msva-query-agent CONTEXT PEER PKC_TYPE [PEER_TYPE] < /path/to/public_key_carrier +msva-query-agent CONTEXT PEER PKC_TYPE PEER_TYPE PKC_DATA + +msva-query-agent --version + =head1 ABSTRACT msva-query-agent validates certificates for a given use by querying a @@ -72,8 +111,7 @@ for the specified purpose. The agent's return message (if any) is emitted on stdout. -The first three command-line arguments are all required, supplied in -order, as follows: +The various arguments are: =over 4 @@ -90,17 +128,21 @@ =item PKC_TYPE The format of public key carrier data provided on standard input -(e.g. 'x509der', 'x509pem', 'opensshpubkey', 'rfc4716') - -=back - -The fourth argument is optional: - -=over 4 +(e.g. 'x509der', 'x509pem', 'opensshpubkey', 'rfc4716', 'openpgp4fpr') =item PEER_TYPE -The type of peer we are inquiring about (e.g. 'client', 'server') +The type of peer we are inquiring about (e.g. 'client', 'server', +'peer'). This argument is optional and defaults will be used (based +on CONTEXT) if it is not supplied. + +=item PKC_DATA + +This is the actual public key carrier data itself. If less than five +arguments are given, then the PKC_DATA is expected on stdin. If five +arguments are given, the fifth argument is interpreted as the +PKC_DATA. This is likely only useful for supplying an OpenPGP +fingerprint with the 'openpgp4fpr' type. =back @@ -118,7 +160,7 @@ =item MONKEYSPHERE_VALIDATION_AGENT_SOCKET Socket over which to query the validation agent. If unset, the -default value is 'http://localhost:8901'. +default value is 'http://127.0.0.1:8901'. =item MSVA_LOG_LEVEL diff -Nru msva-perl-0.8/Net/Server/MSVA.pm msva-perl-0.9/Net/Server/MSVA.pm --- msva-perl-0.8/Net/Server/MSVA.pm 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/Net/Server/MSVA.pm 2012-07-28 16:43:34.000000000 -0400 @@ -19,6 +19,7 @@ { package Net::Server::MSVA; use strict; use base qw(Net::Server::Fork); + use Net::Server 2.000 (); my $msva; # guarantee initial failure -- this will be cleared after we bind @@ -37,6 +38,11 @@ $msva->pre_loop_hook($self, @_); } + sub pre_accept_hook { + my $self = shift; + $msva->pre_accept_hook($self, @_); + } + sub set_exit_status { my $self = shift; $exit_status = shift; diff -Nru msva-perl-0.8/openpgp2x509 msva-perl-0.9/openpgp2x509 --- msva-perl-0.8/openpgp2x509 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/openpgp2x509 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,348 @@ +#!/usr/bin/perl + +# Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net> +# Copyright: 2011 +# License: GPL-3+ + +# WARNING: This is very rough code! the interface WILL change +# dramatically. The only thing I can commit to keeping stable are the +# OIDs. + +# Use this code to take an OpenPGP certificate (pubkey) and emit a +# corresponding OpenPGP-validated X.509 certificate. + +# Usage: openpgp2x509 ssh://lair.fifthhorseman.net + +use strict; +use warnings; +use Crypt::X509 0.50; +use Math::BigInt; +use GnuPG::Interface 0.43; +use Regexp::Common qw /net/; +use MIME::Base64; + +my $cert = Crypt::X509::_init('Certificate'); +$cert->configure('encode' => { 'time' => 'raw' } ); +my $pgpe = Crypt::X509::_init('PGPExtension'); +$pgpe->configure('encode' => { 'time' => 'raw' } ); +my $rsapubkeyinfo = Crypt::X509::_init('RSAPubKeyInfo'); + +my $dntypes = { 'CN' => '2.5.4.3', # common name + 'emailAddress' => '1.2.840.113549.1.9.1', # e-mail address + 'C' => '2.5.4.6', # country + 'ST' => '2.5.4.8', # state + 'L' => '2.5.4.7', # locality + 'O' => '2.5.4.10', # organization + 'OU' => '2.5.4.11', # organization unit (often used as a comment) + }; + +my $algos = { + 'RSA' => '1.2.840.113549.1.1.1', + 'RSAwithMD2' => '1.2.840.113549.1.1.2', + 'RSAwithMD4' => '1.2.840.113549.1.1.3', + 'RSAwithMD5' => '1.2.840.113549.1.1.4', + 'RSAwithSHA1' => '1.2.840.113549.1.1.5', + 'OAEP' => '1.2.840.113549.1.1.6', + 'RSAwithSHA256' => '1.2.840.113549.1.1.11', + 'RSAwithSHA384' => '1.2.840.113549.1.1.12', + 'RSAwithSHA512' => '1.2.840.113549.1.1.13', + 'RSAwithSHA224' => '1.2.840.113549.1.1.14', + 'NullSignatureUseOpenPGP' => '1.3.6.1.4.1.37210.1.1', + 'OpenPGPCertificateEmbedded' => '1.3.6.1.4.1.37210.1.2', + + }; + +# NullSignatureUseOpenPGP: this X509 certificate is not +# self-verifiable. It must be verified by fetching certificate +# material from OpenPGP keyservers or from the user's private OpenPGP +# keyring. + +# The identity material and usage in the OpenPGP keyservers SHOULD be +# tested against the context in which the certificate is being used. +# If no context information is explicitly available to the +# implementation checking the certificate's validity, the +# implementation MUST assume that the context is the full set of +# possible contexts asserted by the X.509 material itself (is this +# doable?) + +# 0) certificate validity ambiguity -- X.509 certificates are +# generally considered to be entirely valid or entirely invalid. +# OpenPGP certificates can have some User IDs that are valid, and +# others that are not. If an implementation is asked to return a +# simple boolean response to a validity inquiry, without knowing +# the context in which the certificate was proposed for use, it +# MUST validate the full conjunction of all assertions made in the +# X.509 certificate itself in order to return "true". + + + +# OpenPGPCertificateEmbedded: the "signature" material in the X.509 +# certificate is actually a set of OpenPGP packets corresponding to a +# complete "transferable public key" as specified in +# https://tools.ietf.org/html/rfc4880#section-11.1 , in "raw" +# (non-ascii-armored) form. + +# If it were implemented, it would be the same as +# NullSignatureUseOpenPGP, but with the OpenPGP material transported +# in-band in addition. + +## NOTE: There is no implementation of the OpenPGPCertificateEmbedded, +## and maybe there never will be. Another approach would be to +## transmitting OpenPGP signature packets in the TLS channel itself, +## with an extension comparable to OCSP stapling. + +# the OpenPGPCertificateEmbedded concept has a few downsides: + +# 1) data duplication -- the X.509 Subject Public Key material is +# repeated (either in the primary key packet, or in one of the +# subkey packets). The X.509 Subject material (and any +# subjectAltNames) are also duplicated in the User ID packets. +# This increases the size of the certificate. It also creates +# potential inconsistencies. If the X.509 Subject Public Key +# material is not found found in the OpenPGP Transferable Public +# Key (either as a primary key or as a subkey), conforming +# implementations MUST reject the certificate. + +# 2) the requirement for out-of-band verification is not entirely +# removed, since conformant implementations may want to check the +# public keyservers for things like revocation certificates. + + + + +# this is a 5 followed by a 0. it fits into the "Parameters" section +# of an ASN.1 algorithmIdentifier object. what does this mean? +# I think it means the NULL type. +my $noparams = sprintf('%c%c', 5, 0); + +my $extensions = { 'PGPExtension' => '1.3.6.1.4.1.3401.8.1.1' }; + +my $gnupg = GnuPG::Interface::->new(); +$gnupg->options->quiet(1); +$gnupg->options->batch(1); + +sub err { + printf STDERR @_; +} + + +sub ts2Time { + my $ts = shift; + + if (!defined($ts)) { + # see https://tools.ietf.org/html/rfc5280#section-4.1.2.5 + return {'generalTime' => '99991231235959Z' }; + } else { + my ($sec,$min,$hour,$mday,$mon,$year) = gmtime($ts); + $year += 1900; + if (($year < 1950) || + ($year >= 2050)) { + return {'generalTime' => sprintf('%04d%02d%02d%02d%02d%02dZ', $year, $mon+1, $mday, $hour, $min, $sec) }; + } else { + return {'utcTime' => sprintf('%02d%02d%02d%02d%02d%02dZ', ($year%100), $mon+1, $mday, $hour, $min, $sec) }; + } + } +} + +sub ts2ISO8601 { + my $ts = shift; + $ts = time() + if (!defined($ts)); + my ($sec,$min,$hour,$mday,$mon,$year) = gmtime($ts); + $year += 1900; + return sprintf('%04d-%02d-%02dT%02d:%02d:%02dZ', $year, $mon+1, $mday, $hour, $min, $sec); +}; + +sub makeX509CertForUserID { + my $userid = shift; + my $hostname; + my $protocol; + my $emailaddress; + my $humanname; + my $subject; + my $ret = []; + + if ($userid =~ /^\s+/) { + err("We will not process User IDs with leading whitespace\n"); + return $ret; + } + if ($userid =~ /\s+$/) { + err("We will not process User IDs with trailing whitespace\n"); + return $ret; + } + if ($userid =~ /\n/) { + err("We will not process User IDs containing newlines\n"); + return $ret; + } + # FIXME: do we want to rule out any other forms of User ID? + + + if ($userid =~ /^(.*)\s+<([^><@\s]+\@$RE{net}{domain})>$/ ) { + # this is a typical/expected OpenPGP User ID. + $humanname = $1; + $emailaddress = $2; + $subject = [ + [ { + 'type' => $dntypes->{'CN'}, + 'value' => { + 'printableString' => $humanname, + }, + } ], + [ { + 'type' => $dntypes->{'emailAddress'}, + 'value' => { + 'ia5String' => $emailaddress, + }, + } ], + ]; + } elsif ($userid =~ /^(https|ssh|smtps?|ike|postgresql|imaps?|submission):\/\/($RE{net}{domain})$/) { + $protocol = $1; + $hostname = $2; + $subject = [ [ { + 'type' => $dntypes->{'CN'}, + 'value' => { + 'printableString' => $hostname + }, + } ] ]; + } else { + # what should we do here? Maybe we just assume this is a bare Human Name? + err("Assuming '%s' is a bare human name.\n", $userid); + $humanname = $userid; + } + + foreach my $gpgkey ($gnupg->get_public_keys('='.$userid)) { + my $validity = '-'; + my @sans; + foreach my $tryuid ($gpgkey->user_ids) { + if ($tryuid->as_string eq $userid) { + $validity = $tryuid->validity; + } + + if (defined($protocol) && + ($tryuid->validity =~ /^[fu]$/) && + ($tryuid =~ /^$protocol\:\/\/($RE{net}{domain})/ )) { + push(@sans, $2); + } + } + if ($validity !~ /^[fu]$/) { + err("key 0x%s only has validity %s for User ID '%s' (needs full or ultimate validity)\n", $gpgkey->fingerprint->as_hex_string, $validity, $userid); + next; + } + + # treat primary keys just like subkeys: + foreach my $subkey ($gpgkey, @{$gpgkey->subkeys}) { + if ($subkey->{algo_num} != 1) { + err("key 0x%s is algorithm %d (not RSA) -- we currently only handle RSA\n", $subkey->fingerprint->as_hex_string, $subkey->algo_num); + next; + } + # FIXME: reject/skip over revoked/expired keys. + + my $pubkey = { 'modulus' => @{$subkey->pubkey_data}[0], + 'exponent' => @{$subkey->pubkey_data}[1], + }; + my $vnotbefore = $subkey->creation_date; + + my $vnotafter = $subkey->expiration_date; + # expiration date should be the minimum of the primary key and the subkey: + if (!defined($vnotafter)) { + $vnotafter = $gpgkey->expiration_date; + } elsif (defined($gpgkey->expiration_date)) { + $vnotafter = $gpgkey->expiration_date + if ($gpgkey->expiration_date < $vnotafter); + } + + my $cnotbefore = ts2Time($vnotbefore); + my $cnotafter = ts2Time($vnotafter); + + my $pgpeval = $pgpe->encode({ 'version' => 0, 'keyCreation' => $cnotbefore }); + print $pgpe->{error} + if (!defined($pgpeval)); + + my $pubkeybitstring = $rsapubkeyinfo->encode($pubkey); + print $rsapubkeyinfo->{error} + if (!defined($pubkeybitstring)); + + my @extensions; + push(@extensions, { 'extnID' => $extensions->{'PGPExtension'}, + 'extnValue' => $pgpeval + }); + + # FIXME: base some keyUsage extensions on the type of User ID + # and on the usage flags of the key in question. + + # if 'a' is present + # if protocol =~ /^http|ssh|smtps?|postgresql|imaps?|submission$/ then set TLS server eKU + ??? + # if protocol eq 'ike' then ??? (ask micah) + # if protocol =~ /^smtps?$/ then set TLS client + ??? + # if defined($humanname) then set TLS client + ??? + + # if 'e' is present: + # ??? + + # if 's' is present: + # ??? + + # if 'c' is present: I think we should never specify CA:TRUE or + # CA:FALSE in these certificates, since (a) we do not expect + # these keys to actually be making X.509-style certifications, + # but (b) we also don't want to assert that they can't make + # any certifications whatsoever. + + + # FIXME: add subjectAltName that matches the type of information + # we believe we're working with (see the cert-id draft). + + # FIXME: if @sans is present, add them as subjectAltNames (do we + # want to do this? maybe this should be optional). + + + my $newcert = { + 'tbsCertificate' => { + 'version' => 2, # 0 == version 1, 1 == version 2, 2 == version 3 + # this is a convenient way to pass the fpr too. + 'serialNumber' => Math::BigInt->new('0x'.$subkey->fingerprint->as_hex_string), + 'subjectPublicKeyInfo' => { + 'algorithm' => { + 'parameters' => $noparams, + 'algorithm' => $algos->{'RSA'}, + }, + 'subjectPublicKey' => $pubkeybitstring, + }, + 'validity' => { + 'notAfter' => $cnotafter, + 'notBefore' => $cnotbefore, + }, + 'signature' => { # maybe we should make up our own "signature algorithm" here? + 'parameters' => $noparams, + 'algorithm' => $algos->{'NullSignatureUseOpenPGP'} + }, + 'subject' => { + 'rdnSequence' => $subject, + }, + 'issuer' => { + 'rdnSequence' => [ [ { + 'type' => $dntypes->{'OU'}, + 'value' => { 'printableString' => sprintf('Please check the OpenPGP keyservers for certification information. (certificate generated on %s)', ts2ISO8601(time())) }, + } ] ], + }, + 'extensions' => \@extensions, + }, + 'signature' => 'use OpenPGP', + 'signatureAlgorithm' => { + 'parameters' => $noparams, + 'algorithm' => $algos->{'NullSignatureUseOpenPGP'} + } + }; + + my $dd = $cert->encode($newcert); + + push(@{$ret}, $dd); + } + } + return $ret; +} + + +foreach $cert ( @{ makeX509CertForUserID($ARGV[0]) } ) { + printf("-----BEGIN CERTIFICATE-----\n%s-----END CERTIFICATE-----\n", encode_base64($cert)); +} diff -Nru msva-perl-0.8/tests/basic msva-perl-0.9/tests/basic --- msva-perl-0.8/tests/basic 2010-12-20 16:11:39.000000000 -0500 +++ msva-perl-0.9/tests/basic 2012-07-28 16:43:34.000000000 -0400 @@ -25,7 +25,7 @@ REPS=5 -CERTTYPES="x509pem x509der opensshpubkey rfc4716" +CERTTYPES="x509pem x509der opensshpubkey rfc4716 openpgp4fpr" printf "testing %d reps of simple/quick true/false:\n" "$REPS" for n in $(seq 1 "$REPS") ; do @@ -71,11 +71,21 @@ # translate X and Y's keys into OpenPGP cert for name in x y; do - PEM2OPENPGP_USAGE_FLAGS=authenticate pem2openpgp "https://${name}.example.net" < "${WORKDIR}/sec/${name}.key" | gpg --import + uid="https://${name}.example.net" + PEM2OPENPGP_USAGE_FLAGS=authenticate pem2openpgp "$uid" < "${WORKDIR}/sec/${name}.key" | gpg --import + # export fingerprint for openpgp4fpr + gpg --with-colons --fingerprint "=${uid}" | grep '^fpr:' | cut -d: -f10 > "${WORKDIR}/pkc/${name}.openpgp4fpr" done +# touch an empty openpgp4fpr file for z, who is not supposed to be in +# the monkeysphere at all, and therefore has no openpgp4fpr +touch "${WORKDIR}/pkc/z.openpgp4fpr" # and the same for the clients A and B for name in a b; do - PEM2OPENPGP_USAGE_FLAGS=authenticate pem2openpgp "${name} <${name}@example.net>" < "${WORKDIR}/sec/${name}.key" | gpg --import + uid="${name} <${name}@example.net>" + # make user keys 'a' and 's' capable + PEM2OPENPGP_USAGE_FLAGS=authenticate,sign pem2openpgp "$uid" < "${WORKDIR}/sec/${name}.key" | gpg --import + # export fingerprint for openpgp4fpr + gpg --with-colons --fingerprint "=${uid}" | grep '^fpr:' | cut -d: -f10 > "${WORKDIR}/pkc/${name}.openpgp4fpr" done runtests() { @@ -83,12 +93,14 @@ for name in x y z; do for ctype in $CERTTYPES; do ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https "${name}.example.net" "${ctype}" < "${WORKDIR}/pkc/x.${ctype}" + echo done done # A shouldn't validate as A or B: for name in a b; do for ctype in $CERTTYPES; do ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https "${name} <${name}@example.net>" "${ctype}" client < "${WORKDIR}/pkc/a.${ctype}" + echo done done @@ -99,29 +111,38 @@ echo "Testing bad data:" # it should fail if we pass it the wrong kind of data: ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https x.example.net "x509der" < "${WORKDIR}/pkc/x.x509pem" + echo ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https x.example.net "x509pem" < "${WORKDIR}/pkc/x.x509der" + echo echo "Done testing bad data." for ctype in $CERTTYPES; do # X should now validate as X "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https x.example.net "${ctype}" < "${WORKDIR}/pkc/x.${ctype}" + echo "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https 'a <a@example.net>' "${ctype}" client < "${WORKDIR}/pkc/a.${ctype}" + # also test "e-mail" context + "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent e-mail 'a <a@example.net>' "${ctype}" < "${WORKDIR}/pkc/a.${ctype}" # but X should not validate as Y or Z: for name in x y z; do ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https "${name}.example.net" "${ctype}" < "${WORKDIR}/pkc/x.${ctype}" + echo done # and A shouldn't validate as B: ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https "b <b@example.net>" "${ctype}" client < "${WORKDIR}/pkc/a.${ctype}" + echo # neither Y nor Z should validate as any of them: for src in y z; do for targ in x y z; do ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https "${targ}.example.net" "${ctype}" < "${WORKDIR}/pkc/${src}.${ctype}" + echo done done # B should also still not validate as itself: ! "${srcdir}"/test-msva msva-perl "${srcdir}"/test-msva msva-query-agent https "b <b@example.net>" "${ctype}" client < "${WORKDIR}/pkc/b.${ctype}" + echo done } diff -Nru msva-perl-0.8/unit-tests/10.keyserver/10.gnupghome.t msva-perl-0.9/unit-tests/10.keyserver/10.gnupghome.t --- msva-perl-0.8/unit-tests/10.keyserver/10.gnupghome.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/10.keyserver/10.gnupghome.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,39 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Keyserver; +use GnuPG::Interface; +use File::Temp qw(tempdir); +use strict; +use warnings; + +my $fpr='762B57BB784206AD'; +plan tests =>5; + +{ + + $ENV{HOME}='/nonexistant'; + my $ks = new Crypt::Monkeysphere::Keyserver(); + + isa_ok($ks,'Crypt::Monkeysphere::Keyserver'); + is($ks->{keyserver},$Crypt::Monkeysphere::Keyserver::default_keyserver); + +} + +my $tempdir = tempdir("/tmp/unitXXXXX", CLEANUP=> 1); +my $gnupg = new GnuPG::Interface(); +my $testks = 'hkp://keys.gnupg.net'; +$gnupg->options->hash_init(homedir=>$tempdir); + +is($gnupg->options->homedir,$tempdir); + +open GPGCONF, '>', "$tempdir/gpg.conf"; +print GPGCONF "keyserver $testks\n"; +close GPGCONF; + +my $ks=new Crypt::Monkeysphere::Keyserver(gnupg=>$gnupg, + loglevel=>'debug'); + +isa_ok($ks,'Crypt::Monkeysphere::Keyserver'); + +is($ks->{keyserver},$testks); diff -Nru msva-perl-0.8/unit-tests/10.keyserver/20.fetch_fpr.t msva-perl-0.9/unit-tests/10.keyserver/20.fetch_fpr.t --- msva-perl-0.8/unit-tests/10.keyserver/20.fetch_fpr.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/10.keyserver/20.fetch_fpr.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,29 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Keyserver; +use GnuPG::Interface; +use File::Temp qw(tempdir); + +my $keyserver= $ENV{MSTEST_KEYSERVER} || 'hkp://pool.sks-keyservers.net'; + +my $fpr='762B57BB784206AD'; +plan tests =>2; + +my $tempdir = tempdir("unitXXXXX", CLEANUP=> 1); +my $gnupg = new GnuPG::Interface(); +$gnupg->options->hash_init(homedir=>$tempdir); + +my $ks=new Crypt::Monkeysphere::Keyserver(gnupg=>$gnupg, + keyserver=>$keyserver, + loglevel=>'debug'); + +isa_ok($ks,'Crypt::Monkeysphere::Keyserver'); + +$ks->fetch_fpr($fpr); + +is(scalar($gnupg->get_public_keys('0x'.$fpr)),1); + + + + diff -Nru msva-perl-0.8/unit-tests/10.keyserver/20.fetch_uid.t msva-perl-0.9/unit-tests/10.keyserver/20.fetch_uid.t --- msva-perl-0.8/unit-tests/10.keyserver/20.fetch_uid.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/10.keyserver/20.fetch_uid.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,34 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Keyserver; +use GnuPG::Interface; +use File::Temp qw(tempdir); + +use strict; + +my $keyserver= $ENV{MSTEST_KEYSERVER} || 'hkp://pool.sks-keyservers.net'; + +my $uid='David Bremner <david@tethera.net>'; +plan tests =>2; + +my $tempdir = tempdir("unitXXXXX", CLEANUP=> 1); +my $gnupg = new GnuPG::Interface(); +$gnupg->options->hash_init(homedir=>$tempdir); + +my $ks=new Crypt::Monkeysphere::Keyserver(gnupg=>$gnupg, + keyserver=>$keyserver, + loglevel=>'debug'); + +isa_ok($ks,'Crypt::Monkeysphere::Keyserver'); + +$ks->fetch_uid($uid); + +my $count=0; +grep { $count += ($_ eq '784206AD') } + (map { $_->short_hex_id } ($gnupg->get_public_keys('='.$uid))); + +is($count,1); + + + diff -Nru msva-perl-0.8/unit-tests/20.validator/10.findall.t msva-perl-0.9/unit-tests/20.validator/10.findall.t --- msva-perl-0.8/unit-tests/20.validator/10.findall.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/20.validator/10.findall.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,38 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Validator; +use GnuPG::Interface; +use File::Temp qw(tempdir); +use Data::Dumper; + +use strict; + + +my $gpgdir = $ENV{MSTEST_GNUPGHOME}; + +unless (defined $gpgdir && -d $gpgdir){ + plan skip_all => "Preseeded GPGHOME not found"; + goto end; +} + + +my $gnupg = new GnuPG::Interface(); +$gnupg->options->hash_init(homedir=>$gpgdir); + +my $validator=new Crypt::Monkeysphere::Validator(gnupg=>$gnupg, + kspolicy=>'never', + loglevel=>'debug'); + + +plan tests =>2; + +isa_ok($validator,'Crypt::Monkeysphere::Validator'); + +my $uid='Joe Tester <joe@example.net>'; + +my @keys=$validator->findall($uid); + +ok(scalar @keys >= 3); + +end: diff -Nru msva-perl-0.8/unit-tests/20.validator/20.lookup.t msva-perl-0.9/unit-tests/20.validator/20.lookup.t --- msva-perl-0.8/unit-tests/20.validator/20.lookup.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/20.validator/20.lookup.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,34 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Validator; +use GnuPG::Interface; +use File::Temp qw(tempdir); +use Data::Dumper; + +use strict; + +my $uid='David Bremner <david@tethera.net>'; +plan tests =>2; + +my $keyserver= $ENV{MSTEST_KEYSERVER} || 'hkp://pool.sks-keyservers.net'; +my $tempdir = tempdir("unitXXXXX", CLEANUP=> 1); +my $gnupg = new GnuPG::Interface(); +$gnupg->options->hash_init(homedir=>$tempdir, + extra_args =>[ qw(--trusted-key 762B57BB784206AD)] + ); + +my $validator=new Crypt::Monkeysphere::Validator(gnupg=>$gnupg, + keyserver=>$keyserver, + loglevel=>'debug'); + +isa_ok($validator,'Crypt::Monkeysphere::Validator'); + +my $return=$validator->lookup(uid=>$uid,fpr=>'F8841978E8FA6FC65D3405155A5EA5837BD0B401'); + +print Dumper($return) if ($ENV{MSTEST_DEBUG}); + +ok(defined($return->{valid_key})); + + + diff -Nru msva-perl-0.8/unit-tests/30.fingerprints/fpr.t msva-perl-0.9/unit-tests/30.fingerprints/fpr.t --- msva-perl-0.8/unit-tests/30.fingerprints/fpr.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/30.fingerprints/fpr.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,18 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::OpenPGP; +use Data::Dumper; + +use strict; + +my $timestamp = 1299825212; +my $key = { modulus => Math::BigInt->new('0xcceb95c3c00b8a12c9de4829a803302f76549a50ee9b7ee58ee3a75ed1839d77d2f57b766e9954581d64eb5599ae98326a028831fbadad8065d63bc5a7b8d831e06d363fd9954f271fda1d746674b0ad6e8dff9fc5ddd4608bdf95760372f50897637a379079f3eb2544099a4511fc8af8e5992e15df8eac619b58a9970a3bdb'), + exponent => Math::BigInt->new('0x10001'), + }; +plan tests =>1; + +is(unpack('H*', Crypt::Monkeysphere::OpenPGP::fingerprint($key, $timestamp)),"10cc971bbbb37b9152e8e759a2882699b47c6497"); + + + diff -Nru msva-perl-0.8/unit-tests/40.keytrans/01.openssh_pack.t msva-perl-0.9/unit-tests/40.keytrans/01.openssh_pack.t --- msva-perl-0.8/unit-tests/40.keytrans/01.openssh_pack.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/40.keytrans/01.openssh_pack.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,21 @@ +# -*- perl -*- +use Test::More; + +use strict; +use warnings; + +use Crypt::Monkeysphere::Keytrans; +use MIME::Base64; +use File::Temp qw(tempdir); + +plan tests =>1; + +# this is dkg's ssh pubkey: +my $exp = Math::BigInt->new('0x10001'); +my $mod = Math::BigInt->new('0xBC358E82F23E5660301E5DBB370B42FD3EBAFE700B8E82F928798C0BA55DE5F96B984C2EA6D0BA67699E7777DA3FAF9CEA29A2030B81761603F8714E76AA2905A8DA2BAAFB19DEC147032E57585B6F4B3B1A4531942A1B3E635E1328AA50D98FA8CA7B2E64537CC26E0DE94F197A97854FE7C3B4F04F4FD96BCE8A311B2767CB0DB6E3A2D1871EE3B6B6309C0322EFCF9D3D30533575509B9A071C0C03A4B9C480D7B7E628BBF2A6714A54B5AA77F05CA7CDADD45A7C2C070DEB51F15122660B15919D7919A299E38D6BBD762C2E4BB306A0B506C7917DA3C0619E6116ADE290FDB35BA24D279212F24F097D1F70326B9207C27E536A29FEAA022504371CC01B'); +my $sshpubkey = 'AAAAB3NzaC1yc2EAAAADAQABAAABAQC8NY6C8j5WYDAeXbs3C0L9Prr+cAuOgvkoeYwLpV3l+WuYTC6m0LpnaZ53d9o/r5zqKaIDC4F2FgP4cU52qikFqNorqvsZ3sFHAy5XWFtvSzsaRTGUKhs+Y14TKKpQ2Y+oynsuZFN8wm4N6U8ZepeFT+fDtPBPT9lrzooxGydnyw2246LRhx7jtrYwnAMi78+dPTBTNXVQm5oHHAwDpLnEgNe35ii78qZxSlS1qnfwXKfNrdRafCwHDetR8VEiZgsVkZ15GaKZ441rvXYsLkuzBqC1BseRfaPAYZ5hFq3ikP2zW6JNJ5IS8k8JfR9wMmuSB8J+U2op/qoCJQQ3HMAb'; + +my $out = encode_base64(Crypt::Monkeysphere::Keytrans::openssh_rsa_pubkey_pack($mod, $exp), ''); + +is($out, $sshpubkey); + diff -Nru msva-perl-0.8/unit-tests/40.keytrans/10.openpgp2ssh.t msva-perl-0.9/unit-tests/40.keytrans/10.openpgp2ssh.t --- msva-perl-0.8/unit-tests/40.keytrans/10.openpgp2ssh.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/40.keytrans/10.openpgp2ssh.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,57 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Keytrans qw(GnuPGKey_to_OpenSSH_pub); +use GnuPG::Interface; +use File::Temp qw(tempdir); + +plan tests => 1; + +my $tempdir = tempdir("unitXXXXX", CLEANUP => 1); +my $gnupg = new GnuPG::Interface(); +$gnupg->options->hash_init(homedir=>$tempdir); + +my $openpgpdata = " +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.11 (GNU/Linux) + +mI0ETa5YiwEEALJhsHgLEokvKM+d1oAAy+oaDywLWsbqzuCCqu5h9Hu7MYxeGmTA +tg8fXatgXEBUUe+e1i1aF94kTqcqcS5M+71ce2yHNyxl7U0pGVMOPiFiRVKK8x/7 +wE2LTaPHhskc8kkKrxoJMbXmn0Oq5wn8xLkidIsVE+AyQ+HbD9C7UAnhABEBAAG0 +NXRlc3Qga2V5IChETyBOT1QgVVNFISkgPHRlc3RAZXhhbXBsZS5uZXQ+IChJTlNF +Q1VSRSEpiL4EEwECACgFAk2uWIsCGwMFCQABUYAGCwkIBwMCBhUIAgkKCwQWAgMB +Ah4BAheAAAoJEEi/A6Yee54PGcID/iL1tRDgFnNaNNdEpChbjrWcoCIQOIw2VvYH +UJY3oiKPWv/f8NMOylFLBG9pjDUd96wkimUvAKccPDwuhwMQq+KTcDPZXm8AeeUX +IMHmPE33qqvifV9dFGlIGa4a3tmGjJvjhKmNSJGJWG9wRK3C2BrJdQVF9sk2FHXd +1nlddMRV +=MxOB +-----END PGP PUBLIC KEY BLOCK----- +"; + + +my $sshdata = "AAAAB3NzaC1yc2EAAAADAQABAAAAgQCyYbB4CxKJLyjPndaAAMvqGg8sC1rG6s7ggqruYfR7uzGMXhpkwLYPH12rYFxAVFHvntYtWhfeJE6nKnEuTPu9XHtshzcsZe1NKRlTDj4hYkVSivMf+8BNi02jx4bJHPJJCq8aCTG15p9DqucJ/MS5InSLFRPgMkPh2w/Qu1AJ4Q=="; + + +my $input = IO::Handle->new(); +my $output = IO::Handle->new(); +my $handles = GnuPG::Handles->new(stdin => $input, + stdout => $output, + stderr => $output); + +my $pid = $gnupg->import_keys(handles => $handles); + +$input->write($openpgpdata); +$input->close(); +waitpid($pid, 0); + +my @keys = $gnupg->get_public_keys(); + +foreach $key (@keys) { + my $output = GnuPGKey_to_OpenSSH_pub($key); + is($sshdata, $output); +} + + + + + diff -Nru msva-perl-0.8/unit-tests/40.keytrans/20.sshfpr.t msva-perl-0.9/unit-tests/40.keytrans/20.sshfpr.t --- msva-perl-0.8/unit-tests/40.keytrans/20.sshfpr.t 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/40.keytrans/20.sshfpr.t 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,52 @@ +# -*- perl -*- +use Test::More; + +use Crypt::Monkeysphere::Keytrans qw(GnuPGKey_to_OpenSSH_fpr); +use GnuPG::Interface; +use File::Temp qw(tempdir); + +plan tests => 1; + +my $tempdir = tempdir("unitXXXXX", CLEANUP => 1); +my $gnupg = new GnuPG::Interface(); +$gnupg->options->hash_init(homedir=>$tempdir); + +my $openpgpdata = " +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.11 (GNU/Linux) + +mI0ETa5YiwEEALJhsHgLEokvKM+d1oAAy+oaDywLWsbqzuCCqu5h9Hu7MYxeGmTA +tg8fXatgXEBUUe+e1i1aF94kTqcqcS5M+71ce2yHNyxl7U0pGVMOPiFiRVKK8x/7 +wE2LTaPHhskc8kkKrxoJMbXmn0Oq5wn8xLkidIsVE+AyQ+HbD9C7UAnhABEBAAG0 +NXRlc3Qga2V5IChETyBOT1QgVVNFISkgPHRlc3RAZXhhbXBsZS5uZXQ+IChJTlNF +Q1VSRSEpiL4EEwECACgFAk2uWIsCGwMFCQABUYAGCwkIBwMCBhUIAgkKCwQWAgMB +Ah4BAheAAAoJEEi/A6Yee54PGcID/iL1tRDgFnNaNNdEpChbjrWcoCIQOIw2VvYH +UJY3oiKPWv/f8NMOylFLBG9pjDUd96wkimUvAKccPDwuhwMQq+KTcDPZXm8AeeUX +IMHmPE33qqvifV9dFGlIGa4a3tmGjJvjhKmNSJGJWG9wRK3C2BrJdQVF9sk2FHXd +1nlddMRV +=MxOB +-----END PGP PUBLIC KEY BLOCK----- +"; + + +my $sshdata = "e6:b3:db:be:c6:5d:f7:65:f2:bb:6e:06:69:36:f5:e5"; + + +my $input = IO::Handle->new(); +my $output = IO::Handle->new(); +my $handles = GnuPG::Handles->new(stdin => $input, + stdout => $output, + stderr => $output); + +my $pid = $gnupg->import_keys(handles => $handles); + +$input->write($openpgpdata); +$input->close(); +waitpid($pid, 0); + +my @keys = $gnupg->get_public_keys(); + +foreach $key (@keys) { + my $output = GnuPGKey_to_OpenSSH_fpr($key); + is($sshdata, $output); +} diff -Nru msva-perl-0.8/unit-tests/keys.txt msva-perl-0.9/unit-tests/keys.txt --- msva-perl-0.8/unit-tests/keys.txt 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/keys.txt 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,47 @@ +# For use with gpg --batch --gen-key + +Key-Type: DSA +Key-Length: 1024 +Subkey-Type: ELG-E +Subkey-Length: 1024 +Name-Real: Joe Tester +Name-Email: joe@example.net +Expire-Date: 0 + +Key-Type: RSA +Key-Length: 2048 +Key-Usage: sign +Subkey-Type: RSA +Subkey-Length: 1024 +Subkey-Usage: auth +Name-Real: Joe Tester +Name-Email: joe@example.net +Expire-Date: 0 + +Key-Type: RSA +Key-Length: 2048 +Key-Usage: sign +Subkey-Type: RSA +Subkey-Length: 1024 +Subkey-Usage: sign +Name-Real: Joe Tester +Name-Email: joe@example.net +Expire-Date: 0 + +Key-Type: RSA +Key-Length: 2048 +Key-Usage: auth +Name-Real: Joe Tester +Name-Email: joe@example.net +Expire-Date: 0 + +Key-Type: RSA +Key-Length: 2048 +Key-Usage: encrypt +Subkey-Type: RSA +Subkey-Length: 1024 +Subkey-Usage: auth +Name-Real: Joe Tester +Name-Email: jojo@example.net +Expire-Date: 0 + diff -Nru msva-perl-0.8/unit-tests/README msva-perl-0.9/unit-tests/README --- msva-perl-0.8/unit-tests/README 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/README 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,7 @@ +To run all unit tests, + + perl run-tests.pl + +to run a subset, + + perl run-tests.pl dir1 [dir2..] diff -Nru msva-perl-0.8/unit-tests/run-tests.pl msva-perl-0.9/unit-tests/run-tests.pl --- msva-perl-0.8/unit-tests/run-tests.pl 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/run-tests.pl 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,59 @@ +#!/usr/bin/perl +use strict; + +use TAP::Harness; +use File::Find; +use FindBin; +use GnuPG::Interface; +use GnuPG::Handles; +use File::Temp qw(tempdir); + +my $BINDIR; +BEGIN { $BINDIR = $FindBin::Bin; } + + +{ +# Generate Keys from template file + + my $tempdir = tempdir("/tmp/test-gnupgXXXXX", CLEANUP=> 1); + my $gnupg = new GnuPG::Interface(); + $gnupg->options->hash_init(homedir=>$tempdir,batch=>1); + + my $GPGQR=''; + if (system qw(gpg --quick-random --version) ==0) { + $GPGQR='--quick-random'; + } elsif (system qw(gpg --debug-quick-random --version) ==0) { + $GPGQR='--debug-quick-random'; + } + + print STDERR "WARNING: no quick random option found. Tests may hang!\n" + unless(scalar $GPGQR); + + my $pid = $gnupg->wrap_call( commands=>[qw(--gen-key --batch),$GPGQR], + command_args=>[$BINDIR.'/keys.txt'], + handles=>new GnuPG::Handles() ); + waitpid $pid,0; + + $ENV{MSTEST_GNUPGHOME}=$tempdir; +} + +my @dirs = scalar(@ARGV) > 0 ? @ARGV : ($BINDIR); + +my @tests; + +sub wanted { + push (@tests,$File::Find::name) if -f && m/.*\.t$/; +} + +find(\&wanted, @dirs); + +@tests=sort @tests; + +print STDERR "found ",scalar(@tests)," tests\n"; + +my $harness = TAP::Harness->new( { verbosity => 1, + lib => [ $BINDIR.'/..'] }); + +$harness->runtests(@tests); + +1; diff -Nru msva-perl-0.8/unit-tests/TODO msva-perl-0.9/unit-tests/TODO --- msva-perl-0.8/unit-tests/TODO 1969-12-31 19:00:00.000000000 -0500 +++ msva-perl-0.9/unit-tests/TODO 2012-07-28 16:43:34.000000000 -0400 @@ -0,0 +1,8 @@ + +The following are currently not tested + +- subvalid keys for a userid +- multiple subkeys from the same primary key +- multiple uids on the same key + + -- David Bremner <bremner@debian.org>, Wed, 23 Mar 2011 20:40:14 -0300
Attachment:
pgpBwVKCoBafk.pgp
Description: PGP signature