[Pgp-tools-commit] r722 - in trunk: . gpg-key2latex gpg-key2ps

Guilhem Moulin guilhem-guest at moszumanska.debian.org
Thu Nov 6 21:51:23 UTC 2014


Author: guilhem-guest
Date: 2014-11-06 21:51:23 +0000 (Thu, 06 Nov 2014)
New Revision: 722

Added:
   trunk/gpg-key2latex/
   trunk/gpg-key2latex/gpg-key2latex
Modified:
   trunk/gpg-key2ps/gpg-key2ps
Log:
gpg-key2latex: new script to generate a LaTeX file with fingerprint paper slips.

Added: trunk/gpg-key2latex/gpg-key2latex
===================================================================
--- trunk/gpg-key2latex/gpg-key2latex	                        (rev 0)
+++ trunk/gpg-key2latex/gpg-key2latex	2014-11-06 21:51:23 UTC (rev 722)
@@ -0,0 +1,504 @@
+#!/usr/bin/perl
+
+# gpg-key2latex -- Generate a LaTeX file for fingerprint slips.
+# $Id$
+#
+# Copyright (c) 2014 Guilhem Moulin <guilhem at guilhem.org>
+#
+# 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/>.
+
+use warnings;
+use strict;
+
+my $REVISION = '$Rev$';
+my ($REVISION_NUMBER) = $REVISION =~ /(\d+)/;
+our $VERSION = '0.0.0.'.($REVISION_NUMBER // 'unknown');
+
+use Encode ();
+use File::Temp ();
+use I18N::Langinfo 'langinfo';
+use IO::Handle ();
+use IO::Select ();
+use POSIX qw/dup2 strftime/;
+
+use Pod::Usage 'pod2usage';
+use Getopt::Long qw/:config posix_default no_ignore_case gnu_compat
+                            bundling auto_help auto_version/;
+use GnuPG::Interface ();
+
+my %options;
+GetOptions(\%options, qw/paper-size|p=s show-subkeys|s show-photo show-qrcode attr-height=i/) or pod2usage(2);
+pod2usage(2) unless @ARGV;
+my $LOCALE = Encode::find_encoding(langinfo(I18N::Langinfo::CODESET()));
+
+chomp ($options{'paper-size'} = `paperconf` || 'a4')
+    unless defined $options{'paper-size'};
+$options{'paper-size'} =~ y/[A-Z]/[a-z]/;
+
+my (@KEYIDS, @KEYS);
+
+# Get the list of all matching keys.
+{
+    my $gpg = GnuPG::Interface::->new();
+    $gpg->options->hash_init( 'extra_args' => [ qw/--fingerprint
+                                                   --fixed-list-mode
+                                                   --no-auto-check-trustdb --with-colons/ ]
+                            , 'meta_interactive' => 0 );
+    my $stdout = IO::Handle::->new();
+    my $handles = GnuPG::Handles::->new( stdout => $stdout );
+    my $pid = $gpg->list_public_keys( handles => $handles, command_args => \@ARGV );
+    while (<$stdout>) {
+        push @KEYIDS, $1 if /^fpr:(?:[^:]*:){8}([0-9A-F]{40})(?::.*)?$/;
+    }
+    waitpid $pid, 0;
+    close $stdout;
+}
+
+# Read each key independently
+foreach my $keyid (@KEYIDS) {
+    my $photos = $options{'show-photo'} ? File::Temp::->new(TMPDIR => 1) : '/dev/null';
+    my $gpg = GnuPG::Interface::->new();
+    # we need --attribute-{fd,file} and --status-{fd,file} to get the
+    # correct attribute size
+    $gpg->options->hash_init( 'extra_args' => [ '--attribute-file', $photos,
+                                                qw/--fingerprint --fingerprint
+                                                   --fixed-list-mode
+                                                   --no-auto-check-trustdb --with-colons/ ]
+                            , 'meta_interactive' => 0 );
+    
+    my $stdout = IO::Handle::->new();
+    my $status = IO::Handle::->new();
+    my $handles = GnuPG::Handles::->new( stdout => $stdout, status => $status );
+    my $pid = $gpg->list_public_keys( handles => $handles, command_args => [ $keyid ] );
+
+    $_->blocking(0) for ($stdout, $status);
+    my $output = IO::Select::->new();
+    $output->add($stdout, $status);
+
+    my %key;
+    my ($uid, $sub); # current context
+
+    my ($oldstdout, $oldstatus) = ('', '');
+    while ($output->count() > 0) {
+        foreach my $fd (@{(IO::Select::select($output))[0]}) { # reader
+            if ($fd->eof) {
+                $output->remove($fd);
+                close $fd;
+                next;
+            }
+            if ($fd == $stdout) {
+                while (<$fd>) {
+                    if ($oldstdout) { # prepend unfinished output
+                        $_ = $oldstdout . $_;
+                        $oldstdout = '';
+                    }
+                    if (!/\n\z/) { # there is more coming
+                        $oldstdout = $_;
+                        next;
+                    }
+                    chomp;
+                    if (/^pub:([^:]+):([^:]*):([^:]*):([0-9A-F]{16}):(\d+):(\d*):(?:[^:]*:){4}([^:]*)/) {
+                        if ($1 =~ /[eir]/ or $7 =~ /D/ ) {
+                            warn "Ignoring unusable key $4.\n";
+                            last;
+                        }
+                        $key{length} = $2;
+                        $key{algo} = $3;
+                        $key{creation} = $5;
+                        $key{expiration} = $6 if $6 ne '';
+                        $key{flags} = $7;
+                        next;
+                    }
+                    if (/^(uid|uat):([^:]+):(?:[^:]*:){5}([0-9A-F]{40}):[^:]*:([^:]+)/) {
+                        undef $uid;
+                        next if $2 =~ /[er]/;
+    
+                        $uid = $3; # use the SHA1 to have proper distinction between UIDs/UATs
+                        if ($1 eq 'uid') {
+                            my $text = $4;
+                            $text =~ s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge;
+                            # --with-colons always outputs UTF-8
+                            push @{$key{uids}}, { type => 'uid', text => Encode::decode_utf8($text) };
+                        }
+                        next;
+                    }
+                    if (/^sub:([^:]+):([^:]*):([^:]*):([0-9A-F]{16}):(\d+):(\d*):(?:[^:]*:){4}([^:]*)/) {
+                        undef $uid;
+                        undef $sub;
+                        next if $1 =~ /[eir]/ or $7 =~ /D/; # ignore unsable subkey
+                        $sub = {};
+                        $sub->{length} = $2;
+                        $sub->{algo} = $3;
+                        $sub->{creation} = $5;
+                        $sub->{expiration} = $6 if $6 ne '';
+                        $sub->{flags} = $7;
+                        next;
+                    }
+                    if (/^fpr:(?:[^:]*:){8}([0-9A-F]{40})(?::.*)?$/) {
+                        if (defined $sub) {
+                            $sub->{fpr} = $1;
+                            push @{$key{sub}}, $sub;
+                            undef $sub;
+                        }
+                        else {
+                            $key{fpr} = $1;
+                        }
+                        next;
+                    }
+                    if (!/^(?:rvk|tru|spk):/) { # revoke/revoker/trust/fpr
+                        warn "Unknown value: '$_'\n";
+                    }
+                }
+            }
+            elsif ($fd == $status) {
+                while (<$fd>) {
+                    if ($oldstatus) { # prepend unfinished output
+                        $_ = $oldstatus . $_;
+                        $oldstatus = '';
+                    }
+                    if (!/\n\z/) { # there is more coming
+                        $oldstatus = $_;
+                        next;
+                    }
+                    chomp;
+                    # see /usr/share/doc/gnupg2/DETAILS.gz
+                    if (/^\[GNUPG:\] ATTRIBUTE [0-9A-F]{24}([0-9A-F]{16}) (\d+) 1 1 1 \d+ \d+ (\d+)$/) {
+                        push @{$key{uats}}, {size => $2, revoked => $3 & 0x02};
+                        next;
+                    }
+                    if (!/^\[GNUPG:\] KEYEXPIRED \d+$/) {
+                        warn "Unknown value: '$_'";
+                    }
+                }
+            }
+        }
+    }
+    warn "Parsing gpg's output went wrong.\n" if $oldstdout or $oldstatus;
+    waitpid $pid, 0;
+    close $_ for ($stdout, $status);
+
+    if ($options{'show-photo'}) {
+        open my $fd, '<:raw', $photos or die "Couldn't open: $!";
+        # get photo sizes and split $photos
+        foreach (@{$key{uats}}) {
+            my $chunk;
+            my $got = read $fd, $chunk, $_->{size} or die "Couldn't read: $!";
+            warn "Read $_->{size} bytes but got $got bytes.\n" if $got != $_->{size};
+        
+            # take the first non-revoked attribute
+            unless ($_->{revoked}) {
+                $key{photo} = substr($key{fpr},-16).'.jpg';
+                open my $fd2, '>:raw', $key{photo} or die "Couldn't open: $!";
+                print $fd2 (substr $chunk, 16);
+                close $fd2;
+                last;
+            }
+        }
+        close $fd;
+    }
+
+    if ($options{'show-qrcode'}) {
+        $key{qrcode} = substr($key{fpr},-16).'-qrcode.pdf';
+        pipe my ($rfd, $wfd) or die "Can't pipe: $!";
+        if (my $pid = fork) {
+            close $wfd or die "Can't close: $!";
+            dup2 (fileno $rfd, 0);
+            close $rfd or die "Can't close: $!";
+            system qw/epstopdf -f -o/, $key{qrcode};
+            die "epstopdf exited with value $?.\n" if 0 < $? and $? <= 2;
+            waitpid $pid, 0;
+        }
+        else {
+            close $rfd or die "Can't close: $!";
+            dup2 (fileno $wfd, 1);
+            close $wfd or die "Can't close: $!";
+            exec qw/qrencode -i -m0 -d300 -tEPS -o -/, "OPENPGP4FPR:$key{fpr}" or die "Can't exec: $!";
+        }
+    }
+
+    push @KEYS, \%key;
+}
+
+print "\\documentclass[landscape,$options{'paper-size'}paper]{article}\n";
+print << 'EOF'
+\usepackage{fancyvrb}
+\usepackage[export]{adjustbox}
+\usepackage{graphicx,calc}
+\usepackage{ifluatex,ifxetex}
+\usepackage[margin=.5cm,centering]{geometry}
+\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0
+  \usepackage[utf8x]{inputenc}
+\else
+  \usepackage[log-declarations=false]{xparse}
+  \usepackage{fontspec}
+  \setmonofont{Droid Sans Mono}
+  \ifxetex
+    \usepackage[quiet]{xeCJK}
+    \CJKfontspec{Droid Sans Fallback}
+  \fi
+\fi
+
+EOF
+;
+
+
+sub pubkey_string ($$) {
+    my ($type,$key) = @_;
+    my $str = $type.'  ';
+    # See 'pubkey_string' in gnupg's source code (g10/keyid.c)
+    $str .= $key->{algo} == 1  ? $key->{length}.'R' : # RSA
+            $key->{algo} == 2  ? $key->{length}.'r' : # RSA encrypt only (legacy)
+            $key->{algo} == 3  ? $key->{length}.'s' : # RSA sign only
+            $key->{algo} == 16 ? $key->{length}.'g' : # Elgamal encrypt only
+            $key->{algo} == 17 ? $key->{length}.'D' : # DSA
+            $key->{algo} == 20 ? $key->{length}.'G' : # Elgamal encrypt+sign (legacy)
+                                 $key->{length}.'?';
+    $str .= '/'.substr($key->{fpr},-8).' ';
+    $str .= strftime '%Y-%m-%d', localtime($key->{creation});
+    $str .= ' [expires: '.strftime('%Y-%m-%d', localtime($key->{expiration})).']'
+      if defined $key->{expiration};
+    $str .= ' ' x (72 + 1 - length($str) - length($key->{flags})) . $key->{flags};
+    $str . sprintf "\n     Key fingerprint = %s %s %s %s %s  %s %s %s %s %s\n",
+                   map { substr($key->{fpr}, ($_ * 4), 4) } (0..10);
+}
+
+unless (defined $options{'attr-height'}) {
+    $options{'attr-height'} = 0;
+    $options{'attr-height'} >= $_ or $options{'attr-height'} = $_ for map {$#{$_->{uids}}+1} @KEYS;
+    $options{'attr-height'} = 5 if $options{'attr-height'} > 5;
+}
+
+foreach my $n (0 .. $#KEYS) {
+    my $key = $KEYS[$n];
+    print "\\begin{SaveVerbatim}{PubKey$n}\n"
+         .pubkey_string('pub',$key)
+         ."\\end{SaveVerbatim}\n";
+
+    print "\\begin{SaveVerbatim}{UID$n}\n";
+    my $tag = "uid  ";
+    my $max = 72 - length $tag;
+    my $x = 2.2 * ($options{'attr-height'} > 5 ? 5 : $options{'attr-height'});
+    $max -= $x if defined $key->{photo};
+    $max -= $x if defined $key->{photo} and defined $key->{qrcode} and
+                  $#{$key->{uids}} < 1.5*$options{'attr-height'};
+    foreach my $uid (grep {$_->{type} eq 'uid'} @{$key->{uids}}) {
+        my $text = $LOCALE->encode($uid->{text});
+        for (my $i = 0; $i < length $text; $i+=$max) {
+            print STDOUT ($i ? ' ' x length $tag : $tag ), substr ($text, $i, $max), "\n";
+        }
+    }
+    print "\\end{SaveVerbatim}\n";
+
+    if ($options{'show-subkeys'}) {
+        print "\\begin{SaveVerbatim}{SubKey$n}\n";
+        print pubkey_string('sub',$_) foreach @{$key->{sub}};
+        print "\\end{SaveVerbatim}\n";
+    }
+    print "\\expandafter\\newsavebox\\csname Key$n\\endcsname\n\n";
+}
+
+print << "EOF"
+\\def\\COLUMNS{2}
+\\def\\MAXKEY{$#KEYS}
+
+EOF
+;
+
+print << 'EOF'
+\newlength\MaxWidth
+\newlength\Width
+
+\pagestyle{empty}
+\begin{document}
+\setlength\parindent{0pt}
+
+\setlength\MaxWidth{ \textwidth/\COLUMNS - 1ex*\COLUMNS - 1pt*(\COLUMNS-1) }
+\settowidth\Width{\texttt{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}}
+%                        "     Key fingerprint = 7420 DF86 BCE1 5A45 8DCE  9976 3927 8DA8 109E 6244"
+
+EOF
+;
+
+foreach my $n (0 .. $#KEYS) {
+    my $key = $KEYS[$n];
+    my $w;
+    if (defined $key->{photo} and defined $key->{qrcode}) {
+        $w = $#{$key->{uids}} < 1.5*$options{'attr-height'}
+           ? ($options{'attr-height'}+5)."\\baselineskip+1pt" # horizontal
+           : '5\baselineskip'                               # vertical
+    }
+    elsif (defined $key->{photo}) {
+        $w = '5\baselineskip'
+    }
+    elsif (defined $key->{qrcode}) {
+        $w = "$options{'attr-height'}\\baselineskip"
+    }
+    print "\\expandafter\\savebox\\csname Key$n\\endcsname{%\n"
+         ."  \\begin{adjustbox}{minipage=\\Width,valign=t,max width=\\MaxWidth,margin=1ex}%\n"
+         ."    \\BUseVerbatim{PubKey$n}\\\\[.3\\baselineskip]%\n"
+         ."    \\parbox[b]{\\Width".(defined $w ? "-($w)" : '' )."}{\\BUseVerbatim{UID$n}}%\n";
+    if (defined $key->{photo} or defined $key->{qrcode}) {
+        print "    \\parbox[b]{$w}{\\raggedleft\\tt%\n";
+        print "      \\includegraphics[height=$options{'attr-height'}\\baselineskip,max width=5\\baselineskip,raise=-.3\\baselineskip]{$key->{photo}}%\n"
+            if defined $key->{photo};
+        print "      ".($#{$key->{uids}} < 1.5*$options{'attr-height'} ? "~" : "\\\\[1ex]")."%\n"
+            if defined $key->{photo} and defined $key->{qrcode};
+        print "      \\includegraphics[height=$options{'attr-height'}\\baselineskip,max width=5\\baselineskip,raise=-.3\\baselineskip]{$key->{qrcode}}%\n"
+            if defined $key->{qrcode};
+        print "    }%\n";
+    }
+    print "    \\\\[.3\\baselineskip]%\n"
+         ."    \\BUseVerbatim{SubKey$n}%\n"
+        if $options{'show-subkeys'};
+    print "  \\end{adjustbox}%\n}\n";
+}
+
+
+print << 'EOF'
+\makeatletter
+\newtoks\toks at table
+\newtoks\toks at row
+\newtoks\toks at cell
+\newcounter{n at column}
+\newcounter{n at key}
+
+\def\free at space{\textheight}
+\newlength\Row at Height
+\newlength\Cell at Height
+
+\toks at table = {\setcounter{n at key}{0}}
+\toks at cell  = {\expandafter\usebox\csname Key\then at key\endcsname}
+
+\loop {
+  \setcounter{n at column}{0}
+  \setlength\Row at Height{0pt}
+  \toks at row = {}
+
+  % generate a single row
+  \loop \ifnum \value{n at column} < \COLUMNS
+    \settototalheight\Cell at Height{\the\toks at cell}
+    \ifnum \Cell at Height>\Row at Height \global\Row at Height=\Cell at Height \fi
+
+    \ifnum \value{n at column} > 0 \toks at row = \expandafter{\the\toks at row & } \fi
+    \global\toks at row = \expandafter{\the\toks at row%
+      \the\toks at cell%
+      \stepcounter{n at key}%
+      \ifnum \value{n at key}>\MAXKEY \setcounter{n at key}{0} \fi%
+    }
+
+    \stepcounter{n at key}
+    \ifnum \value{n at key}>\MAXKEY \setcounter{n at key}{0} \fi
+    \stepcounter{n at column}
+  \repeat
+}
+\edef\free at space{\number \numexpr \free at space - \Row at Height \relax}
+\ifnum \free at space > 0
+  \toks at table = \expandafter{\the\toks at table\the\toks at row \\ \hline}
+  \repeat
+
+\begin{tabular}{@{}|*\COLUMNS{@{}l@{}|@{}}}\hline%
+  \the\toks at table
+\end{tabular}
+\makeatother
+\end{document}
+EOF
+;
+
+__END__
+
+=encoding utf8
+
+=head1 NAME
+
+gpg-key2latex - Generate a LaTeX file for fingerprint slips.
+
+=head1 SYNOPSIS
+
+=over
+
+=item B<gpg-key2latex> [B<-p> I<papersize>] [B<-s>] I<keyid> [I<keyid>...]
+
+=back
+
+=head1 DESCRIPTION
+
+gpg-key2latex generates a LaTeX file with an OpenPGP key fingerprint and
+User IDs, repeated as often as it fits on a single page.  The LaTeX data
+is written to STDOUT.
+Note: In most cases the generated file can be compiled to PDF using
+pdflatex(1), but xelatex(1) is required if some UID contains CJK
+characters.
+
+=head1 OPTIONS
+
+=over
+
+=item B<-p> I<paper-size>, B<--paper-size=>I<paper-size>
+
+Select the output paper size. The default is the output of paperconf(1),
+or I<a4> if libpaper-utils isn't installed.
+
+=item B<-s>, B<--show-subkeys>
+
+Show subkey information.
+
+=item B<--show-photo>
+
+Show the first valid user attribute, if any.  Note: This writes JPG
+files to the current directory.
+
+=item B<--show-qrcode>
+
+Show a QR code of the OpenPGP key fingerprint (40 hexadecimal digits,
+without spaces), prefixed with the string "OPENPGP4FPR:".  Note: This
+writes PDF files to the current directory.
+Requires qrencode(1) and epstopdf(1).
+
+=item B<--attr-height>
+
+The height, in number of lines, of the photo and QR code.  The default
+is the number of User ID, with a maximum of 5.
+
+=item B<-?>, B<--help>
+
+Print a brief help and exit.
+
+=item B<--version>
+
+Print the version and exit.
+
+=back
+
+=head1 WEBSITE
+
+L<http://pgp-tools.alioth.debian.org/>
+
+=head1 SEE ALSO
+
+gpg(1), gpg-key2ps(1)
+
+=head1 BUGS AND FEEDBACK
+
+Bugs or feature requests for B<gpg-key2latex> should be filed with the
+Debian project's bug tracker at L<https://www.debian.org/Bugs/>.
+
+=head1 AUTHOR
+
+Guilhem Moulin E<lt>guilhem at guilhem.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (c) 2014 Guilhem Moulin.
+B<gpg-key2latex> is free software, distributed under the GNU Public
+License, version 3 or later.


Property changes on: trunk/gpg-key2latex/gpg-key2latex
___________________________________________________________________
Added: svn:executable
   + *

Modified: trunk/gpg-key2ps/gpg-key2ps
===================================================================
--- trunk/gpg-key2ps/gpg-key2ps	2014-10-30 16:54:16 UTC (rev 721)
+++ trunk/gpg-key2ps/gpg-key2ps	2014-11-06 21:51:23 UTC (rev 722)
@@ -402,6 +402,10 @@
 
 GNU Privacy Guard.
 
+=item gpg-key2latex(1)
+
+Like B<gpg-key2ps>, but produces LaTeX output instead.
+
 =item http://pgp-tools.alioth.debian.org/
 
 The homepage of B<gpg-key2ps> and the other tools bundled in B<signing-party>.




More information about the Pgp-tools-commit mailing list