5 # See the pod documentation at the end of this file for author,
6 # copyright, and licence information.
9 # libintl-perl (Locale::Recode)
10 # OR libtext-iconv-perl (Text::Iconv),
11 # OR the "recode" binary
16 # * use the user's normal keyring to find signatures
17 # * support for multiple user keys
18 # * better charset conversion
20 # see the Debian changelog for further changes.
22 my $VERSION = qq$Rev$;
36 gpgsigs $VERSION- http://pgp-tools.alioth.debian.org/
37 (c) 2004 Uli Martens <uli\@youam.net>
38 (c) 2004, 2005 Peter Palfrader <peter\@palfrader.org>
39 (c) 2004, 2005, 2006, 2007 Christoph Berg <cb\@df7cb.de>
45 my ($fd, $error) = @_;
50 Usage: $PROGRAM_NAME [-r] [-t <charset>] <keyid> <keytxt> [<outfile>]
52 keyid is a long or short keyid (e.g. DE7AAF6E94C09C7F or 94C09C7F)
53 separate multiple keyids with ','
54 -r call gpg --recv-keys before proceeding
55 -f <charset> convert <keytxt> from charset
56 -t <charset> convert UIDs to charset in output
62 my ($fromcharset, $charset, $recv_keys, $refresh, $latex);
63 Getopt
::Long
::config
('bundling');
65 '-f=s' => \
$fromcharset,
70 help
=> sub { usage
(*STDOUT
, 0); },
71 version
=> sub { version
(*STDOUT
); exit 0;},
72 ) or usage
(*STDERR
, 1);
76 $fromcharset ||= "ISO-8859-1";
77 $charset ||= $ENV{LC_ALL
} || $ENV{LC_CTYPE
} || $ENV{LANG
} || "ISO-8859-1";
78 $charset = "ISO-8859-1" unless $charset =~ /[\.-]/;
84 my ($text, $from, $to) = @_;
86 if (eval "require Locale::Recode") {
87 my $rt = Locale
::Recode
->new (from
=> $from, to
=> $to);
92 } elsif (eval "require Text::Iconv") {
93 my $it = Text
::Iconv
->new($from, $to);
95 my $result = $it->convert($text);
96 warn ("Could not convert '$text'\n") unless defined $result;
97 return (defined $result) ?
$result : $text
99 my $pid = open3
(\
*WTRFH
, \
*RDRFH
, \
*ERRFH
, 'recode', "utf8..$charset");
103 my $result = <RDRFH
>;
107 warn ("'recode' failed, is it installed?\n") unless defined $result;
108 return (defined $result) ?
$result : $text
114 my @mykeys = split /,/, uc(shift @ARGV);
115 my $keytxt = (shift @ARGV) || usage
(*STDERR
, 1);
116 my $outfile = (shift @ARGV) || '-';
118 map { s/^0x//i; } @mykeys;
119 my %uids = map { $_ => [] } @mykeys;
121 if (!@mykeys || scalar @ARGV) {
124 foreach my $falsekey (grep { $_ !~ /^([0-9A-F]{16,16}|[0-9A-F]{8,8})$/ } @mykeys) {
125 print STDERR
"Invalid keyid $falsekey given\n";
129 -r
$keytxt or die ("$keytxt does not exist\n");
132 # get list of keys in file
134 open (TXT
, $keytxt) or die ("Cannot open $keytxt\n");
136 if ( m/^pub +(?:\d+)[DR]\/([0-9A
-F
]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} *(.*)/ ) {
143 # get all known signatures
145 print STDERR
"Requesting keys from keyserver\n";
146 system "gpg --recv-keys @keys";
149 print STDERR
"Running --list-sigs, this will take a while ";
150 open SIGS
, "gpg --fixed-list-mode --with-colons --list-sigs @mykeys @keys 2>/dev/null |"
151 or die "can't get gpg listing";
153 my ($key, $uid, $sigs, $uidstatus);
155 if ( m/^pub:(?:.*?:){3,3}([0-9A-F]{16,16}):/ ) {
160 if ( m/^uid:(.):(?:.*?:){7,7}(.*):/s ) {
163 $uid =~ s/\\x([0-9a-f][0-9a-f])/ chr(hex($1)) /gie;
164 $uid = myrecode
($uid, "UTF-8", $charset);
166 my ($shortkey) = substr $key, -8;
167 # Remember non-revoked uids
168 if ($uidstatus ne "r") {
169 push @
{$uids{$shortkey}}, $uid;
174 if ( m/^uat:(.):/ ) { # uat:-::::2006-08-03::27BAEAF742BD253C2F3F03B043DC1536880193C4::1 7993:
176 next if $uidstatus ne "-";
177 system "gpg --photo-viewer 'convert - %k.eps' --list-options show-photos --list-key $key";
179 my ($shortkey) = substr $key, -8;
180 push @
{$uids{$shortkey}}, $uid;
183 if ( m/^sig:(?:.*?:){3,3}([0-9A-F]{8})([0-9A-F]{8}):(?:.*?:){5,5}(.*?):/ ) {
185 if ($class eq '10x') {
187 } elsif ($class eq '11x') {
189 } elsif ($class eq '12x') {
191 } elsif ($class eq '13x') {
196 # Handle the case where one UID was signed multiple times
197 # with different signature classes.
198 my $before = $sigs->{$key}->{$uid}->{$1.$2};
199 if (defined $before) {
200 if ($before eq 'S' || $before eq 's') {
201 $sigs->{$key}->{$uid}->{$1.$2} = $class;
202 } elsif ($class eq 'S' || $class eq 's') {
203 # intentionally left blank
204 } elsif ($before < $class) {
205 $sigs->{$key}->{$uid}->{$1.$2} = $class;
208 $sigs->{$key}->{$uid}->{$1.$2} .= $class;
210 $sigs->{$key}->{$uid}->{$2} = $sigs->{$key}->{$uid}->{$1.$2};
213 next if ( m/^(rev|rvk|sub|tru):/ ); # revoke/revoker/subkey/trust
214 warn "unknown value: '$_', key: ".(defined $key ?
$key :'none')."\n";
219 for my $k ( keys %{$sigs} ) {
220 if ( $k =~ m/^[0-9A-F]{8}([0-9A-F]{8})$/ ) {
221 $sigs->{$1} = $sigs->{$k};
227 open MD
, "gpg --with-colons --print-md md5 $keytxt|" or warn "can't get gpg md5\n";
230 open MD
, "gpg --with-colons --print-md sha1 $keytxt|" or warn "can't get gpg sha1\n";
233 open MD
, "gpg --with-colons --print-md sha256 $keytxt|" or warn "can't get gpg sha256\n";
237 my @MD5 = split /:/, $MD5;
238 my @SHA1 = split /:/, $SHA1;
239 my @SHA256 = split /:/, $SHA256;
242 $SHA256 = $SHA256[2];
244 $MD5 =~ s/(.{16})/$1 /;
245 $SHA1 =~ s/(.{20})/$1 /;
246 $SHA256 =~ s/(.{32})/$1 /;
247 $MD5 =~ s/([0-9A-Z]{2})/$1 /ig;
248 $SHA1 =~ s/([0-9A-Z]{4})/$1 /ig;
249 $SHA256 =~ s/([0-9A-Z]{4})/$1 /ig;
254 my $metatxt = quotemeta($keytxt);
255 $MD5 =~ s/^$metatxt:\s*//;
256 $SHA1 =~ s/^$metatxt:\s*//;
257 $SHA256 =~ s/^$metatxt:\s*//;
263 my ($key, $uid) = @_;
264 if (! defined $sigs->{$key}->{$uid}) {
265 warn "uid '$uid' not found on key $key\n";
266 #for (keys %{ $sigs->{$key} }) {
267 # print STDERR "only have $_\n";
269 return '(' . (' ' x
@mykeys) . ')';
272 foreach my $mykey (@mykeys) {
273 $r .= defined $sigs->{$key}->{$uid}->{$mykey} ?
$sigs->{$key}->{$uid}->{$mykey} : ' ';
282 print STDERR
"Annotating $keytxt, writing into $outfile\n";
283 open (TXT
, $keytxt) or die ("Cannot open $keytxt\n");
284 open (WRITE
, '>'.$outfile) or die ("Cannot open $outfile for writing\n");
288 \documentclass{article}
289 \usepackage[margin=2cm]{geometry}
291 \usepackage{graphicx}
299 $_ = myrecode
($_, $fromcharset, $charset);
300 if (/^MD5 Checksum:/ && defined $MD5) {
301 s/[_[:xdigit:]][_ [:xdigit:]]+_/$MD5/;
303 if (/^SHA1 Checksum:/ && defined $SHA1) {
304 s/[_[:xdigit:]][_ [:xdigit:]]+_/$SHA1/;
306 if (/^SHA256 Checksum:/ && defined $SHA256) {
307 s/[_[:xdigit:]][_ [:xdigit:]]+_/$SHA256/;
309 if ( m/^pub +(?:\d+)[DR]\/([0-9A
-F
]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} *(.*)/ ) {
312 #if ($uid) { # in gpg 1.2, the first uid is here
313 # print WRITE print_tag($key, $uid) . " $_";
320 if ( m/^ *Key fingerprint/ ) {
323 foreach my $mykey (@mykeys) {
324 foreach my $myuid (@
{$uids{$mykey}}) {
325 $inc .= defined $sigs->{$mykey}->{$myuid}->{$key} ?
$sigs->{$mykey}->{$myuid}->{$key} : ' ';
328 print WRITE
"[$inc] incoming signatures\n" if $inc =~ /\S/;
329 if ($refresh or $latex) {
330 foreach $uid (@
{$uids{$key}}) {
331 print WRITE print_tag
($key, $uid) . " $uid\n";
332 if ($latex and ($uid eq "Photo ID")) {
333 print WRITE
"\\begin{flushright}\n";
334 print WRITE
"\\includegraphics[height=3cm]{$key.eps}\n";
335 print WRITE
"\\end{flushright}\n";
342 if ( m/^uid +(.*)$/ ) {
344 next if $refresh or $latex;
345 unless (defined $key) {
346 warn "key is undefined - input text is possibly malformed near line $line\n";
349 die "bad tag from $key | $uid" unless defined (print_tag
($key, $uid));
350 print WRITE print_tag
($key, $uid) . " $_";
356 print WRITE
"Legend:\n";
358 foreach my $i (0 .. @mykeys - 1) {
359 print WRITE
'(' . ' 'x
$i . 'S' . ' 'x
(@mykeys-$i-1) . ") signed with $mykeys[$i] $uids{$mykeys[$i]}->[0]\n";
360 $num_myuids += @
{$uids{$mykeys[$i]}};
363 foreach my $mykey (@mykeys) {
364 foreach my $myuid (@
{$uids{$mykey}}) {
365 my $inc = defined $sigs->{$mykey}->{$myuid}->{$key} ?
$sigs->{$mykey}->{$myuid}->{$key} : ' ';
366 print WRITE
"[" . ' 'x
$i . 'S' . ' 'x
($num_myuids-$i-1) . "] has signed $mykey $myuid\n";
385 B<gpgsigs> - annotate list of GnuPG keys with already done signatures
389 B<gpgsigs> [-r] [-f I<charset>] [-t I<charset>] I<keyid>I<[>B<,>I<keyidI<[>B<,>I<...>I<]>>I<]> F<keytxt> [F<outfile>]
393 B<gpgsigs> was written to assist the user in signing keys during a keysigning
394 party. It takes as input a file containing keys in C<gpg --list-keys> format
395 and prepends every line with a tag indicating if the user has already signed
396 that uid. When the file contains C<MD5 Checksum:> or C<SHA1 Checksum:> lines
397 and placeholders (C<__ __>), the checksum is inserted.
405 Call I<gpg --recv-keys> before creating the output.
409 Convert F<keytxt> from I<charset>. The default is ISO-8859-1.
413 Convert UIDs to I<charset>. The default is derived from LC_ALL, LC_CTYPE, and
414 LANG, and if all these are unset, the default is ISO-8859-1.
418 Use this keyid (8 or 16 byte) for annotation. Multiple keyids can be separated
423 Read input from F<keytxt>.
427 Write output to F<outfile>. Default is stdout.
433 The following key signing parties are using B<gpgsigs>:
435 http://www.palfrader.org/ksp-lt2k4.html
437 http://www.palfrader.org/ksp-lt2k5.html
441 B<GnuPG> is known to change its output format quite often. This version has
442 been tested with gpg 1.2.5 and gpg 1.4.1. YMMV.
448 http://pgp-tools.alioth.debian.org/
450 =head1 AUTHORS AND COPYRIGHT
452 (c) 2004 Uli Martens <uli@youam.net>
454 (c) 2004, 2005 Peter Palfrader <peter@palfrader.org>
456 (c) 2004, 2005, 2006, 2007 Christoph Berg <cb@df7cb.de>
462 Redistribution and use in source and binary forms, with or without
463 modification, are permitted provided that the following conditions
466 1. Redistributions of source code must retain the above copyright
467 notice, this list of conditions and the following disclaimer.
469 2. Redistributions in binary form must reproduce the above copyright
470 notice, this list of conditions and the following disclaimer in the
471 documentation and/or other materials provided with the distribution.
473 3. The name of the author may not be used to endorse or promote products
474 derived from this software without specific prior written permission.
476 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
477 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
478 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
479 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
480 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
481 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
482 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
483 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
484 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
485 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.