Debian Bug report logs - #686054
pre-approval: msva-perl/0.8.1-1

Package: release.debian.org; Maintainer for release.debian.org is Debian Release Team <debian-release@lists.debian.org>;

Reported by: Daniel Kahn Gillmor <dkg@fifthhorseman.net>

Date: Tue, 28 Aug 2012 04:33:02 UTC

Severity: normal

Done: Julien Cristau <jcristau@debian.org>

Bug is archived. No further changes may be made.

Toggle useless messages

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Tue, 28 Aug 2012 04:33:04 GMT) Full text and rfc822 format available.

Acknowledgement sent to Daniel Kahn Gillmor <dkg@fifthhorseman.net>:
New Bug report received and forwarded. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Tue, 28 Aug 2012 04:33:04 GMT) Full text and rfc822 format available.

Message #5 received at submit@bugs.debian.org (full text, mbox):

From: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
To: submit@bugs.debian.org
Subject: unblock: msva-perl/0.9-1
Date: Tue, 28 Aug 2012 00:29:42 -0400
[Message part 1 (text/plain, inline)]
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

[msva-perl_0.8-2_0.9-1.debdiff (text/x-diff, inline)]
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
[Message part 3 (application/pgp-signature, inline)]

Added indication that 686054 affects msva-perl Request was from Daniel Kahn Gillmor <dkg@fifthhorseman.net> to control@bugs.debian.org. (Sun, 09 Sep 2012 20:21:02 GMT) Full text and rfc822 format available.

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Tue, 11 Sep 2012 14:57:03 GMT) Full text and rfc822 format available.

Acknowledgement sent to Daniel Kahn Gillmor <dkg@fifthhorseman.net>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Tue, 11 Sep 2012 14:57:03 GMT) Full text and rfc822 format available.

Message #12 received at 686054@bugs.debian.org (full text, mbox):

From: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
To: 686054@bugs.debian.org
Subject: unblock: msva-perl/0.9.1-1
Date: Tue, 11 Sep 2012 10:53:21 -0400
[Message part 1 (text/plain, inline)]
Control: retitle 686054 unblock: msva-perl/0.9.1-1

msva-perl's new upstream release 0.9.1 contains a bugfix for a
non-standard (but still supported) method of running msva-perl.

So please consider the msva-perl unblock request to refer to 0.9.1-1
instead of 0.9-1.

The relevant difference upstream between 0.9 and 0.9.1 is just:

--- a/Crypt/Monkeysphere/MSVA.pm
+++ b/Crypt/Monkeysphere/MSVA.pm
@@ -709,7 +709,7 @@
     my $self = shift;
     my $server = shift;
 
-    $self->parent_changed($server) if (getppid() != $self->{parent_pid});
+    $self->parent_changed($server) if (defined $self->{parent_pid} && getppid() != $self->{parent_pid});
   }
 
   sub parent_changed {


Attached is the full debdiff between 0.8-2 (currently in testing) and
0.9.1-1.

The rest of the analysis of the situation in #686054 remains valid.

Regards,

        --dkg

[msva-perl_0.8-2_0.9.1-1.debdiff (text/x-diff, inline)]
diff -Nru msva-perl-0.8/Changelog msva-perl-0.9.1/Changelog
--- msva-perl-0.8/Changelog	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/Changelog	2012-09-09 15:35:13.000000000 -0400
@@ -1,3 +1,43 @@
+msva-perl (0.9.1) upstream;
+
+  * Bug Fix Release:
+  * Fix error when msva-perl is run without arguments.
+  * Correct internal version number.
+
+ -- Daniel Kahn Gillmor <dkg@fifthhorseman.net>  Sun, 09 Sep 2012 15:15:34 -0400
+
+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.1/Crypt/Monkeysphere/Keyserver.pm
--- msva-perl-0.8/Crypt/Monkeysphere/Keyserver.pm	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/Keyserver.pm	2012-09-09 15:35:13.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.1/Crypt/Monkeysphere/Keytrans.pm
--- msva-perl-0.8/Crypt/Monkeysphere/Keytrans.pm	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/Keytrans.pm	2012-09-09 15:35:13.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.1/Crypt/Monkeysphere/Logger.pm
--- msva-perl-0.8/Crypt/Monkeysphere/Logger.pm	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/Logger.pm	2012-09-09 15:35:13.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.1/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.1/Crypt/Monkeysphere/MSVA/Client.pm	2012-09-09 15:35:13.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.1/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.1/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.1/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.1/Crypt/Monkeysphere/MSVA/MarginalUI.pm	2012-09-09 15:35:13.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.1/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.1/Crypt/Monkeysphere/MSVA/Monitor.pm	2012-09-09 15:35:13.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.1/Crypt/Monkeysphere/MSVA.pm
--- msva-perl-0.8/Crypt/Monkeysphere/MSVA.pm	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/MSVA.pm	2012-09-09 15:35:13.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.1';
 
   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 (defined $self->{parent_pid} && 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.1/Crypt/Monkeysphere/OpenPGP.pm
--- msva-perl-0.8/Crypt/Monkeysphere/OpenPGP.pm	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/OpenPGP.pm	2012-09-09 15:35:13.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.1/Crypt/Monkeysphere/Util.pm
--- msva-perl-0.8/Crypt/Monkeysphere/Util.pm	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/Util.pm	2012-09-09 15:35:13.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.1/Crypt/Monkeysphere/Validator.pm
--- msva-perl-0.8/Crypt/Monkeysphere/Validator.pm	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/Crypt/Monkeysphere/Validator.pm	2012-09-09 15:35:13.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.1/debian/changelog
--- msva-perl-0.8/debian/changelog	2011-02-13 19:49:54.000000000 -0500
+++ msva-perl-0.9.1/debian/changelog	2012-09-09 15:37:14.000000000 -0400
@@ -1,3 +1,26 @@
+msva-perl (0.9.1-1) unstable; urgency=low
+
+  * New Upstream version
+   - fix an error when msva-perl is launched without arguments.
+
+ -- Daniel Kahn Gillmor <dkg@fifthhorseman.net>  Sun, 09 Sep 2012 15:16:50 -0400
+
+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.1/debian/control
--- msva-perl-0.8/debian/control	2011-02-13 19:44:29.000000000 -0500
+++ msva-perl-0.9.1/debian/control	2012-09-09 15:36:08.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.1/debian/msva-perl.install
--- msva-perl-0.8/debian/msva-perl.install	2011-02-13 19:44:29.000000000 -0500
+++ msva-perl-0.9.1/debian/msva-perl.install	2012-09-09 15:36:08.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.1/gpgkeys_hkpms
--- msva-perl-0.8/gpgkeys_hkpms	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/gpgkeys_hkpms	2012-09-09 15:35:13.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.1/msva-perl
--- msva-perl-0.8/msva-perl	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/msva-perl	2012-09-09 15:35:13.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.1/msva-query-agent
--- msva-perl-0.8/msva-query-agent	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/msva-query-agent	2012-09-09 15:35:13.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.1/Net/Server/MSVA.pm
--- msva-perl-0.8/Net/Server/MSVA.pm	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/Net/Server/MSVA.pm	2012-09-09 15:35:13.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.1/openpgp2x509
--- msva-perl-0.8/openpgp2x509	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/openpgp2x509	2012-09-09 15:35:13.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.1/tests/basic
--- msva-perl-0.8/tests/basic	2010-12-20 16:11:39.000000000 -0500
+++ msva-perl-0.9.1/tests/basic	2012-09-09 15:35:13.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.1/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.1/unit-tests/10.keyserver/10.gnupghome.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/10.keyserver/20.fetch_fpr.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/10.keyserver/20.fetch_uid.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/20.validator/10.findall.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/20.validator/20.lookup.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/30.fingerprints/fpr.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/40.keytrans/01.openssh_pack.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/40.keytrans/10.openpgp2ssh.t	2012-09-09 15:35:13.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.1/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.1/unit-tests/40.keytrans/20.sshfpr.t	2012-09-09 15:35:13.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.1/unit-tests/keys.txt
--- msva-perl-0.8/unit-tests/keys.txt	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/unit-tests/keys.txt	2012-09-09 15:35:13.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.1/unit-tests/README
--- msva-perl-0.8/unit-tests/README	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/unit-tests/README	2012-09-09 15:35:13.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.1/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.1/unit-tests/run-tests.pl	2012-09-09 15:35:13.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.1/unit-tests/TODO
--- msva-perl-0.8/unit-tests/TODO	1969-12-31 19:00:00.000000000 -0500
+++ msva-perl-0.9.1/unit-tests/TODO	2012-09-09 15:35:13.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
[Message part 3 (application/pgp-signature, inline)]

Changed Bug title to 'unblock: msva-perl/0.9.1-1' from 'unblock: msva-perl/0.9-1' Request was from Daniel Kahn Gillmor <dkg@fifthhorseman.net> to 686054-submit@bugs.debian.org. (Tue, 11 Sep 2012 14:57:03 GMT) Full text and rfc822 format available.

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Sat, 29 Sep 2012 15:15:03 GMT) Full text and rfc822 format available.

Acknowledgement sent to Julien Cristau <jcristau@debian.org>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Sat, 29 Sep 2012 15:15:03 GMT) Full text and rfc822 format available.

Message #19 received at 686054@bugs.debian.org (full text, mbox):

From: Julien Cristau <jcristau@debian.org>
To: Daniel Kahn Gillmor <dkg@fifthhorseman.net>, 686054@bugs.debian.org
Subject: Re: Bug#686054: unblock: msva-perl/0.9-1
Date: Sat, 29 Sep 2012 17:10:59 +0200
[Message part 1 (text/plain, inline)]
On Tue, Aug 28, 2012 at 00:29:42 -0400, Daniel Kahn Gillmor wrote:

> 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
> 
Considering the size of the changes and the absence of any reverse
dependency I'm rather more tempted to remove msva-perl from wheezy at
this point, sorry.

Cheers,
Julien
[signature.asc (application/pgp-signature, inline)]

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Sun, 30 Sep 2012 22:57:02 GMT) Full text and rfc822 format available.

Acknowledgement sent to anarcat <anarcat@debian.org>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Sun, 30 Sep 2012 22:57:02 GMT) Full text and rfc822 format available.

Message #24 received at 686054@bugs.debian.org (full text, mbox):

From: anarcat <anarcat@debian.org>
To: Julien Cristau <jcristau@debian.org>, 686054@bugs.debian.org
Cc: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
Subject: Re: Bug#686054: unblock: msva-perl/0.9-1
Date: Sun, 30 Sep 2012 18:53:52 -0400
[Message part 1 (text/plain, inline)]
On Sat, Sep 29, 2012 at 05:10:59PM +0200, Julien Cristau wrote:
> On Tue, Aug 28, 2012 at 00:29:42 -0400, Daniel Kahn Gillmor wrote:
> 
> > 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
> 
> Considering the size of the changes and the absence of any reverse
> dependency I'm rather more tempted to remove msva-perl from wheezy at
> this point, sorry.

It seems to me this package has significant bugs, even in squeeze, as it
stands now. Sure, removing it will fix the problem from the release
teams' point of view, but I am not sure this will better serve our
users.

It would be illogical, in my opinion, to remove msva-perl from wheezy
where the buggy version also exists in squeeze, as oppose to letting the
upstream fix through. We want the upgrade from squeeze to fix bugs, not
remove packages, and I think this is what version 0.9.1 will do for our
users.

The new upstream release fixes those bugs and is fairly lightweight
compared to other gigantic software we will need to let through. I am
specifically thinking of packages like Iceweasel, Konqueror or
Evolution, which all have critical bugs filed against.

Furthermore, most of the changes in that diff are not new features but
actual bugfixes. The problems in the squeeze and wheezy versions were
not simple to fix and demanded a serious rethinking of the problem..

Please accept the unblock request, for all the Monkeysphere users, and
against the SSL Certificate Authorities cartel.

Thanks,

A.

-- 
The United States is a nation of laws:
badly written and randomly enforced.
                        - Frank Zappa
[signature.asc (application/pgp-signature, inline)]

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Sat, 02 Feb 2013 13:21:09 GMT) Full text and rfc822 format available.

Acknowledgement sent to Dominic Hargreaves <dom@earth.li>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Sat, 02 Feb 2013 13:21:09 GMT) Full text and rfc822 format available.

Message #29 received at 686054@bugs.debian.org (full text, mbox):

From: Dominic Hargreaves <dom@earth.li>
To: 682353@bugs.debian.org, 682518@bugs.debian.org, 677565@bugs.debian.org
Cc: 686054@bugs.debian.org
Subject: Re: RC bugs in msva-perl
Date: Sat, 2 Feb 2013 13:19:21 +0000
On Sat, Feb 02, 2013 at 01:12:16PM +0000, Dominic Hargreaves wrote:
> Hi Daniel,
> 
> These three RC bugs are still present in wheezy although they were
> fixed in sid in July.
> 
> Am I right in thinking that #682518 and #677565 are actually the
> same issue, and that they are fixed by upstream commits?
> 
> 7f8a57a1ebc8f5b4d63acbd8f7104c85f7dc0a19  Make msva's primary listening daemon a subprocess instead of the parent
> 06800804fa6ee5454e393e3ac9492136022b887f  newer versions of Net::Server can bind to multiple sockets
> 
> Do you have any plans to prepare a t-p-u upload to fix this set of
> wheezy RC bugs?

Argh, sorry, I somehow completely missed the unblock request.
As the release team aren't too happy about the size of the 0.9 debdiff,
what do you think about my suggestion - are these the right commits
to fix the RC bugs in question (one of them needs rebasing/porting; the
other appears to be apply cleanly)?

Cheers,
Dominic.

-- 
Dominic Hargreaves | http://www.larted.org.uk/~dom/
PGP key 5178E2A5 from the.earth.li (keyserver,web,email)



Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Sat, 02 Feb 2013 14:33:08 GMT) Full text and rfc822 format available.

Acknowledgement sent to intrigeri <intrigeri@debian.org>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Sat, 02 Feb 2013 14:33:08 GMT) Full text and rfc822 format available.

Message #34 received at 686054@bugs.debian.org (full text, mbox):

From: intrigeri <intrigeri@debian.org>
To: Dominic Hargreaves <dom@earth.li>
Cc: 677565@bugs.debian.org, 682353@bugs.debian.org, 682518@bugs.debian.org, 686054@bugs.debian.org
Subject: Re: Bug#677565: RC bugs in msva-perl
Date: Sat, 02 Feb 2013 15:31:33 +0100
Hi,

Dominic Hargreaves wrote (02 Feb 2013 13:19:21 GMT) :
> As the release team aren't too happy about the size of the 0.9 debdiff,
> what do you think about my suggestion - are these the right commits
> to fix the RC bugs in question (one of them needs rebasing/porting; the
> other appears to be apply cleanly)?

FWIW, I've asked about the same on the Monkeysphere mailing-list last
October, see dkg's answer there:
https://lists.riseup.net/www/arc/monkeysphere/2012-10/

Cheers,
-- 
  intrigeri
  | GnuPG key @ https://gaffer.ptitcanardnoir.org/intrigeri/intrigeri.asc
  | OTR fingerprint @ https://gaffer.ptitcanardnoir.org/intrigeri/otr.asc



Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Mon, 04 Feb 2013 18:33:09 GMT) Full text and rfc822 format available.

Acknowledgement sent to Dominic Hargreaves <dom@earth.li>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Mon, 04 Feb 2013 18:33:09 GMT) Full text and rfc822 format available.

Message #39 received at 686054@bugs.debian.org (full text, mbox):

From: Dominic Hargreaves <dom@earth.li>
To: intrigeri <intrigeri@debian.org>
Cc: 677565@bugs.debian.org, 682353@bugs.debian.org, 682518@bugs.debian.org, 686054@bugs.debian.org
Subject: Re: Bug#677565: RC bugs in msva-perl
Date: Mon, 4 Feb 2013 18:28:44 +0000
On Sat, Feb 02, 2013 at 03:31:33PM +0100, intrigeri wrote:
> Hi,
> 
> Dominic Hargreaves wrote (02 Feb 2013 13:19:21 GMT) :
> > As the release team aren't too happy about the size of the 0.9 debdiff,
> > what do you think about my suggestion - are these the right commits
> > to fix the RC bugs in question (one of them needs rebasing/porting; the
> > other appears to be apply cleanly)?
> 
> FWIW, I've asked about the same on the Monkeysphere mailing-list last
> October, see dkg's answer there:
> https://lists.riseup.net/www/arc/monkeysphere/2012-10/

Thanks for that - so it seems that it's not as clear cut as I hoped
which patches are needed. I don't use msva-perl myself, so I probably
won't be doing anything more on this one.

Cheers,
Dominic.

-- 
Dominic Hargreaves | http://www.larted.org.uk/~dom/
PGP key 5178E2A5 from the.earth.li (keyserver,web,email)



Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Fri, 08 Feb 2013 05:51:09 GMT) Full text and rfc822 format available.

Acknowledgement sent to Daniel Kahn Gillmor <dkg@fifthhorseman.net>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Fri, 08 Feb 2013 05:51:09 GMT) Full text and rfc822 format available.

Message #44 received at 686054@bugs.debian.org (full text, mbox):

From: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
To: Dominic Hargreaves <dom@earth.li>, 682518@bugs.debian.org
Cc: intrigeri <intrigeri@debian.org>, 677565@bugs.debian.org, 682353@bugs.debian.org, 686054@bugs.debian.org, Monkeysphere Developers <monkeysphere@lists.riseup.net>
Subject: Re: Bug#682518: Bug#677565: RC bugs in msva-perl
Date: Fri, 08 Feb 2013 00:48:55 -0500
[Message part 1 (text/plain, inline)]
On 02/04/2013 01:28 PM, Dominic Hargreaves wrote:
> On Sat, Feb 02, 2013 at 03:31:33PM +0100, intrigeri wrote:
>> FWIW, I've asked about the same on the Monkeysphere mailing-list last
>> October, see dkg's answer there:
>> https://lists.riseup.net/www/arc/monkeysphere/2012-10/

I've just pushed a proposed upstream msva-perl/0.8.1 targetted bugfix
tag to git://lair.fifthhorseman.net/~dkg/msva-perl, and a "wheezy"
branch that uses that and targets testing-proposed-updates.

The debdiff between 0.8-2 and the proposed 0.8.1-1 is attached here.  It
is smaller than the previously-submitted changeset to 0.9.1-1, but it is
still non-trivial, alas, due to having to accomodate the new Net::Server
and the change to avoid crashing X11 sessions if the agent fails for any
reason we were not able to anticipate.

I've tested 0.8.1-1 on a wheezy system and it works for me.  I plan to
upload it to t-p-u sometime tomorrow or the next day unless i hear from
anyone that it didn't work for them.

Regards,

	--dkg
[msva-perl_0.8-2_0.8.1-1.debdiff (text/plain, attachment)]
[signature.asc (application/pgp-signature, attachment)]

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Fri, 08 Feb 2013 09:18:09 GMT) Full text and rfc822 format available.

Acknowledgement sent to intrigeri <intrigeri@debian.org>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Fri, 08 Feb 2013 09:18:10 GMT) Full text and rfc822 format available.

Message #49 received at 686054@bugs.debian.org (full text, mbox):

From: intrigeri <intrigeri@debian.org>
To: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
Cc: Dominic Hargreaves <dom@earth.li>, 682518@bugs.debian.org, 677565@bugs.debian.org, 682353@bugs.debian.org, 686054@bugs.debian.org, Monkeysphere Developers <monkeysphere@lists.riseup.net>
Subject: Re: [monkeysphere] Bug#682518: Bug#677565: RC bugs in msva-perl
Date: Fri, 08 Feb 2013 10:14:43 +0100
Hi,

Daniel Kahn Gillmor wrote (08 Feb 2013 05:48:55 GMT) :
> I've just pushed a proposed upstream msva-perl/0.8.1 targetted bugfix
> tag to git://lair.fifthhorseman.net/~dkg/msva-perl, and a "wheezy"
> branch that uses that and targets testing-proposed-updates.

Excellent! Thanks a lot.

> I've tested 0.8.1-1 on a wheezy system and it works for me.

I'm going to test it during a few days.

> I plan to upload it to t-p-u sometime tomorrow or the next day
> unless i hear from anyone that it didn't work for them.

Looks like a good plan, but I suggest waiting a bit longer for:

  1. You and someone else (I volunteer) to try the proposed package
     for a few days: given t-p-u uploads have no time to be tested in
     sid, we should be extra careful about them.
  2. A pre-approval from the release team, which is required by the
     current freeze policy before uploading to t-p-u.

Cheers,
-- 
  intrigeri
  | GnuPG key @ https://gaffer.ptitcanardnoir.org/intrigeri/intrigeri.asc
  | OTR fingerprint @ https://gaffer.ptitcanardnoir.org/intrigeri/otr.asc



Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Fri, 08 Feb 2013 19:06:11 GMT) Full text and rfc822 format available.

Acknowledgement sent to Daniel Kahn Gillmor <dkg@fifthhorseman.net>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Fri, 08 Feb 2013 19:06:11 GMT) Full text and rfc822 format available.

Message #54 received at 686054@bugs.debian.org (full text, mbox):

From: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
To: intrigeri <intrigeri@debian.org>
Cc: Dominic Hargreaves <dom@earth.li>, 682518@bugs.debian.org, 677565@bugs.debian.org, 682353@bugs.debian.org, 686054@bugs.debian.org, Monkeysphere Developers <monkeysphere@lists.riseup.net>, debian-release@lists.debian.org
Subject: Re: [monkeysphere] Bug#682518: Bug#677565: RC bugs in msva-perl
Date: Fri, 08 Feb 2013 14:03:48 -0500
[Message part 1 (text/plain, inline)]
On 02/08/2013 04:14 AM, intrigeri wrote:

> I'm going to test it during a few days.

thank you very much, intrigeri!

> Looks like a good plan, but I suggest waiting a bit longer for:
> 
>   1. You and someone else (I volunteer) to try the proposed package
>      for a few days: given t-p-u uploads have no time to be tested in
>      sid, we should be extra careful about them.
>   2. A pre-approval from the release team, which is required by the
>      current freeze policy before uploading to t-p-u.

now that i have a volunteer other than myself to test it, i will wait
until i hear back from you :)

meanwhile, if anyone on the release team (cc'ed here) wants to give a
review of the proposed debdiff, i would be happy to know if msva-perl
0.8.1-1 would be acceptable for t-p-u.

	--dkg

[signature.asc (application/pgp-signature, attachment)]

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Fri, 15 Feb 2013 12:57:11 GMT) Full text and rfc822 format available.

Acknowledgement sent to intrigeri <intrigeri@debian.org>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Fri, 15 Feb 2013 12:57:11 GMT) Full text and rfc822 format available.

Message #59 received at 686054@bugs.debian.org (full text, mbox):

From: intrigeri <intrigeri@debian.org>
To: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
Cc: 686054@bugs.debian.org, Dominic Hargreaves <dom@earth.li>, 682518@bugs.debian.org, 677565@bugs.debian.org, 682353@bugs.debian.org, Monkeysphere Developers <monkeysphere@lists.riseup.net>, debian-release@lists.debian.org
Subject: Re: Bug#686054: [monkeysphere] Bug#682518: Bug#677565: RC bugs in msva-perl
Date: Fri, 15 Feb 2013 13:55:25 +0100
Hi,

Daniel Kahn Gillmor wrote (08 Feb 2013 19:03:48 GMT) :
> now that i have a volunteer other than myself to test it, i will wait
> until i hear back from you :)

I've been using the proposed msva-perl's integration into the SSH
client for a week and have not experienced any regression.

> meanwhile, if anyone on the release team (cc'ed here) wants to give a
> review of the proposed debdiff, i would be happy to know if msva-perl
> 0.8.1-1 would be acceptable for t-p-u.

Most of the cherry-picked changes make sense to me, fix important or
RC bugs, and are obviously fine for t-p-u to me.

Some of the cherry-picked changes (e.g. the one that fixes #614313)
are not explicitly documented in debian/changelog, and don't exactly
match the current freeze policy. However, I think these ones are
trivial and have already had more than six months of exposure
in unstable.

So, I recommend the release team pre-approves this t-p-u upload, and
perhaps Daniel could go through the msva-perl_debian/0.8-2..wheezy
log, and makes sure everything is mentionned in debian/changelog
(while documenting every upstream change in there would not be
necessary if this was a regular upload to sid, I think a t-p-u upload
is a bit different and should document it details every change it
brings in).

Cheers,
-- 
  intrigeri
  | GnuPG key @ https://gaffer.ptitcanardnoir.org/intrigeri/intrigeri.asc
  | OTR fingerprint @ https://gaffer.ptitcanardnoir.org/intrigeri/otr.asc



Changed Bug title to 'pre-approval: msva-perl/0.8.1-1' from 'unblock: msva-perl/0.9.1-1' Request was from intrigeri <intrigeri@debian.org> to control@bugs.debian.org. (Fri, 15 Feb 2013 13:12:03 GMT) Full text and rfc822 format available.

Information forwarded to debian-bugs-dist@lists.debian.org, Debian Release Team <debian-release@lists.debian.org>:
Bug#686054; Package release.debian.org. (Tue, 19 Feb 2013 19:57:03 GMT) Full text and rfc822 format available.

Acknowledgement sent to Jonathan Wiltshire <jmw@debian.org>:
Extra info received and forwarded to list. Copy sent to Debian Release Team <debian-release@lists.debian.org>. (Tue, 19 Feb 2013 19:57:03 GMT) Full text and rfc822 format available.

Message #66 received at 686054@bugs.debian.org (full text, mbox):

From: Jonathan Wiltshire <jmw@debian.org>
To: Daniel Kahn Gillmor <dkg@fifthhorseman.net>, 686054@bugs.debian.org
Cc: intrigeri <intrigeri@debian.org>
Subject: Re: Bug#686054: [monkeysphere] Bug#682518: Bug#677565: RC bugs in msva-perl
Date: Tue, 19 Feb 2013 19:54:01 +0000
[Message part 1 (text/plain, inline)]
Control: tag -1 + pending

On Fri, Feb 08, 2013 at 02:03:48PM -0500, Daniel Kahn Gillmor wrote:
> On 02/08/2013 04:14 AM, intrigeri wrote:
> 
> > I'm going to test it during a few days.
> 
> thank you very much, intrigeri!
> 
> > Looks like a good plan, but I suggest waiting a bit longer for:
> > 
> >   1. You and someone else (I volunteer) to try the proposed package
> >      for a few days: given t-p-u uploads have no time to be tested in
> >      sid, we should be extra careful about them.
> >   2. A pre-approval from the release team, which is required by the
> >      current freeze policy before uploading to t-p-u.
> 
> now that i have a volunteer other than myself to test it, i will wait
> until i hear back from you :)
> 
> meanwhile, if anyone on the release team (cc'ed here) wants to give a
> review of the proposed debdiff, i would be happy to know if msva-perl
> 0.8.1-1 would be acceptable for t-p-u.

Please go ahead.

Thanks,


-- 
Jonathan Wiltshire                                      jmw@debian.org
Debian Developer                         http://people.debian.org/~jmw

4096R: 0xD3524C51 / 0A55 B7C5 1223 3942 86EC  74C3 5394 479D D352 4C51

<directhex> i have six years of solaris sysadmin experience, from
            8->10. i am well qualified to say it is made from bonghits
			layered on top of bonghits
[signature.asc (application/pgp-signature, inline)]

Added tag(s) pending. Request was from Jonathan Wiltshire <jmw@debian.org> to 686054-submit@bugs.debian.org. (Tue, 19 Feb 2013 19:57:03 GMT) Full text and rfc822 format available.

Reply sent to Julien Cristau <jcristau@debian.org>:
You have taken responsibility. (Sat, 23 Feb 2013 10:03:03 GMT) Full text and rfc822 format available.

Notification sent to Daniel Kahn Gillmor <dkg@fifthhorseman.net>:
Bug acknowledged by developer. (Sat, 23 Feb 2013 10:03:03 GMT) Full text and rfc822 format available.

Message #73 received at 686054-done@bugs.debian.org (full text, mbox):

From: Julien Cristau <jcristau@debian.org>
To: Jonathan Wiltshire <jmw@debian.org>, 686054-done@bugs.debian.org
Cc: Daniel Kahn Gillmor <dkg@fifthhorseman.net>, intrigeri <intrigeri@debian.org>
Subject: Re: Bug#686054: [monkeysphere] Bug#682518: Bug#677565: RC bugs in msva-perl
Date: Sat, 23 Feb 2013 10:59:30 +0100
[Message part 1 (text/plain, inline)]
On Tue, Feb 19, 2013 at 19:54:01 +0000, Jonathan Wiltshire wrote:

> On Fri, Feb 08, 2013 at 02:03:48PM -0500, Daniel Kahn Gillmor wrote:
> > meanwhile, if anyone on the release team (cc'ed here) wants to give a
> > review of the proposed debdiff, i would be happy to know if msva-perl
> > 0.8.1-1 would be acceptable for t-p-u.
> 
> Please go ahead.
> 
Unblocked.

Cheers,
Julien
[signature.asc (application/pgp-signature, inline)]

Bug archived. Request was from Debbugs Internal Request <owner@bugs.debian.org> to internal_control@bugs.debian.org. (Sun, 24 Mar 2013 07:30:37 GMT) Full text and rfc822 format available.

Send a report that this bug log contains spam.


Debian bug tracking system administrator <owner@bugs.debian.org>. Last modified: Sat Apr 19 14:39:13 2014; Machine Name: beach.debian.org

Debian Bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.