X-Git-Url: https://git.sthu.org/?a=blobdiff_plain;f=gpgsigs%2Fgpgsigs;h=50505d24251bbb6c58f62c0a25af78dc41b38c4a;hb=HEAD;hp=d89b1d57975b5b0141bad26c318ac1920464a05e;hpb=2f36dca12671524f05d1eca72dd12cf7fde3ad12;p=pgp-tools.git diff --git a/gpgsigs/gpgsigs b/gpgsigs/gpgsigs old mode 100644 new mode 100755 index d89b1d5..50505d2 --- a/gpgsigs/gpgsigs +++ b/gpgsigs/gpgsigs @@ -1,67 +1,105 @@ #!/usr/bin/perl -# Copyright (c) 2004 Uli Martens -# Copyright (c) 2004 Peter Palfrader -# Copyright (c) 2004 Christoph Berg -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. The name of the author may not be used to endorse or promote products -# derived from this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# +# $Id$ + +# See the pod documentation at the end of this file for author, +# copyright, and licence information. # # Depends: # libintl-perl (Locale::Recode) # OR libtext-iconv-perl (Text::Iconv), # OR the "recode" binary +# +# Changelog: +# 0.1 +# 0.2 2005-05-14 cb: +# * use the user's normal keyring to find signatures +# * support for multiple user keys +# * better charset conversion +# * pod documentation +# see the Debian changelog for further changes. +my $VERSION = qq$Rev$; +$ENV{PATH} .= ":/usr/share/signing-party"; use strict; use warnings; -use File::Temp qw{tempdir}; use English; use IPC::Open3; +use Getopt::Long; + + +sub version($) +{ + my ($fd) = @_; + + print $fd < + (c) 2004, 2005 Peter Palfrader + (c) 2004, 2005, 2006, 2007 Christoph Berg +EOF +} + +sub usage($$) +{ + my ($fd, $error) = @_; + + version($fd); + print $fd <] [] -my $r; -my $i; -if (eval "require Locale::Recode") { - $r = Locale::Recode->new (from => 'UTF-8', - to => 'ISO-8859-1'); -} elsif (eval "require Text::Iconv") { - $i = Text::Iconv->new("UTF-8", "ISO-8859-1"); +keyid is a long or short keyid (e.g. DE7AAF6E94C09C7F or 94C09C7F) +separate multiple keyids with ',' +-r call gpg --recv-keys before proceeding +-f convert from charset +-t convert UIDs to charset in output +--refresh regenerate UID lists on keys +--latex generate LaTeX output including photo IDs +EOF + exit $error; } -sub myrecode($) { - my ($text) = @_; - if (defined $r) { + +my ($fromcharset, $charset, $recv_keys, $refresh, $latex); +Getopt::Long::config('bundling'); +GetOptions( + '-f=s' => \$fromcharset, + '-t=s' => \$charset, + r => \$recv_keys, + refresh => \$refresh, + latex => \$latex, + help => sub { usage(*STDOUT, 0); }, + version => sub { version(*STDOUT); exit 0;}, +) or usage(*STDERR, 1); + + +# charset conversion +$fromcharset ||= "ISO-8859-1"; +$charset ||= $ENV{LC_ALL} || $ENV{LC_CTYPE} || $ENV{LANG} || "ISO-8859-1"; +$charset = "ISO-8859-1" unless $charset =~ /[\.-]/; +$charset =~ s/.*\.//; +$charset =~ s/@.*//; + + +sub myrecode($$$) { + my ($text, $from, $to) = @_; + + if (eval "require Locale::Recode") { + my $rt = Locale::Recode->new (from => $from, to => $to); + my $orig = $text; - $r->recode($text); -#printf STDERR "perl: $orig to $text\n"; + $rt->recode($text); return $text; - } elsif (defined $i) { - $text = $i->convert($text); + } elsif (eval "require Text::Iconv") { + my $it = Text::Iconv->new($from, $to); + + my $result = $it->convert($text); + warn ("Could not convert '$text'\n") unless defined $result; + return (defined $result) ? $result : $text } else { - my $pid = open3(\*WTRFH, \*RDRFH, \*ERRFH, 'recode', 'utf8..iso8859-1'); + my $pid = open3(\*WTRFH, \*RDRFH, \*ERRFH, 'recode', "utf8..$charset"); print WTRFH $text; close WTRFH; local $/ = undef; @@ -69,73 +107,123 @@ sub myrecode($) { close RDRFH; close ERRFH; waitpid $pid, 0; - - die ("'recode' failed, is it installed?\n") unless defined $result; -#printf STDERR "manual: $text to $result\n"; - return $result; - }; + warn ("'recode' failed, is it installed?\n") unless defined $result; + return (defined $result) ? $result : $text + } } -my $EXPECTED_MD5 = '90 43 B8 1B'; - -my $mykey = uc(shift @ARGV); -my $keyring = shift @ARGV; -my $keytxt = shift @ARGV; -my $outfile = shift @ARGV; +# parse options +my @mykeys = split /,/, uc(shift @ARGV); +my $keytxt = (shift @ARGV) || usage(*STDERR, 1); +my $outfile = (shift @ARGV) || '-'; -$keyring = 'ksp-lt2k4.asc' unless defined $keyring; -$keytxt = 'ksp-lt2k4.txt' unless defined $keytxt; -$outfile = 'ksp-lt2k4-annotated.txt' unless defined $outfile; +map { s/^0x//i; } @mykeys; +my %uids = map { $_ => [] } @mykeys; -if (!defined $mykey || scalar @ARGV || ($mykey !~ /^[0-9A-F]{16,16}$/ && $mykey !~ /^[0-9A-F]{8,8}$/)) { - print STDERR "Usage: $PROGRAM_NAME keyid [ [ [) { + if ( m/^pub +(?:\d+)[DR]\/([0-9A-F]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} *(.*)/ ) { + push @keys, $1; + } +} +close TXT; -my $tempdir = tempdir( "gpgsigs-XXXXX", DIR => '/tmp/', CLEANUP => 1); -$ENV{'GNUPGHOME'} = $tempdir; -print STDERR "Creating a temporary gnupghome and importing keys\n"; -system(qw{gpg --import}, $keyring); +# get all known signatures +if ($recv_keys) { + print STDERR "Requesting keys from keyserver\n"; + system "gpg --recv-keys @keys"; +} -print STDERR "Running --list-sigs, this will take a while\n"; -open SIGS, "gpg --fixed-list-mode --with-colons --list-sigs 2>/dev/null |" +print STDERR "Running --list-sigs, this will take a while "; +open SIGS, "gpg --fixed-list-mode --with-colons --list-sigs @mykeys @keys 2>/dev/null |" or die "can't get gpg listing"; -my $key; -my $uid; +my ($key, $uid, $sigs, $photocount); while () { if ( m/^pub:(?:.*?:){3,3}([0-9A-F]{16,16}):/ ) { $key = $1; + print STDERR "."; + undef $photocount; next; } - if ( m/^uid:(?:.*?:){8,8}(.*):/ ) { - $uid = $1; - $uid = myrecode($uid); + if ( m/^uid:(.):(?:.*?:){7,7}(.*):/s ) { + my $uidstatus = $1; + $uid = $2; + $uid =~ s/\\x([0-9a-f][0-9a-f])/ chr(hex($1)) /gie; + $uid = myrecode($uid, "UTF-8", $charset); + + my ($shortkey) = substr $key, -8; + # Remember non-revoked uids + next if $uidstatus eq "r"; + push @{$uids{$shortkey}}, $uid; next; } - if ( m/^sig:(?:.*?:){3,3}([0-9A-F]{8})([0-9A-F]{8}):(?:.*?:){3,3}(.*):.*?:/ ) { - $sigs->{$key}->{$uid}->{$1.$2} = $3; - $sigs->{$key}->{$uid}->{$2} = $3; + if ( m/^uat:(.)::::[^:]+::([0-9A-F]+)::\d+ (\d+)/ ) { # uat:-::::2006-08-03::27BAEAF742BD253C2F3F03B043DC1536880193C4::1 7993: + my $uidstatus = $1; + # $2 is hash of attribute data + my $size = $3 - 19; # FIXME: find a nicer way to find out picture size + $uid = "[jpeg image of size $size]"; + next if $uidstatus eq "r"; + if ($latex and not $photocount) { # call once per key + my ($shortkey) = substr $key, -8; + system "rm -f $shortkey.[1-9]*.eps"; + system "gpg --photo-viewer 'gpgsigs-eps-helper $shortkey' --list-options show-photos --list-key $key > /dev/null"; + $photocount = 1; + } + my ($shortkey) = substr $key, -8; + push @{$uids{$shortkey}}, $uid; next; } - if ( m/^uat:/ ) { - $uid = "Photo ID"; + if ( m/^sig:(?:.*?:){3,3}([0-9A-F]{8})([0-9A-F]{8}):(?:.*?:){5,5}(.*?):/ ) { + my $class = $3; + if ($class eq '10x') { + $class = 'S'; + } elsif ($class eq '11x') { + $class = '1'; + } elsif ($class eq '12x') { + $class = '2'; + } elsif ($class eq '13x') { + $class = '3'; + } else { + $class = 's'; + }; + # Handle the case where one UID was signed multiple times + # with different signature classes. + my $before = $sigs->{$key}->{$uid}->{$1.$2}; + if (defined $before) { + if ($before eq 'S' || $before eq 's') { + $sigs->{$key}->{$uid}->{$1.$2} = $class; + } elsif ($class eq 'S' || $class eq 's') { + # intentionally left blank + } elsif ($before < $class) { + $sigs->{$key}->{$uid}->{$1.$2} = $class; + }; + } else { + $sigs->{$key}->{$uid}->{$1.$2} .= $class; + }; + $sigs->{$key}->{$uid}->{$2} = $sigs->{$key}->{$uid}->{$1.$2}; next; } - next if ( m/^(rev|sub|tru):/ ); + next if ( m/^(rev|rvk|sub|tru):/ ); # revoke/revoker/subkey/trust warn "unknown value: '$_', key: ".(defined $key ? $key :'none')."\n"; -} +} close SIGS; +print STDERR "\n"; for my $k ( keys %{$sigs} ) { if ( $k =~ m/^[0-9A-F]{8}([0-9A-F]{8})$/ ) { @@ -144,63 +232,307 @@ for my $k ( keys %{$sigs} ) { } -open MD, "gpg --print-md md5 $keytxt|" or warn "can't get gpg md5"; +# read checksums +open MD, "gpg --with-colons --print-md md5 $keytxt|" or warn "can't get gpg md5\n"; my $MD5 = ; close MD; -open MD, "gpg --print-md sha1 $keytxt|" or warn "can't get gpg sha1"; +open MD, "gpg --with-colons --print-md sha1 $keytxt|" or warn "can't get gpg sha1\n"; my $SHA1 = ; close MD; +open MD, "gpg --with-colons --print-md sha256 $keytxt|" or warn "can't get gpg sha256\n"; +my $SHA256 = ; +close MD; +open MD, "gpg --with-colons --print-md ripemd160 $keytxt|" or warn "can't get gpg ripemd160\n"; +my $RIPEMD160 = ; +close MD; + +my @MD5 = split /:/, $MD5; +my @SHA1 = split /:/, $SHA1; +my @SHA256 = split /:/, $SHA256; +my @RIPEMD160 = split /:/, $RIPEMD160; +$MD5 = $MD5[2]; +$SHA1 = $SHA1[2]; +$SHA256 = $SHA256[2]; +$RIPEMD160 = $RIPEMD160[2]; + +$MD5 =~ s/(.{16})/$1 /; +$SHA1 =~ s/(.{20})/$1 /; +$SHA256 =~ s/(.{32})/$1 /; +$RIPEMD160 =~ s/(.{20})/$1 /; +$MD5 =~ s/([0-9A-Z]{2})/$1 /ig; +$SHA1 =~ s/([0-9A-Z]{4})/$1 /ig; +$SHA256 =~ s/([0-9A-Z]{4})/$1 /ig; +$RIPEMD160 =~ s/([0-9A-Z]{4})/$1 /ig; chomp $MD5; chomp $SHA1; +chomp $SHA256; +chomp $RIPEMD160; my $metatxt = quotemeta($keytxt); $MD5 =~ s/^$metatxt:\s*//; $SHA1 =~ s/^$metatxt:\s*//; +$SHA256 =~ s/^$metatxt:\s*//; +$RIPEMD160 =~ s/^$metatxt:\s*//; + -if (defined $MD5) { - warn ("md5 of $keytxt does not begin with $EXPECTED_MD5") unless ($MD5 =~ /^$EXPECTED_MD5/); -}; +# write out result +sub print_tag +{ + my ($key, $uid) = @_; + if (! defined $sigs->{$key}->{$uid}) { + warn "uid '$uid' not found on key $key\n"; + #for (keys %{ $sigs->{$key} }) { + # print STDERR "only have $_\n"; + #}; + return '(' . (' ' x @mykeys) . ')'; + } + my $r = '('; + foreach my $mykey (@mykeys) { + $r .= defined $sigs->{$key}->{$uid}->{$mykey} ? $sigs->{$key}->{$uid}->{$mykey} : ' '; + } + $r .= ')'; + return $r; +} +$key = undef; +$uid = undef; +my $line = 0; +my $keys = 0; print STDERR "Annotating $keytxt, writing into $outfile\n"; open (TXT, $keytxt) or die ("Cannot open $keytxt\n"); open (WRITE, '>'.$outfile) or die ("Cannot open $outfile for writing\n"); + +if ($latex) { + print WRITE <<'EOF'; +\documentclass{article} +\usepackage[margin=2cm]{geometry} +\usepackage{alltt} +\usepackage{graphicx} +\usepackage{grffile} +\begin{document} +\begin{alltt} +EOF +} + while () { - if (/^MD5 Checksum: __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __/ && defined $MD5) { - print WRITE "MD5 Checksum: $MD5 [ ]\n"; + $line++; + $_ = myrecode($_, $fromcharset, $charset); + if (/^MD5 Checksum:/ && defined $MD5) { + s/[_[:xdigit:]][_ [:xdigit:]]+_/$MD5/; } - elsif (/^SHA1 Checksum: ____ ____ ____ ____ ____ ____ ____ ____ ____ ____/ && defined $SHA1) { - print WRITE "SHA1 Checksum: $SHA1 [ ]\n"; - } else { + if (/^SHA1 Checksum:/ && defined $SHA1) { + s/[_[:xdigit:]][_ [:xdigit:]]+_/$SHA1/; + } + if (/^SHA256 Checksum:/ && defined $SHA256) { + s/[_[:xdigit:]][_ [:xdigit:]]+_/$SHA256/; + } + if (/^RIPEMD160 Checksum:/ && defined $RIPEMD160) { + s/[_[:xdigit:]][_ [:xdigit:]]+_/$RIPEMD160/; + } + + if ( m/^[0-9]+\s+\[ \] Fingerprint OK/ ){ + if ($latex) { + if ($keys > 0) { + print WRITE "\\end{samepage}\n"; + } + print WRITE "\\begin{samepage}\n"; + ++$keys; + } print WRITE; - }; - if ( m/^([0-9]{3}) \[ \] Fingerprint OK \[ \] ID OK$/ ) { - $_ = ; - if ( m/^pub ( 768|1024|2048|4096)[DR]\/([0-9A-F]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} (.*)/ ) { - my $l2 = $_; - my $uid = $3; - my $keyid = $2; - if ( ! defined $sigs->{$keyid}->{$uid} ) { - warn "uid '$uid' not found on key $keyid"; - }; - print WRITE ( defined $sigs->{$keyid}->{$uid}->{$mykey} ? "(S)" : "( )" ); - print WRITE " $l2"; - $_ = ; - print WRITE $_; - while () { - my $l3 = $_; - if ( m/^uid (.*)$/ ) { - print WRITE defined $sigs->{$keyid}->{$1} - ? ( defined $sigs->{$keyid}->{$1}->{$mykey} ? "(S)" : "( )" ) - : " "; - print WRITE " $l3"; - } else { - print WRITE "$l3"; - last; + next; + } + + if ( m/^pub +(?:\d+)[DR]\/([0-9A-F]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} *(.*)/ ) { + $key = $1; + $uid = $2; + #if ($uid) { # in gpg 1.2, the first uid is here + # print WRITE print_tag($key, $uid) . " $_"; + # next; + #} + print WRITE; + undef $photocount; + next; + } + + if ( m/^ *Key fingerprint/ ) { + print WRITE; + my $inc = ""; + foreach my $mykey (@mykeys) { + foreach my $myuid (@{$uids{$mykey}}) { + $inc .= defined $sigs->{$mykey}->{$myuid}->{$key} ? $sigs->{$mykey}->{$myuid}->{$key} : ' '; + } + } + print WRITE "[$inc] incoming signatures\n" if $inc =~ /\S/; + if ($refresh or $latex) { + foreach $uid (@{$uids{$key}}) { + print WRITE print_tag($key, $uid) . " $uid\n"; + if ($latex and ($uid =~ /^\[jpeg image/)) { + $photocount++; + print WRITE "\\begin{flushright}\n"; + print WRITE "\\includegraphics[height=3cm]{$key.$photocount}\n"; + print WRITE "\\end{flushright}\n"; } } - } else { - print WRITE "$_"; } + next; + + } + if ( m/^uid +(.*)$/ ) { + $uid = $1; + next if $refresh or $latex; + unless (defined $key) { + warn "key is undefined - input text is possibly malformed near line $line\n"; + next; + }; + die "bad tag from $key | $uid" unless defined (print_tag($key, $uid)); + print WRITE print_tag($key, $uid) . " $_"; + next; } + print WRITE; +} + +if ($latex && ($keys > 0)) { + print WRITE "\\end{samepage}\n"; +} + +print WRITE "Legend:\n"; +my $num_myuids = 0; +foreach my $i (0 .. @mykeys - 1) { + print WRITE '(' . ' 'x$i . 'S' . ' 'x(@mykeys-$i-1) . ") signed with $mykeys[$i] $uids{$mykeys[$i]}->[0]\n"; + $num_myuids += @{$uids{$mykeys[$i]}}; +} +my $i = 0; +foreach my $mykey (@mykeys) { + foreach my $myuid (@{$uids{$mykey}}) { + my $inc = defined $sigs->{$mykey}->{$myuid}->{$key} ? $sigs->{$mykey}->{$myuid}->{$key} : ' '; + print WRITE "[" . ' 'x$i . 'S' . ' 'x($num_myuids-$i-1) . "] has signed $mykey $myuid\n"; + $i++; + } +} +close TXT; + +if ($latex) { + print WRITE <<'EOF'; +\end{alltt} +\end{document} +EOF } -close TXT + +close WRITE; + +__END__ + +=head1 NAME + +B - annotate list of GnuPG keys with already done signatures + +=head1 SYNOPSIS + +B [I] II<[>B<,>IB<,>I<...>I<]>>I<]> F [F] + +=head1 DESCRIPTION + +B was written to assist the user in signing keys during a keysigning +party. It takes as input a file containing keys in C format +and prepends every line with a tag indicating if the user has already signed +that uid. When the file contains C lines and placeholders +(C<__ __>), the checksum is inserted. ALGO can be set to the following algorithms: +MD5 SHA1 SHA256 or RIPEMD160. + +=head1 OPTIONS + +=over + +=item B<-r> + +Call I before creating the output. + +=item B<-f> I + +Convert F from I. The default is ISO-8859-1. + +=item B<-t> I + +Convert UIDs to I. The default is derived from LC_ALL, LC_CTYPE, and +LANG, and if all these are unset, the default is ISO-8859-1. + +=item B<--refresh> + +Refresh the UID lists per key from gpg. Useful when UIDs were added or revoked +since the input text was generated. + +=item B<--latex> + +Generate LaTeX output, including photo IDs. Implies B<--refresh>. +B This writes eps files to the current directory. + +=item I + +Use this keyid (8 or 16 byte) for annotation. Multiple keyids can be separated +by a comma (B<,>). + +=item F + +Read input from F. + +=item F + +Write output to F. Default is stdout. + +=back + +=head1 EXAMPLES + +The following key signing parties are using B: + +http://www.palfrader.org/ksp-lt2k4.html + +http://www.palfrader.org/ksp-lt2k5.html + +=head1 BUGS + +B is known to change its output format quite often. This version has +been tested with gpg 1.2.5 and gpg 1.4.1. YMMV. + +=head1 SEE ALSO + +gpg(1), caff(1). + +http://pgp-tools.alioth.debian.org/ + +=head1 AUTHORS AND COPYRIGHT + +(c) 2004 Uli Martens + +(c) 2004, 2005 Peter Palfrader + +(c) 2004, 2005, 2006, 2007 Christoph Berg + +=head1 LICENSE + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. The name of the author may not be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.