./0000755000175000017500000000000013233575735011076 5ustar guilhemguilhem./README0000644000175000017500000000227013233575632011753 0ustar guilhemguilhemThis is a collection of several projects relating to OpenPGP. * caff: CA - Fire and Forget signs and mails a key * pgp-clean: removes all non-self signatures from key * pgp-fixkey: removes broken packets from keys * gpg-mailkeys: simply mail out a signed key to its owner * gpg-key2ps: generate PostScript file with fingerprint paper slips * gpgdir: recursive directory encryption tool * gpglist: show who signed which of your UIDs * gpgsigs: annotates list of GnuPG keys with already done signatures * gpgparticipants: create list of party participants for the organiser * gpgwrap: a passphrase wrapper * keyanalyze: minimum signing distance (MSD) analysis on keyrings * keylookup: ncurses wrapper around gpg --search * sig2dot: converts a list of GnuPG signatures to a .dot file * springgraph: creates a graph from a .dot file * keyart: creates a random ASCII art of a PGP key file * gpg-key2latex: generate LaTeX file with fingerprint paper slips For more information on each of these tools, please see their respective manpages. Please note that each individual project has its own license, consult the licensing information in the subdirectories. -- Peter Palfrader, Tue, 29 Jun 2004 13:48:09 +0200 ./springgraph/0000755000175000017500000000000013233575632013416 5ustar guilhemguilhem./springgraph/springgraph0000755000175000017500000006724313233575632015704 0ustar guilhemguilhem#!/usr/bin/perl -w # springgraph v0.79, (c) 2002 Darxus@ChaosReigns.com, released under the GPL # Download current version from: http://www.chaosreigns.com/code/springgraph/ # # This program attempts to render .dot files in a fashion similar to neato, # which is part of graphviz: http://www.research.att.com/sw/tools/graphviz/. # I have never looked at any of the code in graphviz. # # Example usage: # # cat test.dot | ./springgraph.pl -s 3 > springgraph.png # # The "-s 3" specifies the scale, and is optional. All of the node # locations are multiplied by this. Increase the scale to eliminate # node overlaps. Decrease the scale to make the graph smaller. # # Requirements: GD.pm (http://www.perl.com/CPAN/authors/id/L/LD/LDS/) # # Definition of the .dot files which springgraph renders # can be found in the graphviz man pages. A copy is here: # http://www.unisa.edu.au/eie/csec/graphviz/dot.1.html. Springgraph only # supports the fillcolor and label node attributes, and can only handle # two nodes per edge definition ("node1 -> node2", not "node1 -> node2 # -> node3"). # # Springgraph fully supports the .dot files generated by sig2dot # (http://www.chaosreigns.com/code/sig2dot), which generates .dot files # from GPG/PGP signature relationships. # # Thanks to the following for help with the math for the arrowheads: # Mike Joseph # Walt Mankowski # Jeff Weisberg # # Yes, the placement of the freaking arrowheads was by far the hardest # part of writing this program. # # Thanks to Hartmut Palm for cylinder translation/rotation code in # VRML.pm: http://dc.gfz-potsdam.de/~palm/vrmlperl/ # v0.26 May 06 16:12:30 2002 # v0.27 May 06 18:15:38 2002 cleanup # v0.44 May 06 23:56:45 2002 # v0.56 May 07 05:10:02 2002 # v0.60 May 07 23:27:29 2002 arrow heads !! (not filled in due to segfault) # v0.61 May 07 2002 handle absence of beginning double-quote in fillcolor attribute # v0.62 May 08 19:44:04 2002 use getopts to get scale argument # v0.63 May 08 21:29:48 2002 made fillcolor optional again # v0.64 May 08 22:28:40 2002 render http://www.research.att.com/sw/tools/graphviz/examples/undirected/ER.dot.txt # and http://www.research.att.com/sw/tools/graphviz/examples/undirected/process.dot.txt # (added support for undirected graphs ("--" links) # v0.65 May 08 22:44:00 2002 render http://www.research.att.com/sw/tools/graphviz/examples/directed/fsm.dot.txt # (do not attempt to draw a line from a node to itself and cause a devision by zero) # v0.67 May 09 05:53:16 2002 support multiple nodes on one link line, adjusted detection of completion # render http://www.research.att.com/sw/tools/graphviz/examples/directed/unix.dot.txt # (support node names containing spaces) # v0.68 May 09 17:29:06 2002 cleaned up link line processing a bit (removed extraneous define checks) # v0.69 May 09 18:23:19 2002 render http://www.research.att.com/sw/tools/graphviz/examples/undirected/inet.dot.txt # (support {} lists in link (edge) lines) # v0.70 May 10 00:39:20 2002 Strip double-quotes that were getting missed to support sig2dot v0.27. # v0.71 May 11 20:06:17 2002 don't draw twice, added some 3D math (but not output yet) # v0.72 May 11 21:31:20 2002 3D output !!! (via -p flag) # v0.73 May 11 22:34:23 2002 added labels to 3D output # v0.74 May 12 02:07:29 2002 output 3D output suitable for animation # v0.75 May 13 01:45:41 2002 beginnings of vrml output (-v) - colored spheres # v0.76 May 13 04:30:13 2002 added connections between nodes to vrml # output, thanks cylinder translation/rotation # code from VRML.pm by Hartmut Palm: # http://dc.gfz-potsdam.de/~palm/vrmlperl/ # v0.77 May 13 04:41:53 2002 made colors optional in pov and vrml output # v0.78 May 13 06:31:34 2002 removed extra cylinders from vrml output # v0.79 May 13 07:20:23 2002 made 2d output background transparent # v0.80 Mar 19 2003 optimization patch from Marco Bodrato # v0.81 Aug 20 2003 Caption stderr progress notes use Getopt::Std; use strict; use vars qw( $push $pull %node $im $source $dest $nodenum $blue $black $opt_b $bgcol @bgcolor $dist $iter $maxiter $percent $xdist $ydist $newdist2 $xmove $ymove $movecount $rate $nodes %link $continue $done $line @nodelist %saw $name $label $margin $minx $miny $maxx $maxy $scale $nodesize $powderblue $linecol $h $s $v $r $g $b $color $maxxlength $minxlength $pi $twopi $angle @point $width $height $arrowlength $arrowwidth $num $opt_s $edge @parts $part @sources @dests $sourcesstring $destsstring $pov $opt_p $zdist $zmove $pov_or_vrml $opt_v $vrml $opt_t $trans $opt_f $font $fontsize $opt_h $opt_l @linecolor ); $push = 2000; $pull = .1; $maxiter = 400; $rate = 2; $nodes = 5; #$done = 0.1; $done = 0.3; #$done = 3; $margin = 20; #$nodesize = 80; $nodesize = 40; $arrowlength = 10; # pixels $arrowwidth = 10; srand 1; #comment out this line to generate graphs differently every time $pi = 3.141592653589793238462643383279502884197169399375105; # from memory $twopi = $pi * 2; getopts('s:pvhtb:l:f:'); # -h: Show some help if ($opt_h) { usage(); exit 1; } use GD; # -s: set scale if ($opt_s) { $scale = $opt_s; } else { $scale = 1; } # -p: Output as Pov-Ray if ($opt_p) { $pov = 1; } else { $pov = 0; } # -v: Output as VRML if ($opt_v) { $vrml = 1; } else { $vrml = 0; } # -t: Make background transparent if ($opt_t) { $trans = 1; } else { $trans = 0; } # -b: Set background color if ($opt_b) { $trans = 0; $opt_b =~ m/^(..)(..)(..)$/ or die "Invalid color: $opt_b"; @bgcolor = (hex($1), hex($2), hex($3)); } else { @bgcolor = (255, 255, 255); } # -l: Set line color if ($opt_l) { $trans = 0; $opt_l =~ m/^(..)(..)(..)$/ or die "Invalid color: $opt_l"; @linecolor = (hex($1), hex($2), hex($3)); } else { @linecolor = (169, 169, 169); } # -f: Set TrueType font, style and size if ($opt_f) { $opt_f =~ m/^([^:]+)((:[^:]*)(:\d*)?)?$/ or die "Invalid font: $opt_f"; $font = $1; $fontsize = 13; if ($2) { $2 =~ m/^(:[^:]*)(:\d*)?$/; $font .= $1 if defined $1 and $1 ne ':'; $fontsize = $2 if defined $2 and $2 ne ':'; $fontsize =~ s/^://; } } $done = $done / $scale; while ($line = ) { undef $name; next if ($line =~ m#^//#); chomp $line; # 2 = arro1, 1 = no arrow if ($line =~ m#^(.*-[>-][^\[]*)#) { $edge = $1; @parts = split(/(-[->])/,$edge); for $part (0 .. $#parts) { if (defined $parts[$part+2] and $parts[$part] ne '->' and $parts[$part] ne '--') { #print ":$parts[$part]:".$parts[$part+1].":".$parts[$part+2].":\n"; undef @sources; undef @dests; $parts[$part] =~ s/^\s*"?//; $parts[$part] =~ s/"?\s*$//; $parts[$part+2] =~ s/^\s*"?//; $parts[$part+2] =~ s/"?\s*;?\s*$//; if ($parts[$part] =~ m#^{(.*)}$#) { $sourcesstring = $1; #print STDERR "sourcesstring:$sourcesstring:\n"; @sources = split(/[\s*;?\s*]/,$sourcesstring); } else { $sources[0] = $parts[$part]; } if ($parts[$part+2] =~ m#^{(.*)}$#) { $destsstring = $1; #print STDERR "destsstring:$destsstring:\n"; @dests = split(/[\s*;?\s*]/,$destsstring); } else { $dests[0] = $parts[$part+2]; } for $source (@sources) { next if ($source eq ""); for $dest (@dests) { next if ($dest eq ""); $source =~ s/^\s*"?//; $source =~ s/"?\s*$//; $dest =~ s/^\s*"?//; $dest =~ s/"?\s*;?\s*$//; $link{$source}{$dest} = 2 if ($parts[$part+1] eq '->'); $link{$source}{$dest} = 1 if ($parts[$part+1] eq '--'); push (@nodelist,$source,$dest); #print STDERR "$source ".$parts[$part+1]." $dest\n"; } } } } # $source = $1; # $dest = $2; # $source =~ s/^\W*//; # $source =~ s/\W*$//; # $dest =~ s/^\W*//; # $dest =~ s/\W*$//; # $link{$source}{$dest} = 2; # push (@nodelist,$source,$dest); # print STDERR "source:$source:dest:$dest:\n"; } else { # if ($line =~ m#^edge# or $line =~ m#^node#) # { # print STDERR "Skipping: $line\n"; # next; # } if ($line =~ m#^(\S+).*\[.*\]#) { $name = $1; $name =~ tr/"//d; if ($name eq 'node' or $name eq 'edge') { next; } #print STDERR "name:$name:\n"; } if ($line =~ m#\[.*label=([^,\]]*).*\]#) { $label = $1; $label =~ tr/"//d; $node{$name}{'label'} = $label; #print STDERR "label:$label:\n"; } if ($line =~ m#\[.*fillcolor="?([\d\.]+),([\d\.]+),([\d\.]+).*\]#) { $h = $1; $s = $2; $v = $3; #print STDERR "hsv:$h:$s:$v:\n"; $h = $h * 360; ($r,$g,$b) = &hsv2rgb($h,$s,$v); $node{$name}{r} = $r; $node{$name}{g} = $g; $node{$name}{b} = $b; #print STDERR "rgb:$r:$g:$b:\n"; } } } undef %saw; @saw{@nodelist} = (); @nodelist = sort keys %saw; # remove sort if undesired undef %saw; if ($pov or $vrml) { $pov_or_vrml = 1; } else { $pov_or_vrml = 0; } for $nodenum (@nodelist) { $node{$nodenum}{x}=rand;# $maxx; $node{$nodenum}{y}=rand;# $maxy; $node{$nodenum}{z}=rand if $pov_or_vrml; unless(defined $node{$nodenum}{'label'}) { $node{$nodenum}{'label'} = $nodenum; } } print STDERR "springgraph iterating until reaches $done\n\n"; #&draw; $continue = 1; $iter = 0; while($continue > $done) { $continue = $done; $iter++; for $nodenum (@nodelist) { $node{$nodenum}{oldx} = $node{$nodenum}{x}; $node{$nodenum}{oldy} = $node{$nodenum}{y}; $node{$nodenum}{oldz} = $node{$nodenum}{z} if $pov_or_vrml; $xmove = 0; $ymove = 0; $zmove = 0 if $pov_or_vrml; } for $source (@nodelist) { $movecount = 0; for $dest (@nodelist) { next if $source eq $dest; # loops are not supported $xdist = $node{$source}{oldx} - $node{$dest}{oldx}; $ydist = $node{$source}{oldy} - $node{$dest}{oldy}; $dist = $xdist**2 + $ydist**2; if ($pov_or_vrml) { $zdist = $node{$source}{oldz} - $node{$dest}{oldz}; $dist += $zdist**2; } # $distance = sqrt($dist); $percent = $push / $dist; if ($link{$source}{$dest}) { $percent -= $pull; } if ($link{$dest}{$source}) { $percent -= $pull; } $percent *= $rate; $xmove -= $xdist * $percent; $ymove -= $ydist * $percent; $zmove -= $zdist * $percent if $pov_or_vrml; $movecount++; # $pullmove = $pull * $dist; # $pushmove = $push / $dist; # print STDERR "dist: $dist, pull: $pullmove, push: $pushmove\n"; # print STDERR "$source to ${dest}, Dist: $dist Want: $wantdist (${percent}x)\n"; # print STDERR "is: $node[$source]{oldx} $node[$source]{oldy} $xdist $ydist, want: $wantxdist $wantydist ($newdist2)\n"; } if ($movecount) { # renormalize if there are multiple nodes $xmove /= $movecount; $ymove /= $movecount; $zmove /= $movecount if $pov_or_vrml; } $node{$source}{x} -= $xmove; $node{$source}{y} -= $ymove; $node{$source}{z} -= $zmove if $pov_or_vrml; if ($xmove > $continue) { $continue = $xmove; } if ($ymove > $continue) { $continue = $ymove; } if (($pov_or_vrml) and $zmove > $continue) { $continue = $zmove; } } #print STDERR "$iter\n"; if (0) { &draw; open XV,'|-', qw/xv -wait 1 -/; #open XV,'|-', qw/xloadimage -delay 1 stdin/; binmode XV; print XV $im->png; close XV; } if ($iter % 20 == 0) { print STDERR "$continue\n"; } } print STDERR "Iterations: $iter\n"; for $source (@nodelist) { for $color ('r', 'g', 'b') { $node{$source}{$color} = 255 unless (defined $node{$source}{$color}); } } if ($pov) { &drawpov; } elsif ($vrml) { &drawvrml; } else { &draw; } undef $maxx; undef $maxy; sub draw { for $nodenum (@nodelist) { if (!(defined $maxx) or (($node{$nodenum}{x} + (length($node{$nodenum}{'label'}) * 8 + 16)/2) > $maxx + (length($node{$nodenum}{'label'}) * 8 + 16)/2)) { $maxx = $node{$nodenum}{x};# + (length($node{$nodenum}{'label'}) * 8 + 16)/2/2 $maxxlength = (length($node{$nodenum}{'label'}) * 8 + 16)/2; } if (!(defined $minx) or (($node{$nodenum}{x} - (length($node{$nodenum}{'label'}) * 8 + 16)/2) < $minx - (length($node{$nodenum}{'label'}) * 8 + 16)/2)) { $minx = $node{$nodenum}{x};# - (length($node{$nodenum}{'label'}) * 8 + 16)/2/2 $minxlength = (length($node{$nodenum}{'label'}) * 8 + 16)/2; } $maxy = $node{$nodenum}{y} if (!(defined $maxy) or $node{$nodenum}{y} > $maxy); $miny = $node{$nodenum}{y} if (!(defined $miny) or $node{$nodenum}{y} < $miny); } for $nodenum (@nodelist) { #$node{$nodenum}{x} = ($node{$nodenum}{x} - $minx) * $scale + $margin; $node{$nodenum}{x} = ($node{$nodenum}{x} - $minx) * $scale + $minxlength -1 ;# + $margin; $node{$nodenum}{y} = ($node{$nodenum}{y} - $miny) * $scale + $nodesize/2 - 1; } $maxx = ($maxx - $minx) * $scale + $minxlength + $maxxlength;# + $margin*2; $maxy = ($maxy - $miny) * $scale + $nodesize/2*2; $im = new GD::Image($maxx,$maxy); $bgcol = $im->colorAllocate(@bgcolor); $im->transparent($bgcol) if $trans; # make transparent $blue = $im->colorAllocate(0,0,255); $powderblue = $im->colorAllocate(176,224,230); $black = $im->colorAllocate(0,0,0); $linecol = $im->colorAllocate(@linecolor); $im->useFontConfig(1) if $opt_f; for $source (@nodelist) { #print STDERR "node: $source $node[$source]{x},$node[$source]{y}\n"; for $dest (@nodelist) { if (defined $link{$source}{$dest} and $link{$source}{$dest} == 2 and $source ne $dest) { $dist = sqrt( abs($node{$source}{x}-$node{$dest}{x})**2 + abs($node{$source}{y}-$node{$dest}{y})**2 ); $xdist = $node{$source}{x} - $node{$dest}{x}; $ydist = $node{$source}{y} - $node{$dest}{y}; $angle = &acos($xdist/$dist); #$angle = atan2($ydist,$xdist); #$angle += $pi if $ydist < 0; #$dist = abs(cos($angle))*(length($node{$dest}{'label'}) * 8 + 16)/2 + abs(sin($angle))*$nodesize/2; $width = (length($node{$dest}{'label'}) * 8 + 16)/2; $height = $nodesize/2; $dist = sqrt( ($height**2 * $width**2) / ( ($height**2 * (cos($angle)**2) ) + ($width**2 * (sin($angle)**2) ) )); #$dist = $dist*40; $xmove = cos($angle)*$dist; $ymove = sin($angle)*$dist; #$ymove = -$ymove if $ydist < 0; # the part mj omitted $point[0]{x} = $xmove; $point[0]{y} = $ymove; $xmove = cos($angle)*($dist+$arrowlength-3); $ymove = sin($angle)*($dist+$arrowlength-3); #$ymove = -$ymove if $ydist < 0; # the part mj omitted $point[3]{x} = $xmove; $point[3]{y} = $ymove; #$angle = $angle + $arrowwidth/2; $dist = 4; $xmove = $xmove + cos($angle)*$dist; $ymove = $ymove + sin($angle)*$dist; #$ymove = -$ymove if $ydist < 0; # the part mj omitted $angle = $angle + $twopi/4; $dist = $arrowwidth/2; $xmove = $xmove + cos($angle)*$dist; $ymove = $ymove + sin($angle)*$dist; #$ymove = -$ymove if $ydist < 0; # the part mj omitted $point[1]{x} = $xmove; $point[1]{y} = $ymove; $angle = $angle + $twopi/2; $dist = $arrowwidth; $xmove = $xmove + cos($angle)*$dist; $ymove = $ymove + sin($angle)*$dist; #$ymove = -$ymove if $ydist < 0; # the part mj omitted $point[2]{x} = $xmove; $point[2]{y} = $ymove; for $num (0 .. 3) { $point[$num]{y} = - $point[$num]{y} if $ydist < 0; } $im->line($node{$dest}{x}+$point[0]{x},$node{$dest}{y}+$point[0]{y},$node{$dest}{x}+$point[1]{x},$node{$dest}{y}+$point[1]{y},$linecol); $im->line($node{$dest}{x}+$point[1]{x},$node{$dest}{y}+$point[1]{y},$node{$dest}{x}+$point[2]{x},$node{$dest}{y}+$point[2]{y},$linecol); $im->line($node{$dest}{x}+$point[2]{x},$node{$dest}{y}+$point[2]{y},$node{$dest}{x}+$point[0]{x},$node{$dest}{y}+$point[0]{y},$linecol); # $xmove = int($node{$dest}{x}+$point[3]{x}); # $ymove = int($node{$dest}{y}+$point[3]{y}); # $im->fillToBorder($xmove,$ymove,$linecol,$powderblue); #$im->fillToBorder($node{$dest}{x}+$point[3]{x},$node{$dest}{y}+$point[3]{y},$linecol,$linecol); #$im->line($point[1]{x},$point[1]{y},$point[2]{x},$point[2]{y},$linecol); #$im->line($point[2]{x},$point[2]{y},$point[0]{x},$point[0]{y},$linecol); #$im->fillToBorder($point[3]{x},$point[3]{y},$linecol,$linecol); #$im->arc($point[3]{x},$point[3]{y},10,10,0,360,$black); # $im->arc($point[0]{x},$point[0]{y},20,20,0,360,$black); # $im->arc($point[1]{x},$point[1]{y},20,20,0,360,$black); # $im->arc($point[2]{x},$point[2]{y},20,20,0,360,$black); #$im->arc($node{$dest}{x}+$xmove,$node{$dest}{y}+$ymove,20,20,0,360,$black); } } } for $source (@nodelist) { for $dest (@nodelist) { if ($link{$source}{$dest}) { $im->line($node{$source}{x},$node{$source}{y},$node{$dest}{x},$node{$dest}{y},$linecol); } } } for $source (@nodelist) { if ($opt_f) { my @bounds = GD::Image::->stringFT($black,$font,$fontsize,0,0,0,$node{$source}{'label'}); $im->arc($node{$source}{x},$node{$source}{y},$bounds[2]-$bounds[0]+1.5*$fontsize,$nodesize,0,360,$black); } else { $im->arc($node{$source}{x},$node{$source}{y},(length($node{$source}{'label'}) * 8 + 16),$nodesize,0,360,$black); } if (defined $node{$source}{r} and defined $node{$source}{g} and defined $node{$source}{b}) { $color = $im->colorResolve($node{$source}{r},$node{$source}{g},$node{$source}{b}); } else { $color = $bgcol; } $im->fillToBorder($node{$source}{x},$node{$source}{y},$black,$color); } for $source (@nodelist) { if ($opt_f) { my @bounds = GD::Image::->stringFT($black,$font,$fontsize,0,0,0,$node{$source}{'label'}); $im->stringFT($black,$font,$fontsize,0,$node{$source}{x} - ($bounds[0]+$bounds[2])/2.0,$node{$source}{y}+$fontsize/2.0,$node{$source}{'label'}); } else { $im->string(gdLargeFont,$node{$source}{x} - (length($node{$source}{'label'}) * 8 / 2),$node{$source}{y}-8,$node{$source}{'label'},$black); } } binmode STDOUT; print $im->png; } sub drawpov { print'// Generated by springgraph, by Darxus@ChaosReigns.com: // http://www.ChaosReigns.com/code/springgraph/ #include "colors.inc" #include "shapes.inc" #include "textures.inc" #include "glass.inc" #include "stones.inc" light_source {<0, 400, -500> color White rotate <0, 360*clock, 0>} light_source {<400, 0, -500> color White rotate <0, 360*clock, 0>} '; for $source (@nodelist) { $node{$source}{x} = $node{$source}{x} * $scale; $node{$source}{y} = $node{$source}{y} * $scale; $node{$source}{z} = $node{$source}{z} * $scale; $node{$source}{r} = $node{$source}{r} / 256; $node{$source}{g} = $node{$source}{g} / 256; $node{$source}{b} = $node{$source}{b} / 256; } for $source (@nodelist) { print "sphere { <$node{$source}{x},$node{$source}{y},$node{$source}{z}>, 15 pigment {color rgb<$node{$source}{r},$node{$source}{g},$node{$source}{b}>}}\n"; print "text { ttf \"crystal.ttf\", \"$node{$source}{'label'}\", 0.5, 0 translate 2*x rotate <0, 360*clock, 0> translate -0.375*y scale 10 translate <$node{$source}{x},$node{$source}{y},$node{$source}{z}> pigment {color rgb<$node{$source}{r},$node{$source}{g},$node{$source}{b}>}}\n"; #print "text { ttf \"crystal.ttf\", \"$node{$source}{'label'}\", 0.5, 0 translate -".scalar(length($node{$source}{'label'})*0.25)."*x scale 10 translate <$node{$source}{x},$node{$source}{y},$node{$source}{z}> pigment {color rgb<$node{$source}{r},$node{$source}{g},$node{$source}{b}>}}\n"; for $dest (@nodelist) { if ($link{$source}{$dest}) { print "cylinder {<$node{$source}{x},$node{$source}{y},$node{$source}{z}>,<$node{$dest}{x},$node{$dest}{y},$node{$dest}{z}> 0.5 pigment {color rgb<0.5,0.5,0.5>}}\n"; } } } print 'camera { location <0, 0, -500> up <0.0, 1.0, 0> right <4/3, 0.0, 0> look_at <0, 0, -1> rotate <0, 360*clock, 0> } '; } sub drawvrml { my ($t,$r,$length,$color); print'#VRML V2.0 utf8 WorldInfo { info ["Generated by springgraph, by Darxus@ChaosReigns.com: http://www.ChaosReigns.com/code/springgraph/"] } '; for $source (@nodelist) { $node{$source}{x} = $node{$source}{x} * $scale; $node{$source}{y} = $node{$source}{y} * $scale; $node{$source}{z} = $node{$source}{z} * $scale; for $color ('r', 'g', 'b') { if (defined $node{$source}{$color}) { $node{$source}{$color} = $node{$source}{$color} / 256; } } } for $source (@nodelist) { print " Transform { translation $node{$source}{x} $node{$source}{y} $node{$source}{z} children [ Shape{ appearance Appearance { material Material { diffuseColor $node{$source}{r} $node{$source}{g} $node{$source}{b} } } geometry Sphere{radius 15} } ] } "; #print "sphere { <$node{$source}{x},$node{$source}{y},$node{$source}{z}>, 15 pigment {color rgb<$node{$source}{r},$node{$source}{g},$node{$source}{b}>}}\n"; #print "text { ttf \"crystal.ttf\", \"$node{$source}{'label'}\", 0.5, 0 translate 2*x rotate <0, 360*clock, 0> translate -0.375*y scale 10 translate <$node{$source}{x},$node{$source}{y},$node{$source}{z}> pigment {color rgb<$node{$source}{r},$node{$source}{g},$node{$source}{b}>}}\n"; #print "text { ttf \"crystal.ttf\", \"$node{$source}{'label'}\", 0.5, 0 translate -".scalar(length($node{$source}{'label'})*0.25)."*x scale 10 translate <$node{$source}{x},$node{$source}{y},$node{$source}{z}> pigment {color rgb<$node{$source}{r},$node{$source}{g},$node{$source}{b}>}}\n"; for $dest (@nodelist) { if ($link{$source}{$dest}) { ($t,$r,$length) = &cylinder($node{$source}{x},$node{$source}{y},$node{$source}{z},$node{$dest}{x},$node{$dest}{y},$node{$dest}{z}); print " Transform { translation $t rotation $r children [ Shape{ appearance Appearance { material Material { diffuseColor 0.5 0.5 0.5 } } geometry Cylinder { radius 0.5 height $length top FALSE bottom FALSE } } ] } "; } } } # print 'camera { # location <0, 0, -500> # up <0.0, 1.0, 0> # right <4/3, 0.0, 0> # look_at <0, 0, -1> # rotate <0, 360*clock, 0> #} #'; } sub hsv2rgb { #from http://faqchest.dynhost.com/prgm/perlu-l/perl-01/perl-0101/perl-010100/perl01010410_17820.html # Given an h/s/v array, return an r/g/b array. # The r/g/b values will each be between 0 and 255. # The h value will be between 0 and 360, and # the s and v values will be between 0 and 1. # my $h = shift; my $s = shift; my $v = shift; # limit this to h values between 0 and 360 and s/v values # between 0 and 1 unless (defined($h) && defined($s) && defined($v) && $h >= 0 && $s >= 0 && $v >= 0 && $h <= 360 && $s <= 1 && $v <= 1) { return (undef, undef, undef); } my $r; my $g; my $b; # 0.003 is less than 1/255; use this to make the floating point # approximation of zero, since the resulting rgb values will # normally be used as integers between 0 and 255. Feel free to # change this approximation of zero to something else, if this # suits you. if ($s < 0.003) { $r = $g = $b = $v; } else { $h /= 60; my $sector = int($h); my $fraction = $h - $sector; my $p = $v * (1 - $s); my $q = $v * (1 - ($s * $fraction)); my $t = $v * (1 - ($s * (1 - $fraction))); if ($sector == 0) { $r = $v; $g = $t; $b = $p; } elsif ($sector == 1) { $r = $q; $g = $v; $b = $p; } elsif ($sector == 2) { $r = $p; $g = $v; $b = $t; } elsif ($sector == 3) { $r = $p; $g = $q; $b = $v; } elsif ($sector == 4) { $r = $t; $g = $p; $b = $v; } else { $r = $v; $g = $p; $b = $q; } } # Convert the r/g/b values to all be between 0 and 255; use the # ol' 0.003 approximation again, with the same comment as above. $r = ($r < 0.003 ? 0.0 : $r * 255); $g = ($g < 0.003 ? 0.0 : $g * 255); $b = ($b < 0.003 ? 0.0 : $b * 255); return ($r, $g, $b); } # from perlfunc(1) sub acos { atan2( sqrt(1 - $_[0] * $_[0]), $_[0] ) } sub cylinder { my ($x1,$y1,$z1,$x2,$y2,$z2) = @_; my ($t, $r, $length, $rx, $ry, $rz, $dist); $x1 = 0 unless $x1; $x2 = 0 unless $x2; $y1 = 0 unless $y1; $y2 = 0 unless $y2; $z1 = 0 unless $z1; $z2 = 0 unless $z2; my $dx=$x1-$x2; my $dy=$y1-$y2; my $dz=$z1-$z2; $length = sqrt($dx**2 + $dy**2 + $dz**2); $rx = $dx; $ry = $dy+$length; $rz = $dz; $dist = sqrt($rx**2 + $ry**2 + $rz**2); if ($dist) { # renormalize if the cylinder is not degenerated $rx /= $dist; $ry /= $dist; $rz /= $dist; } $t = ($x1-($dx/2))." ".($y1-($dy/2))." ".($z1-($dz/2)); $r = "$rx $ry $rz $pi"; return ($t,$r,$length); } sub usage { print < example.png -p Create a file that can be rendered with POV-Ray -v Create a VRML file -s This option specifies the scale. All of the node locations are multiplied by this. Increase the scale to eliminate node overlaps. Decrease the scale to make the graph smaller. -t Make the background of the resulting image transparent. -b Set background color of image, specify it in the form RRGGBB, in hex digits, e.g. FFFFFF is white, 000000 is black, FF0000 is red, ... -l Set the line color, same format as the background color -f Set the (TrueType) font, and optionally the style and size, to use for labels. Example: "DejaVu Serif:Italic:12". -h Show this help END } ./springgraph/README.springgraph0000644000175000017500000000214613233575632016624 0ustar guilhemguilhemspringgraph ----------- Springgraph will read in a .dot file description of a graph, which, for each node, specifies its name and which other nodes it is connected to, and then renders a graph. Each node is drawn as an ellipse, and each connection is drawn as an arrow. The node placement is a result of all of the nodes moving away from each other, while all nodes which are connected move toward each other. This movement is repeated until it stabilizes. Springgraph was written as an alternative to neato, which is part of graphviz. It attempts to read the same .dot files used by graphviz, but currently only supports a limited number of node attributes (label and fillcolor). Definition of the .dot files which springgraph renders can be found in the graphviz man pages. A copy is here: * http://www.graphviz.org/Documentation.php * http://www.graphviz.org/cvs/doc/info/lang.html Springgraph only supports the fillcolor and label node attributes, and can only handle two nodes per edge definition ("node1 -> node2", not "node1 -> node2 -> node3"). -- Christoph Berg Sun, 6 Mar 2005 17:44:47 +0100 ./springgraph/springgraph.10000644000175000017500000000511013233575632016021 0ustar guilhemguilhem.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH SPRINGGRAPH 1 "September 5, 2005" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME springgraph \- renders a graph from a .dot file .SH SYNOPSIS .B springgraph [OPTIONS] < input-file.dot > output-file.png .br .SH DESCRIPTION Springgraph will read in a .dot file description of a graph, which, for each node, specifies its name and which other nodes it is connected to, and then renders a graph. The output is a PNG file. Each node is drawn as an ellipse, and each connection is drawn as an arrow. The node placement is a result of all of the nodes moving away from each other, while all nodes which are connected move toward each other. This movement is repeated until it stabilizes. Springgraph was written as an alternative to neato, which is part of graphviz. It attempts to read the same .dot files used by graphviz, but currently only supports a limited number of node attributes (label and fillcolor) and can only handle two nodes per edge definition ("node1 -> node2", not "node1 -> node2 -> node3"). .SH OPTIONS \fIspringgraph\fP accepts the following options: .TP 16 .B \-p Create a file that can be rendered with POV-Ray .TP 16 .B \-v create a VRML file .TP 16 .B \-s this option specifies the scale. All of the node locations are multiplied by this. Increase the scale to eliminate node overlaps. Decrease the scale to make the graph smaller. .TP 16 .B \-t make the background of the resulting image transparent. .TP 16 .B \-b set background color of image, specify it in the form RRGGBB, in hex digits, e.g. FFFFFF is white, 000000 is black, FF0000 is red, ... .TP 16 .B \-l set the line color, same format as the background color .TP 16 .B \-h display usage synopsis .SH EXAMPLE digraph { "rene" -> "myon"; "mvo" -> "rene"; } .SH SEE ALSO neato(1) sig2dot(1) http://www.graphviz.org/Documentation.php http://www.graphviz.org/cvs/doc/info/lang.html .SH AUTHOR This manual page was written by Kevin M. Rosenberg , for the Debian GNU/Linux system (but may be used by others). ./springgraph/Makefile0000644000175000017500000000037513233575632015063 0ustar guilhemguilhemall: install: install -D springgraph $(DESTDIR)/usr/bin/springgraph install -D -m644 springgraph.1 $(DESTDIR)/usr/share/man/man1/springgraph.1 install -D -m644 README.springgraph \ $(DESTDIR)/usr/share/doc/signing-party/README.springgraph clean: ./gpgparticipants/0000755000175000017500000000000013233575735014275 5ustar guilhemguilhem./gpgparticipants/gpgparticipants0000755000175000017500000001012213233575735017416 0ustar guilhemguilhem#!/bin/sh # Prepare a printable list of keysigning participants. # Useful for the party organiser. # # $Id: gpgparticipants 764 2015-01-22 22:48:30Z Guilhem Moulin $ # # License: GPLv2 or later # Copyright Philippe Teuwen 2008 usage() { cat <<- EOF Usage: $0 [-a HASHES|--algorithm HASHES] input output datestring organizer title Use a single hyphen-minus (-) in place of input to read from STDIN (resp. of output to write to STDOUT). Example: echo 9AD7E3DB 54C12701 |\\ $0 - ksp-file.txt "20080222 1100" "My Name " "my party 08" EOF exit ${1:-0} } # Handle options and arguments ############################## # Use getopt to validate and normalize options and arguments, # then reinject them as main arguments OPTS=$(getopt -o a:h -l algorithm:,help -n $0 -- "$@") || usage 1 eval set -- "$OPTS" # Default options algos="SHA256,RIPEMD160" # Parse options while [ "$1" != -- ]; do case "$1" in -a|--algorithm) algos="$2"; shift 2;; -h|--help) usage 0; shift;; *) usage 1;; esac done # Get rid of the '--' left before the arguments shift # Five arguments should remain [ $# -eq 5 ] || usage 1 algos=$(echo "$algos" | tr '[:lower:]' '[:upper:]') halgos="$algos" algos=$(echo "$algos" | tr ',' ' ') for algo in $algos; do # Ensure this is a valid algo ${GNUPGBIN:-gpg} --print-md "$algo" /dev/null >/dev/null || exit $? done input="$1" [ "$input" = "-" ] && input=""; output="$2" date="$3" org="$4" title=$(echo "$5"|tr a-z A-Z|sed 's/\(.\)/\1 /g') [ "$output" = - ] && output=/path/to/ksp-file.txt || { exec > "$output"; } # Date of event LC_ALL=C date --date="$date" +"%A, %B %e, %Y; %H:%M" # Organiser contact printf "%80s\n\n\n" "$org" # Title printf "%*s\n\n" $(((72+$(echo "$title"|wc -c))/2)) "$title" # Header cat <. ./gpgparticipants/gpgparticipants-prefill0000755000175000017500000001051313233575632021051 0ustar guilhemguilhem#!/usr/bin/python """Fill in the first digits of the hash in a form created by gpgparticipants.""" __version__ = "1.0" __author__ = "Stefan Huber" __email__ = "shuber@sthu.org" __copyright__ = "Copyright 2013, Stefan Huber" __license__ = "MIT" # also Copyright 2014 Peter Palfrader # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation # files (the "Software"), to deal in the Software without # restriction, including without limitation the rights to use, # copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following # conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR # OTHER DEALINGS IN THE SOFTWARE. import sys import hashlib import getopt hexdigits = "0123456789abcdef" def insertspaces(s): """Inserts a space after every 4-th character, and three spaces after every 8-th character of string s.""" def inpacks(s, n): while len(s) > 0: yield s[0:n] s = s[n:] out = " ".join([ " ".join(inpacks(octp, 4)) for octp in inpacks(s, 8)]) return out def range_hex(length, fixed_prefix=''): """Give all hex-strings from 00...0 until ff...f of given length.""" if length == 0: yield "" elif len(fixed_prefix) > 0: prefix = fixed_prefix[0] for postfix in range_hex(length-1, fixed_prefix[1:]): yield prefix + postfix elif length == 1: for c in hexdigits: yield c elif length > 1: for prefix in range_hex(length-1): for postfix in range_hex(1): yield prefix + postfix def usage(): """Print --help text""" print("""Usage: {0} {0} --help {0} -h Takes a file produced by gpgparticipants as and trys to fill in some digits into the SHA256 field such that the resulting list actually has a SHA256 checksum that starts with those digits. Whenever a match is found a file with the digits filled in is written to `.DIGITS`. OPTIONS: --fastforward If a match is found of given length and --fastforward is given then the program immediately jumps to the next length. --min-length NUM Start search with given length. --max-length NUM Stop search with given length. """.format(sys.argv[0])) if __name__ == "__main__": fastforward = False minlength = 1 maxlength = 32 prefix = '' optlist, args = getopt.getopt(sys.argv[1:], 'h', ['fastforward', 'min-length=', 'max-length=', 'prefix=', 'help']) for o, a in optlist: if o in ("-h", "--help"): usage() exit(0) elif o in ("--fastforward"): fastforward = True elif o in ("--min-length"): minlength = int(a) elif o in ("--max-length"): maxlength = int(a)+1 elif o in ("--prefix"): prefix = a.lower() if len(args) < 2: print >>sys.stderr, "You need to give two filenames." exit(1) if not all(c in hexdigits for c in prefix): print >>sys.stderr, "Invalid prefix." exit(1) emptyfile = open(args[0]).read() idx = emptyfile.find("SHA256 Checksum:") idx = emptyfile.find("_", idx) for l in range(minlength, maxlength): print "Looking at length", l for h in range_hex(l, prefix): H = insertspaces(h.upper()) filledfile = emptyfile[:idx] + H + emptyfile[idx+len(H):] actual = hashlib.sha256(filledfile).hexdigest() if actual[:len(h)] == h: print "Found: ", H open(args[1] + "." + h, "w").write(filledfile) if fastforward: break ./gpgparticipants/gpgparticipants.10000644000175000017500000000275713233575632017565 0ustar guilhemguilhem.TH GPGPARTICIPANTS 1 "March 05, 2008" .SH NAME gpgparticipants \- generate paper list for keysigning party .SH SYNOPSIS .B gpgparticipants .RB [\fIOPTIONS\fR] {input|-} {output|-} datestring organizer title .SH DESCRIPTION .B gpgparticipants is targeted at organisers of a keysigning party that uses the Zimmermann-Sassaman key-signing protocol. It creates a file with all the keys from a list of participators in your keysigning party. You give it a list with key ID's and some general variables, and it will generate a file you can publish. You publish this list; participants will download and print this file to take with them to the party. .SH OPTIONS .TP .BI -a \fIHASHES\fR,\ \fB\-\-algorithm=\fIHASHES\fR For each algorithm in \fIHASHES\fR, a comma-separated list of digest algorithms supported by \fBgpg\fR(1), add a placeholder for the digest value (to be filled by the participants) to the output file. Default: SHA256,RIPEMD160. .SH ENVIRONMENT .TP 13 .I HOME Set the default home directory. .TP 13 .I GNUPGBIN Set the gpg binary. Default: "gpg". .TP 13 .I GNUPGHOME Set the default working directory for gpg. Default: "~/.gnupg". .SH EXAMPLES To create a KSP file with all keys present in the keyring: gpg \-\-with-colons \-\-fingerprint | grep ^fpr: | cut \-d: \-f 10 | \\ gpgparticipants \- ksp-file.txt "20080222 1100" "My Name " "my party 08" .SH SEE ALSO .BR gpg (1), gpgsigs (1) .SH AUTHOR This manual page was written by Thijs Kinkhorst . ./keyart/0000755000175000017500000000000013233575632012371 5ustar guilhemguilhem./keyart/README0000644000175000017500000000175613233575632013262 0ustar guilhemguilhemOpenPGP Random Art ------------------ keyart takes any argument that can uniquely identify a key (name, email address, short key ID, long key ID, fingerprint, etc), and prints out its random visualization ASCII art based on the public key fingerprint. Please see 'doc/drunken-bishop.txt' for more information about the algorithm and changes compared to OpenSSH keys. Usage ----- keyart can take a publicly exported GPG key or keyring to draw the art, or it can use an identifier to uniquely identify a key in your public keyring, such as an email address, short key ID, long key ID or key fingerprint. To produce the random art for a key: $ keyart 0x8086060F +----[DSA 1024]-----+ |E . . ^^i. | |:l. . i.^i:. | |^^.. :.^: : | |: ^ .l | |.. . . : | | . S | | | | | | | | | | | +----[8086060F]-----+ ./keyart/keyart0000755000175000017500000002126113233575632013620 0ustar guilhemguilhem#!/usr/bin/python """This script takes a key ID, fingerprint, email address, publicly exported key, or a keyring, and prints the random ASCII art visualization of the key as per the Drunken Bishop algorithm as applied to OpenSSH keys by Dirk Loss.""" import argparse import os import re import subprocess import sys PARSER = argparse.ArgumentParser( description='Creates ASCII art from OpenPGP keys.') PARSER.add_argument('-c', '--color', help='Print the art with ANSI color.', action='store_true') PARSER.add_argument('-l', '--longid', action='store_true', help='Print the 16-character long ID of a key.') PARSER.add_argument('-f', '--fingerprint', type=str, metavar=('HEX'), action='append', help='A hexadecimal string representing a fingerprint.') PARSER.add_argument('-k', '--keyring', type=str, metavar=('KEYRING'), action='append', help='A publicly exported OpenPGP key or keyring.') PARSER.add_argument('keyid', type=str, nargs='*', metavar=('KEYID'), help='A key identifier (email, ID, fingerprint, etc.).') ARGS = PARSER.parse_args() def draw_art(key_size, key_algo, key_curve, key_fpr): """Execute the Drunken Bishop algorithm on a key.""" art = '' f_bytes = [] pos = 104 walk = [pos] visits = [0]*209 temp = '' try: key_bin = bin(int(key_fpr, 16))[2:].zfill(len(key_fpr)*4) except ValueError: print("The supplied fingerprint is not a hexadecimal string.") sys.exit(3) for i, char in enumerate(key_bin): temp += char if i % 2 == 1: f_bytes.append(temp) temp = '' # create a little-endian bit-pair array for i in range(0, len(f_bytes), 4): f_bytes[i], f_bytes[i+3] = f_bytes[i+3], f_bytes[i] f_bytes[i+1], f_bytes[i+2] = f_bytes[i+2], f_bytes[i+1] for pair in f_bytes: if (20 <= pos <= 36 or 39 <= pos <= 55 or 58 <= pos <= 74 or 77 <= pos <= 93 or 96 <= pos <= 112 or 115 <= pos <= 131 or 134 <= pos <= 150 or 153 <= pos <= 169 or 172 <= pos <= 188): if pair == '00': pos -= 20 # Square 'M' elif pair == '01': pos -= 18 elif pair == '10': pos += 18 else: pos += 20 elif 1 <= pos <= 17: # Square 'T' if pair == '00': pos -= 1 elif pair == '01': pos += 1 elif pair == '10': pos += 18 else: pos += 20 elif 191 <= pos <= 207: # Square 'B' if pair == '00': pos -= 20 elif pair == '01': pos -= 18 elif pair == '10': pos -= 1 else: pos += 1 elif pos in [19, 38, 57, 76, 95, 114, 133, 152, 171]: # Square 'L' if pair == '00': pos -= 19 elif pair == '01': pos -= 18 elif pair == '10': pos += 19 else: pos += 20 elif pos in [37, 56, 75, 94, 113, 132, 151, 170, 189]: # Square 'R' if pair == '00': pos -= 20 elif pair == '01': pos -= 19 elif pair == '10': pos += 18 else: pos += 19 elif pos == 0: # Square 'a' if pair == '01': pos += 1 elif pair == '10': pos += 19 elif pair == '11': pos += 20 elif pos == 18: # Square 'b' if pair == '00': pos -= 1 elif pair == '10': pos += 18 elif pair == '11': pos += 19 elif pos == 190: # Square 'c' if pair == '00': pos -= 19 elif pair == '01': pos -= 18 elif pair == '11': pos += 1 else: # Square 'd' if pair == '00': pos -= 20 elif pair == '01': pos -= 19 elif pair == '10': pos -= 1 walk.append(pos) for square in walk: visits[square] += 1 if visits[square] > 18: visits[square] = 18 # See https://tools.ietf.org/html/rfc4880#section-9.1 # Also https://tools.ietf.org/html/rfc6637#section4 if key_algo == '1' or key_algo == '2' or key_algo == '3': header = 'rsa' + key_size # RSA elif key_algo == '16': header = 'elg' + key_size # Elgamal encrypt only elif key_algo == '17': header = 'dsa' + key_size # DSA elif key_algo == '20': header = 'xxx' + key_size # Elgamal encrypt+sign (legacy) elif key_algo == '18' or key_algo == '19' or key_algo == '22': if key_curve is not None and key_curve != '': header = key_curve elif key_algo == '18': header = 'ecdh' + key_size elif key_algo == '19': header = 'ecdsa' + key_size elif key_algo == '22': header = 'eddsa' + key_size else: header = 'N/A' header = "[%s]" % header if len(header) > 19: header = '' art += '+' + header.center(19, '-') + '+\n' for i, visit in enumerate(visits): # Build up the art with the boundaries and newlines if i % 19 == 0: art += "|{}" elif i % 19 == 18: art += "{}|\n" else: art += '{}' # Insert the 'coin' into the art at this position if i == 104: # Starting position art = art.format(_get_coin(visit, ARGS.color, coin='S')) elif i == walk[len(walk)-1]: # Ending position art = art.format(_get_coin(visit, ARGS.color, coin='E')) else: art = art.format(_get_coin(visit, ARGS.color)) if key_size and ARGS.longid: footer = "["+key_fpr[-16:]+"]" elif key_size: footer = "["+key_fpr[-8:]+"]" else: footer = '' art += '+' + footer.center(19, '-') + '+' return art def _get_coin(num_of_hits, ansi_art=False, coin=None): """Returns the coin for this humber of hits. If ansi_art is enabled the coin will be colorized with ansi codes. If coin is not None, it will use that coin instead of the default (used for the 'S' and 'E', start end coins).""" coins = [' ', '.', '^', ':', 'l', 'i', '?', '(', 'f', 'x', 'X', 'Z', '#', 'M', 'W', '&', '8', '%', '@'] colors = ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] reset = '' if ansi_art: colors = [ '', # no coin '\033[38;5;21m', # blue (cold) '\033[38;5;39m', '\033[38;5;50m', '\033[38;5;48m', '\033[38;5;46m', # green '\033[38;5;118m', '\033[38;5;190m', '\033[38;5;226m', # yellow '\033[38;5;220m', '\033[38;5;214m', # orange '\033[38;5;208m', '\033[38;5;202m', '\033[38;5;196m', # red '\033[38;5;203m', '\033[38;5;210m', '\033[38;5;217m', # pink '\033[38;5;224m', '\033[38;5;231m' # white (hot) ] reset = '\033[0m' color = colors[num_of_hits] if not coin: coin = coins[num_of_hits] return '{}{}{}'.format(color, coin, reset) def gpg_cmd(cmd): gpg = subprocess.Popen(cmd, stdout=subprocess.PIPE) for lines in gpg.communicate()[0].decode('ascii').split('\n'): colons = lines.split(':') if colons[0] == 'pub': size = colons[2] algo = colons[3] curve = colons[16] if 16 < len(colons) else None elif colons[0] == 'fpr': print(draw_art(size, algo, curve, colons[9])) size = None algo = None curve = None if __name__ == '__main__': gpg_bin = os.getenv('GNUPGBIN', 'gpg') strip_nonhex = re.compile('[^a-fA-F0-9]+') cmd = [gpg_bin, '--no-options', '--with-colons', '--fingerprint'] if ARGS.fingerprint: for fpr in ARGS.fingerprint: fpr = strip_nonhex.sub('',fpr) if len(fpr) % 8 != 0: print("Hex string must be a multiple of 8 bytes.") sys.exit(2) print(draw_art(None, None, fpr)) if ARGS.keyring: cmd.append('--no-default-keyring') cmd.extend(['--keyring=%s' % os.path.abspath(keyring) for keyring in ARGS.keyring]) gpg_cmd(cmd) if ARGS.keyid: cmd.append('--') cmd.extend(ARGS.keyid) gpg_cmd(cmd) ./keyart/TODO0000644000175000017500000000046013233575632013061 0ustar guilhemguilhemTODO ---- * Add support for providing multiple exported keys. * Add support for providing a 40-character OpenPGP fingerprint string. * Add support for keyrings. * Add ability to ceate an HTML/PDF document for keysigning parties. * Create documentation on analysis of art collisions, similar to OpenSSH. ./keyart/BUGS0000644000175000017500000000003613233575632013053 0ustar guilhemguilhemBUGS ---- None at this time. ./keyart/LICENSE0000644000175000017500000000254613233575632013405 0ustar guilhemguilhemCopyright (c) 2014, Aaron Toponce (c) 2016 Guilhem Moulin 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. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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. ./keyart/doc/0000755000175000017500000000000013233575632013136 5ustar guilhemguilhem./keyart/doc/party-worksheet0000644000175000017500000000254413233575632016236 0ustar guilhemguilhemBASICS ------ To create a worksheet for a keysigning party, you will need to import each of the participants keys to your GPG keyring. If you've been storing them in a separate keyring, this should be easy. Once each key has been imported, create a single "art.txt" file with all the keys. Something like: $ keyart 8086060F F34D3BC5 C22D2FCF > art.txt In the examples/ directory of this documentation, is a 'party.sh' script that can transform that art.txt file to 5 key art per line. It will print the output to STDOUT. So, you could redirect to a file with: $ ./party.sh art.txt > worksheet.txt Open 'worksheet.txt' in a text editor, and add newlines as necessary, so the key art is not broken by a page break. Use your text editor 'print preview' for help. Print off your modified 'worksheet.txt' for each key signing party attendee. COLOR WORKSHEET --------------- If you would like to create an ANSI colored worksheet, you can install the Python "ansi2html" library, and redirect your output to an HTML file, which could then be used to print the worksheet: $ sudo easy_install ansi2html $ keyart -c 8086060F F34D3BC5 C22D2FCF > art.txt $ ./party.sh art.txt | ansi2html > worksheet.html You may need to make some HTML adjustments to prevent the printer from breaking up the key art on the page break. Use your browser's 'print preview' for help. ./keyart/doc/examples/0000755000175000017500000000000013233575632014754 5ustar guilhemguilhem./keyart/doc/examples/party.sh0000755000175000017500000000064413233575632016456 0ustar guilhemguilhem#!/bin/sh set -ue FILE="${1:-art.txt}" LEN=$(wc -l <"$FILE") I=1 while [ $I -le $LEN ]; do LINE1="$(sed -n "$((I+00))p" "$FILE")" LINE2="$(sed -n "$((I+13))p" "$FILE")" LINE3="$(sed -n "$((I+26))p" "$FILE")" LINE4="$(sed -n "$((I+39))p" "$FILE")" LINE5="$(sed -n "$((I+52))p" "$FILE")" echo "${LINE1}${LINE2}${LINE3}${LINE4}${LINE5}" [ $((I%13)) -eq 0 ] && I=$((I+52)) I=$((I+1)) done ./keyart/doc/druken-bishop.txt0000644000175000017500000001502113233575632016450 0ustar guilhemguilhemReimplement the "Drunken Bishop" walk as implemented by OpenSSH. We use a larger field size for GnuPG due to SHA1 fingerprint sizes (11x19). See http://www.dirk-loss.de/sshvis/drunken_bishop.pdf for the algorithm and security analysis for OpenSSH. The field is as defined: 111111111 0123456789012345678 +-------------------+x (column) 0| | 1| | 2| | 3| | 4| | 5| S | 6| | 7| | 8| | 9| | 10| | +-------------------+ y (row) Each position on the board can be represented by its cartesian coordinates (x,y). We can assign each positition a numerical value by using the equation: pos = x + 19y Each position on the board contains an ASCII character the represents the frequency of visits by the bishop. A blank position has not been visited. The more the bishop has visited a square, the heavier or more dense the ASCII character should be. From light -> dark, the following scale should be used: " .'`^",:;Il!i><~+_-?][}{1)(|\/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$" See the table below (note, this does not necessarily follow OpenSSH): +-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--++--+--+ |0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16|17|18||19|20| +-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--++--+--+ | |.|^|:|l|i|?|(|f|x|X |Z |# |M |W |& |8 |% |@ ||S |E | +-+-+-+-+-+-+-+-+-+-+--+--+--+--+--+--+--+--+--++--+--+ 'S' and 'E' are special characters that represent the starting and ending location of the bishop respectively. 'S' always starts at coordinates (9,5), which is position 104, the center of the board. Movement is defined by taking the fingerprint, and coverting each character to its binary value. For exmaple, the fingerprint: E041 3539 273A 6534 A3E1 9259 22EE E048 8086 060F has the binary values of: 1110000001000001 0011010100111001 0010011100101010 0110010100110100 1010001111100001 1001001001011001 0010001011101110 1110000001001000 1000000010000110 0000011000001111 In OpenSSH, the movement is found using bit-pairs at a time, left to right, least significant bit to most sifginifant bit. In this implementation for OpenPGP, the same rule applies. So: +-----+-----++-----+-----++-----++-----+-----++-----+-----+ Fingerprint: | E | 0 || 4 | 1 || ... || 0 | 6 || 0 | F | +--+--+--+--++--+--+--+--++-----++--+--+--+--++--+--+--+--+ Bit-pair: |11|10|00|00||01|00|00|01|| ... ||00|00|01|10||00|00|11|11| +--+--+--+--++--+--+--+--++-----++--+--+--+--++--+--+--+--+ Step: |4 |3 |2 |1 ||8 |7 |6 |5 || ... ||76|75|74|73||80|79|78|77| +--+--+--+--++--+--+--+--++-----++--+--+--+--++--+--+--+--+ The direction of our drunken bishop follows standard Chess rules for the bishop piece, moving only on the diagnal across the beard, which is defined as follows: +------+-----+ | Pair | Dir | +------+-----+ N | 00 | NW | ^ +------+-----+ | | 01 | NE | W <--+--> E +------+-----+ | | 10 | SW | v +------+-----+ S | 11 | SE | +------+-----+ The bishop starts in the center of the board at position 104. So, each possible move would place him on the following positions on the board, after the first move: +------+-----+------+ | Pair | Pos | Diff | +------+-----+------+ | 00 | 84 | -20 | +------+-----+------+ | 01 | 86 | -18 | +------+-----+------+ | 10 | 122 | +18 | +------+-----+------+ | 11 | 124 | +20 | +------+-----+------+ We must cleanly handle how the bishop behaves when he reaches the edge of the board, or a corner. We'll define the types of positions as follows: +-------------------+ |aTTTTTTTTTTTTTTTTTb| |LMMMMMMMMMMMMMMMMMR| a = NW corner |LMMMMMMMMMMMMMMMMMR| b = NE corner |LMMMMMMMMMMMMMMMMMR| c = SW corner |LMMMMMMMMMMMMMMMMMR| d = SE corner |LMMMMMMMMMMMMMMMMMR| T = Top edge |LMMMMMMMMMMMMMMMMMR| B = Bottom edge |LMMMMMMMMMMMMMMMMMR| R = Right edge |LMMMMMMMMMMMMMMMMMR| L = Left edge |LMMMMMMMMMMMMMMMMMR| M = Middle pos. |cBBBBBBBBBBBBBBBBBd| +-------------------+ When a bishop finds himself in one of these positions, we'll define his adjusted movement, if necessary, as follows: +-----+------+---------+----------+--------+ | Pos | Bits | Heading | Adjusted | Offset | +-----+------+---------+----------+--------+ | a | 00 | NW | no move | 0 | | | 01 | NE | E | +1 | | | 10 | SW | S | +19 | | | 11 | SE | SE | +20 | +-----+------+---------+----------+--------+ | b | 00 | NW | W | -1 | | | 01 | NE | no move | 0 | | | 10 | SW | SW | +18 | | | 11 | SE | S | +19 | +-----+------+---------+----------+--------+ | c | 00 | NW | N | -19 | | | 01 | NE | NE | -18 | | | 10 | SW | no move | 0 | | | 11 | SE | E | +1 | +-----+------+---------+----------+--------+ | d | 00 | NW | NW | -20 | | | 01 | NE | N | -19 | | | 10 | SW | W | -1 | | | 11 | SE | no move | 0 | +-----+------+---------+----------+--------+ | T | 00 | NW | W | -1 | | | 01 | NE | E | +1 | | | 10 | SW | SW | +18 | | | 11 | SE | SE | +20 | +-----+------+---------+----------+--------+ | B | 00 | NW | NW | -20 | | | 01 | NE | NE | -18 | | | 10 | SW | W | -1 | | | 11 | SE | E | +1 | +-----+------+---------+----------+--------+ | R | 00 | NW | NW | -20 | | | 01 | NE | N | -19 | | | 10 | SW | SW | +18 | | | 11 | SE | S | +19 | +-----+------+---------+----------+--------+ | L | 00 | NW | N | -19 | | | 01 | NE | NE | -18 | | | 10 | SW | S | +19 | | | 11 | SE | SE | +20 | +-----+------+---------+----------+--------+ | M | 00 | NW | NW | -20 | | | 01 | NE | NE | -18 | | | 10 | SW | SW | +18 | | | 11 | SE | SE | +20 | +-----+------+---------+----------+--------+ ./keyart/doc/keyart.10000644000175000017500000000517013233575632014522 0ustar guilhemguilhem.\" Manpage for keyart .\" Aaron Toponce .TH keyart 1 "17 Jun 2014" .SH NAME .B keyart \- Create ASCII art of an OpenPGP key. .SH SYNOPSIS .B keyart [\-c|\-\-color] [\-l|\-\-longid] [\-f|\-\-fingerprint \fIHEX\fR [\-f|\-\-fingerprint \fIHEX\fR ...]] [\-k|\-\-keyring \fIKEYRING\fR [\-k|\-\-keyring \fIKEYRING\fR ...]] [\fIKEYID\fR ...] .SH DESCRIPTION .B keyart creates an ASCII art representation of public OpenPGP keys. The art is an implementation of the Drunken Bishop by Dirk Loss. Documentation about the algorithm can be found in /usr/share/doc/signing-party/keyart/, or as appropriate for your distribution. .B keyart supports printing the ASCII art in both plain text (default) and ANSI color. ANSI color uses a "heat map", where cold (blue) represents squares in the room the drunk bishop has rarely visited, and hot (red) represents squares in the room the drunk bishop has frequently visited. There is a 1:1 ratio of color to printed character. The \fIKEYID\fRs are key identifiers (fingerprint, keyid, user ID, etc. see \fBgpg\fR(1) for details). If no \fIKEYID\fR is given, \fBkeyart\fR creates an ASCII art representation for all keys found. .SH OPTIONS .TP 8 .B \-c | \-\-color Print the ASCII art using ANSI color to the terminal. .TP 8 .B \-l | \-\-longid Print the 16-character long ID of a on OpenPGP key in the footer badge. Default is to print the 8-character short ID. Silently ignored if "\fB\-f | \-\-fingerprint \fIHEX\fR" is passed. .TP 8 .B \-f | \-\-fingerprint \fIHEX\fR Any arbitrary hexadecimal string of any length. Could be an MD5, SHA1, SHA2, or SHA3 hexadecimal checksum. Useful for comparing and analyzing potential collisions with existing keys. This option can be repeated multiple times. .TP 8 .B \-k | \-\-keyring \fIKEYRING\fR An OpenPGP public key file or keyring, from which the \fIKEYID\fRs are to be found. If this option is not present, the \fIKEYID\fRs are found from GnuPG's default keyring (usually ~/.gnupg/pubring.gpg); if it is repeated multiple times, the provided \fIKEYRING\fRs are used as successive sources. .TP 8 .B \-h | \-\-help Print the help message and quit. .SH ENVIRONMENT .TP 8 .I HOME Set the default home directory. .TP 8 .I GNUPGBIN Set the gpg binary. Default: "gpg". .TP 8 .I GNUPGHOME Set the default working directory for gpg. Default: "~/.gnupg". .SH EXIT STATUS .TP 8 .B 0 \fBkeyart\fR was executed successfully. .TP 8 .B 1 \fBgpg\fR(1) or \fBgpg2\fR(1) are not installed. .TP 8 .B 2 The supplied hexadecimal string is not a multiple of 8 bytes. .SH SEE ALSO .BR gpg (1) .SH AUTHOR .B keyart and its documentation is written by Aaron Toponce . ./caff/0000755000175000017500000000000013233575735011775 5ustar guilhemguilhem./caff/README0000644000175000017500000000313413233575735012656 0ustar guilhemguilhemcaff -- CA - fire and forget ============================== caff is a script that helps you in keysigning. It takes a list of keyids on the command line, fetches them from a keyserver and calls GnuPG so that you can sign it. It then mails each key to all its email addresses - only including the one UID that we send to in each mail, pruned from all but self sigs and sigs done by you. The mailed key is encrypted with itself as a means to verify that key belongs to the recipient. Since we do not upload the new signatures, or import them into our main keyring, the signature only gets public if: - the email address is valid, and - the person reading the email can decrypt the mail (if it was sent encrypted). Therefore we achieve the same level of security as common Challenge Response systems like CABot, without all the extra hassle of those systems. FEATURES -------- * Easy to setup. * Attaches only the very UID that we send to in the mail. * Prunes the key from all signatures that are not self sigs and not done by you, thereby greatly reducing the size of mails. * Sends the mail encrypted if possible, will warn before sending unencrypted mail (sign only keys) * Creates proper PGP MIME messages. * Uses separate GNUPGHOME for all its operations. DEPENDENCIES ------------ gnupg (>= 1.3.92), perl, libgnupg-interface-perl, libtext-template-perl, libmime-perl, libmailtools-perl (>= 1.62), libnet-idn-encode-perl INSTALLATION ------------ After creating a ~/.caffrc from the template, caff almost works out of the box. -- Peter $Id: README 511 2014-02-26 06:55:07Z Guilhem Moulin $ ./caff/pgp-fixkey0000755000175000017500000002733213233575735014015 0ustar guilhemguilhem#!/usr/bin/perl -w # pgp-fixkey -- remove broken packets from keys # $Id: pgp-fixkey 764 2015-01-22 22:48:30Z Guilhem Moulin $ # # Copyright (c) 2004, 2005 Peter Palfrader # # 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. =pod =head1 NAME pgp-fixkey -- remove broken packets from keys =head1 SYNOPSIS =over =item B [I [I ...]] =back =head1 DESCRIPTION B removes broken packets from keys in the GnuPG keyring that make GnuPG spew ugly warnings. It optionally takes a list of keyids on the command line and only cleans those keys. =head1 OPTIONS =over =item I Use this key. =back =head1 ENVIRONMENT =over =item I The default home directory. =item I The gpg binary. Default: C<"gpg">. =item I The default working directory for gpg. Default: C<$HOME/.gnupg>. =back =head1 FILES =over =item $HOME/.gnupg/pubring.gpg - default GnuPG keyring =back =head1 SEE ALSO caff(1), gpg(1). =head1 AUTHOR Peter Palfrader =cut use strict; use IO::Handle; use English '-no_match_vars'; use File::Path; use Fcntl; use IO::Select; use GnuPG::Interface; my $REVISION = '$Rev: 764 $'; my ($REVISION_NUMER) = $REVISION =~ /(\d+)/; my $VERSION = "0.0.0.$REVISION_NUMER"; sub notice($) { my ($line) = @_; print STDERR "[NOTICE] $line\n"; }; sub info($) { my ($line) = @_; print STDERR "[INFO] $line\n"; }; sub debug($) { my ($line) = @_; #print STDERR "[DEBUG] $line\n"; }; sub trace($) { my ($line) = @_; #print STDERR "[trace] $line\n"; }; sub trace2($) { my ($line) = @_; #print STDERR "[trace2] $line\n"; }; sub make_gpg_fds() { my %fds = ( stdin => IO::Handle->new(), stdout => IO::Handle->new(), stderr => IO::Handle->new(), status => IO::Handle->new() ); my $handles = GnuPG::Handles->new( %fds ); return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles); }; sub readwrite_gpg($$$$$%) { my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd, %options) = @_; trace("Entering readwrite_gpg."); my ($first_line, $dummy) = split /\n/, $in; debug("readwrite_gpg sends ".(defined $first_line ? $first_line : "")); local $INPUT_RECORD_SEPARATOR = undef; my $sout = IO::Select->new(); my $sin = IO::Select->new(); my $offset = 0; trace("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ? $statusfd : 'undef')."."); $inputfd->blocking(0); $stdoutfd->blocking(0); $statusfd->blocking(0) if defined $statusfd; $stderrfd->blocking(0); $sout->add($stdoutfd); $sout->add($stderrfd); $sout->add($statusfd) if defined $statusfd; $sin->add($inputfd); my ($stdout, $stderr, $status) = ("", "", ""); my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'}; trace("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches; my $readwrote_stuff_this_time = 0; my $do_not_wait_on_select = 0; my ($readyr, $readyw, $written); while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { if (defined $exitwhenstatusmatches) { if ($status =~ /$exitwhenstatusmatches/m) { trace("readwrite_gpg found match on $exitwhenstatusmatches"); if ($readwrote_stuff_this_time) { trace("read/write some more\n"); $do_not_wait_on_select = 1; } else { trace("that's it in our while loop.\n"); last; } }; }; $readwrote_stuff_this_time = 0; trace("select waiting for ".($sout->count())." fds."); ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1); trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0)); for my $wfd (@$readyw) { $readwrote_stuff_this_time = 1; if (length($in) != $offset) { trace("writing to $wfd."); $written = $wfd->syswrite($in, length($in) - $offset, $offset); $offset += $written; }; if ($offset == length($in)) { trace("writing to $wfd done."); unless ($options{'nocloseinput'}) { close $wfd; trace("$wfd closed."); }; $sin->remove($wfd); $sin = undef; } } next unless defined $readyr and @$readyr; # Wait some more. for my $rfd (@$readyr) { $readwrote_stuff_this_time = 1; if ($rfd->eof) { trace("reading from $rfd done."); $sout->remove($rfd); close($rfd); next; } trace("reading from $rfd."); if ($rfd == $stdoutfd) { $stdout .= <$rfd>; trace2("stdout is now $stdout\n================"); next; } if (defined $statusfd && $rfd == $statusfd) { $status .= <$rfd>; trace2("status is now $status\n================"); next; } if ($rfd == $stderrfd) { $stderr .= <$rfd>; trace2("stderr is now $stderr\n================"); next; } } } trace("readwrite_gpg done."); return ($stdout, $stderr, $status); }; my $KEYEDIT_PROMPT = '^\[GNUPG:\] GET_LINE keyedit.prompt'; my $KEYEDIT_DELUID_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.uid.okay'; my $KEYEDIT_DELSIG_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.delsig'; my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET_LINE keyedit.prompt)'; my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey'; sub usage() { print STDERR "pgp-fixkey $VERSION - (c) 2004, 2005 Peter Palfrader\n"; print STDERR "Usage: $PROGRAM_NAME [ [ ...]]\n"; exit 1; }; my @KEYIDS; for my $keyid (@ARGV) { $keyid =~ s/^0x//i; unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8})?$/) { print STDERR "$keyid is not a keyid.\n"; usage(); }; push @KEYIDS, uc($keyid); }; # find a list of all interesting keys. if (scalar @KEYIDS == 0) { my $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->meta_interactive( 0 ); my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fast-list-mode', '--fixed-list-mode' ] ); my $pid = $gpg->list_sigs(handles => $handles); my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; if ($stdout eq '') { die ("No data from gpg for list-sigs\n"); }; my $thiskey = undef; for my $line (split /\n/, $stdout) { debug("Checking line $line"); my ($type, undef, undef, undef, $keyid, $created, undef, undef, undef, undef) = split /:/, $line; if ($type eq 'pub') { $thiskey = $keyid; debug("Found key $thiskey"); } elsif (!defined $thiskey) { next; } elsif ($type eq 'sig' || $type eq 'rev') { if (($keyid eq '0' x 16) || ($created eq "") || ($created == 0)) { push @KEYIDS, $thiskey; info("Key $thiskey needs cleaning."); $thiskey = undef; }; }; }; } KEYS: for my $keyid (@KEYIDS) { # get key listing ################# my $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->meta_interactive( 0 ); my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] ); my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]); my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; if ($stdout eq '') { warn ("No data from gpg for list-key $keyid\n"); next; }; my $keyinfo = $stdout; my @publine = grep /^pub/, (split /\n/, $stdout); if ($#publine < 0) { warn ("No public key found for $keyid.\n"); next; } my (undef, undef, undef, undef, $longkeyid, undef, undef, undef, undef, undef, undef, $flags) = split /:/, pop @publine; my $can_encrypt = $flags =~ /E/; unless (defined $longkeyid) { warn ("Didn't find public keyid in edit dialog of key $keyid.\n"); next; }; my @UIDS; my $uid_number = 0; my $this_uid_text = ''; $uid_number++; debug("Doing key $keyid, uid $uid_number"); # prune it ########## $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode', '--command-fd=0', '--no-tty' ] ); ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); $pid = $gpg->wrap_call( commands => [ '--edit-key' ], command_args => [ $keyid ], handles => $handles ); debug("Starting edit session"); ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark all uids ################### my $number_of_subkeys = 0; my $i = 1; my $have_one = 0; my $is_uat = 0; my $delete_some = 0; debug("Parsing stdout output."); for my $line (split /\n/, $stdout) { debug("Checking line $line"); my ($type, undef, undef, undef, undef, undef, undef, undef, undef, $uidtext) = split /:/, $line; if ($type eq 'sub') { $number_of_subkeys++; }; next unless ($type eq 'uid' || $type eq 'uat'); debug("line is interesting."); debug("mark uid."); readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); $i++; }; debug("Parsing stdout output done."); # delete signatures ################### ($stdout, $stderr, $status) = readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1); while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) { # sig:?::17:EA2199412477CAF8:1058095214:::::13x: my @sigline = grep /^sig/, (split /\n/, $stdout); $stdout =~ s/\n/\\n/g; notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX my $line = pop @sigline; my $answer = "no"; if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance debug("[sigremoval] doing line $line."); my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line; if (($signer eq '0' x 16) || ($created == 0)) { debug("[sigremoval] not interested in that sig ($signer, created: $created)."); $answer = "yes"; }; } else { debug("[sigremoval] no sig line here, only got: ".$stdout); }; ($stdout, $stderr, $status) = readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1); }; readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; } ./caff/THANKS0000644000175000017500000000062413233575632012706 0ustar guilhemguilhemCaff is not the work of a sole author. It wouldn't be what it is without the help of several people. I'd like to say thank you to everybody who helped to make caff better. - Nick Mathewson For suggesting and implementing --no-download and --no-sign. - And of course everyone who sent comments, suggestions or bug-reports. -- Peter Palfrader Thu, 15 Jul 2004 03:08:14 +0200 ./caff/caff0000755000175000017500000023033413233575735012627 0ustar guilhemguilhem#!/usr/bin/perl # caff -- CA - Fire and Forget # $Id: caff 893 2017-07-07 15:05:09Z guilhem $ # # Copyright (c) 2004, 2005, 2006 Peter Palfrader # Copyright (c) 2005, 2006 Christoph Berg # Copyright (c) 2014-2016 Guilhem Moulin # # 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. =pod =head1 NAME caff -- CA - Fire and Forget =head1 SYNOPSIS =over =item B [-eERS] [-m I] [-u I] I [I ..] =item B [-eERS] [-m I] [-u I] [I ..] =back =head1 DESCRIPTION CA Fire and Forget is a script that helps you in keysigning. It takes a list of keyids on the command line, fetches them from a keyserver and calls GnuPG so that you can sign it. It then mails each key to all its email addresses - only including the one UID that we send to in each mail, pruned from all but self sigs and sigs done by you. The mailed key is encrypted with itself as a means to verify that key belongs to the recipient. The list of keys to sign can also be provided through caff's standard input, as gpgparticipants(1) formatted content. Only keys for which both the "Fingerprint OK" and "ID OK" boxes are ticked (i.e., marked with an "x") are considered for signing. Furthermore, the input header must include at least one checksum line, and all checksum boxes must be marked as verified (with an "x"). =head1 OPTIONS =over =item B<-e>, B<--export-old> Export old signatures. Default is to ask the user for each old signature. =item B<-E>, B<--no-export-old> Do not export old signatures. Default is to ask the user for each old signature. =item B<-m>, B<--mail> I Whether to send mail after signing. Default is to ask, for each uid, with a default value of yes. =item B<-R>, B<--no-download> Do not retrieve the key to be signed from a keyserver. =item B<-S>, B<--no-sign> Do not sign the keys. =item B<-u> I, B<--local-user> I Select the key that is used for signing, in case you have more than one key. To sign with multiple keys at once, separate multiple keyids by comma. This option requires the key(s) to be defined through the keyid variable in the configuration file. =item B<--key-file> I Import keys from file. Can be supplied more than once. =item B<--keys-from-gnupg> Try to import keys from your standard GnuPG keyrings. =item B<--debug> Enable debug messages. =back =head1 ENVIRONMENT =over =item I The default home directory. =item I The gpg binary. Default: C<"gpg">. =item I The default working directory for gpg. Default: C<$HOME/.gnupg>. =back =head1 FILES =over =item $HOME/.caffrc - configuration file =item $HOME/.caff/keys/yyyy-mm-dd/ - processed keys =item $HOME/.caff/gnupghome/ - caff's working directory for gpg =item $HOME/.caff/gnupghome/gpg.conf - gpg configuration (see B below) useful options include use-agent, keyserver, keyserver-options, default-cert-level, etc. =back =head1 CONFIGURATION FILE OPTIONS The configuration file is a perl script that sets values in the hash B<%CONFIG>. The file is generated when it does not exist. Example: $CONFIG{'owner'} = q{Peter Palfrader}; $CONFIG{'email'} = q{peter@palfrader.org}; $CONFIG{'keyid'} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ]; =head2 Required basic settings =over =item B [string] Your name. B. =item B [string] Your email address, used in From: lines. B. =item B [list of keyids] A list of your keys. This is used to determine which signatures to keep in the pruning step. If you select a key using B<-u> it has to be in this list. B. =back =head2 General settings =over =item B [string] Base directory for the files caff stores. Default: B<$HOME/.caff/>. =item B [hash] How to color output messages. See the C documentation for the list of supported colors; colored output can be disabled by setting this option to an empty hash B<{}>. Default: { error => 'bold bright_red' , warn => 'bright_red' , notice => 'bold' , info => '' , success => 'green' # used in combination with 'notice' and 'info' , fail => 'yellow' # used in combination with 'notice' and 'info' } =back =head2 GnuPG settings =over =item B [string] Path to the GnuPG binary. Default: The value of the I environment variable if set, otherwise C. =item B [string] Path to your secret keyring (GnuPG < 2.1), or to the GnuPGHOME of the agent managing the secret key material (GnuPG >= 2.1). Default: B<$HOME/.gnupg/secring.gpg>. If the value is not a directory with GnuPG >= 2.1, the parent directory (i.e., B<$HOME/.gnupg> by default) is considered instead. =item B [keyid, or list of keyids] Additional keyids to encrypt messages to. Default: none. =item B [string] The prefix to the "sign" command used to make the signature from gpg's shell. Can be set to a mix of "l" (local), "nr" (non-revocable) or "t" (trust) to make a signature of the given type. See gpg(1) for details. Default: "" (i.e., make a regular, exportable, signature). =item B [string] Additional commands to pass to gpg after the "sign" command. Default: none. =back =head2 Key import settings =over =item B [boolean] If true, then skip the step of fetching keys from the keyserver. Default: B<0>. =item B [list of files] A list of files containing keys to be imported. =back =head2 Signing settings =over =item B [boolean] If true, then skip the signing step. Default: B<0>. =item B [boolean] If true, then pause before continuing to the signing step. This is useful for offline signing. Default: B<0>. =item B [seconds] Don't export UIDs by default, on which your latest signature is older than this age. Default: B<24*60*60> (i.e. one day). =item B [keyid, or list of keyids] Select the key that is used for signing, in case you have more than one key. With multiple keyids, sign with each key in turn. =item B [auto|ask|no] Whether to locally sign the UIDs in the user's GnuPGHOME, in addition to caff's signatures in its own GnuPGHOME. Such signatures are not exportable. This can be useful when the recipient forgets to upload the signatures caff sent (or if they are non-exportable as well), as it gives a way to keep track of which UIDs were verified. However, note that local signatures will not be deleted once the recipient does the upload and the signer refreshes her keyring. If the value is not I and if B contains "l", each (local) signature is merely exported from caff's own GnuPGHOME to the user's. Otherwise, if the value is I, each UID signed in caff's own GnuPGHOME gets automatically locally signed in the user's, using the same certification level; this requires a working gpg-agent(1). If I, the user is prompted for which UIDs to locally sign. Default: B. =item B [boolean] If true, then before signing a key gpg will display the photos attached to it, if any. (The photo viewer can be specified with a "photo-viewer" option in caff's GnuPGHOME.) Default: B<0>. =back =head2 Mail settings =over =item B [yes|ask-yes|ask-no|no] Whether to send mails. This is a quad-option, with which you can set the behaviour: yes always sends, no never sends; ask-yes and ask-no asks, for each uid, with according defaults for the question. Default: B. In any case, the messages are also written to $CONFIG{'caffhome'}/keys/ =item B [yes|ask-yes|ask-no|no] The value of this option is considered instead of that of B for recipient keys without encryption capability. Default to the value of B. =item B [string] Sets the value of the "Subject:" header field. C<%k> will be expanded to the long key ID of the signed key. Default: C. =item B [string] Email template which is used as the body text for the email sent out instead of the default text if specified. The following perl variables can be used in the template: =over =item B<{owner}> [string] Your name as specified in the L|/item_owner__5bstring_5d> setting. =item B<{key}> [string] The keyid of the key you signed. =item B<{@uids}> [array] The UIDs for which signatures are included in the mail. =back Note that you should probably customize the template if you intend to send non-exportable signatures (i.e., if B contains "l"), as uploading such signatures doesn't make sense, and they require the import option "import-local-sigs" which isn't set by default. =item B [string] Add a Reply-To: header to messages sent. Default: none. =item B [string] Address to send blind carbon copies to when sending mail. Default: none. =item B [array] Parameters to pass to Mail::Mailer. Default: none. Setting this option is strongly discouraged: fix your local MTA instead. This could for example be $CONFIG{'mailer-send'} = [ 'smtp', Server => 'mail.server', Auth => ['user', 'pass'] ]; to use the perl SMTP client, or $CONFIG{'mailer-send'} = [ 'sendmail', '-f', $CONFIG{'email'}, '-it' ]; to pass arguments to the sendmail program. To specify a sendmail binary you can set the C<< PERL_MAILERS >> environment variable as follows: $ENV{'PERL_MAILERS'} = 'sendmail:/path/to/sendmail_compatible_mta'; For more information see Mail::Mailer(3pm). =back =head1 NOTES As noted above caff uses its own GnuPGHOME and GnuPG configuration file. In fact it only needs its own keyring for the signing work, but it would be unsafe to reuse the same GnuPG configuration file because the user could have set an option in $HOME/.gnupg/gpg.conf which would break caff. Therefore the GnuPG options that are intended to be used with caff, such as C or C, need to be placed in $HOME/.caff/gnupghome/gpg.conf instead. If this file does not exist, the GnuPG options found in $HOME/.gnupg/gpg.conf that are known to be safe (and useful) for caff, are passed to gpg(1) as command-line options. =head1 AUTHORS =over =item Peter Palfrader =item Christoph Berg =item Guilhem Moulin =back =head1 WEBSITE https://pgp-tools.alioth.debian.org/ =head1 SEE ALSO gpg(1), pgp-clean(1), /usr/share/doc/signing-party/caff/ =cut use strict; use warnings; use IO::Handle; use File::Copy qw{copy}; use File::Temp; use Text::Template; use MIME::Entity; use Encode (); use I18N::Langinfo qw{langinfo}; use Net::IDN::Encode qw{email_to_ascii domain_to_ascii}; use Fcntl; use IO::Select; use Getopt::Long; use GnuPG::Interface; use POSIX qw{strftime setlocale}; use Term::ANSIColor qw{colored}; my %CONFIG; my $REVISION = '$Rev: 893 $'; my ($REVISION_NUMER) = $REVISION =~ /(\d+)/; $REVISION_NUMER //= 'unknown'; my $VERSION = "0.0.0.$REVISION_NUMER"; my $LOCALE = Encode::find_encoding(langinfo(I18N::Langinfo::CODESET())); my $USER_AGENT = "caff $VERSION - https://pgp-tools.alioth.debian.org/"; # Global variables my @KEYIDS; my @LOCAL_USER; my $PARAMS; my $KEYSBASE; my $GNUPGHOME; ## # Display an error message on STDERR and then exit. # # @param $exitcode exit code status to use to end the program # @param $line error message to display on STDERR # sub mycolored($@) { my $msg = shift; my $color = join (' ', grep defined, map { defined $_ ? $CONFIG{colors}->{$_} : undef } @_) if defined $CONFIG{colors}; $msg = colored($msg, $color) if defined $color and $color !~ /^\s*$/; return $msg; } sub myerror($$) { my ($exitcode, $line) = @_; print STDERR mycolored("[ERROR] $line", 'error'), "\n"; exit $exitcode; } sub mywarn($) { my ($line) = @_; print STDERR mycolored("[WARN] $line", 'warn'), "\n"; } sub notice($;$) { my ($line,$color) = @_; $color = $color ? 'success' : 'fail' if defined $color; print STDERR mycolored("[NOTICE] $line", 'notice', $color), "\n"; } sub info($;$) { my ($line,$color) = @_; $color = $color ? 'success' : 'fail' if defined $color; print STDERR mycolored("[INFO] $line", 'info', $color), "\n"; } sub debug($) { my ($line) = @_; print STDERR "[DEBUG] $line\n" if $PARAMS->{debug}; } sub trace($) { my ($line) = @_; #print STDERR "[trace] $line\n"; } sub trace2($) { my ($line) = @_; #print STDERR "[trace2] $line\n"; } sub mysystem(@) { system { $_[0] } @_; myerror($?, "$_[0] exited with value ".($? >> 8)) if $?; } # Return -1 if the GnuPG version is < $_[0], 0 if == $_[0], 1 if > $_[0]. my $GNUPG_VERSION; sub GnuPG_version($) { unless (defined $GNUPG_VERSION) { # cache the version $GNUPG_VERSION = `$CONFIG{gpg} --no-options --with-colons --list-config version` or exit 1; chomp $GNUPG_VERSION; $GNUPG_VERSION =~ s/^cfg:version:// or die; debug "gpg (GnuPG) $GNUPG_VERSION"; } my @v1 = split /\./, $GNUPG_VERSION; my @v2 = split /\./, shift; while (@v1 or @v2) { my $v1 = shift @v1 // 0; my $v2 = shift @v2 // 0; my $r = $v1 <=> $v2; return $r unless $r == 0; } return 0; } sub gpgconf(@) { my $pid = open my $fh, '-|', 'gpgconf', @_; my %conf; while (<$fh>) { my ($k, $v) = split /:/, $_; chomp ($conf{$k} = $v); $conf{$k} =~ s/%(\p{AHex}{2})/ chr(hex($1)) /ge; # unescape the %-encoded chars } waitpid $pid, 0; myerror($?, "gpgconf exited with value ".($? >> 8)) if $?; close $fh; return \%conf; } # See RFC 5322 section 3.4.1; only the pattern for the local part, which # doesn't go beyond the ASCII range, is validated. The domain part is # NOT checked against RFC 5322, as it must be encoded to ASCII first; # for now any string in the full-range unicode that does not contain # U+0040 (commercial at), U+FE6B (small commercial at) and U+FF20 # (fullwidth commercial at) is accepted. my $RE_word = qr/[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x41-\x5A\x5E-\x7E]+ # atom: any ASCII CHAR except specials, SPACE and CTLs |\x22(?:[\x00-\x21\x23-\x5B\x5D-\x7E]|\x5C\p{ASCII})*\x22 # quoted string /x; my $RE_address_spec = qr/(?$RE_word(?:\.$RE_word)*)[\@\N{U+FE6B}\N{U+FF20}](?[^\@\N{U+FE6B}\N{U+FF20}]+)/o; # A domain label is a non-empty ASCII string of length at most 63 # characters (RFC 1035 2.3.4). Valid characters are alphanumeric and # hyphen '-', but an hyphen may not appear at the start or end of a # label (RFC 952, RFC 1123 2.1). my $RE_label = qr/[0-9a-z](?:[0-9a-z\x2D]{0,61}[0-9a-z])?/aai; # Take a 'mailbox' (RFC 5322 section 3.4) and return its ASCII-encoded # 'addr-spec'; or undef if it violates one of RFC 5322/5892/1035/5321. # We're not using Email::Valid because it's not unicode-friendly. # NOTE: This subroutine should only be used to extract e-mail addresses # from UIDs. The phrase is NOT checked against RFC 5322 (any string # containing only characters in the full-unicode printable range are # accepted), but we don't care as long as it's not used in email # headers. sub email_valid($) { local $_ = shift // return; return unless /\A$RE_address_spec\z/ao or # addr-spec /\A(?:\p{Print}*\p{Space})?<$RE_address_spec>\z/ao; # [phrase] "<" addr-spec ">" my ($l,$d) = @+{qw/l d/}; if ($d =~ /\P{ASCII}/) { # encode the IDN to ASCII using Punycode for RFC 5321 validation eval { $d = domain_to_ascii($d) }; return if $@; # violates RFC 5892 } my $address = "$l\@$d"; return unless length $d > 0 and length $d <= 255 # violates RFC 1035 2.3.4 "size limits" and length $l <= 64 # violates RFC 5321 4.5.3.1.1 and length $address <= 254 # violates RFC 5321 4.5.3.1.3 and $d =~ /\A$RE_label(?:\.$RE_label)+\z/o; # ignore non-FQDN return $address; } open NULL, '+<', '/dev/null'; my $NULL = fileno NULL; sub generate_config() { notice("Error: \$LOGNAME is not set", 0) unless defined $ENV{'LOGNAME'}; my $gecos = defined $ENV{'LOGNAME'} ? (getpwnam($ENV{LOGNAME}))[6] : undef; my $email; my @keys; # BSD does not have hostname -f, so we try without -f first my $hostname = `hostname`; $hostname = `hostname -f` unless $hostname =~ /\./; chomp $hostname; my ($Cgecos,$Cemail,$Ckeys) = ('','',''); if (defined $gecos) { $gecos =~ s/,.*//; $CONFIG{'gpg'} = $ENV{GNUPGBIN} // 'gpg'; my $gpg = mkGnuPG( extra_args => ['--with-colons'] ); my $handles = mkGnuPG_fds ( stdout => undef ); my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $gecos ]); my %output = readwrite_gpg($handles); waitpid $pid, 0; $handles->{stdout}->close; if ($output{stdout} eq '') { mywarn "No data from $CONFIG{gpg} for list-key"; # There should be at least 'tru:' everywhere. }; @keys = ($output{stdout} =~ /^pub:[^eir:]*:(?:[^:]*:){2}([0-9A-F]{16}):/mg); unless (scalar @keys) { notice("Error: No keys were found using \"$CONFIG{gpg} --list-public-keys '$gecos'\"", 0); @keys = qw{0123456789ABCDEF 89ABCDEF76543210}; $Ckeys = '#'; } my @emails = ($output{stdout} =~ /^uid:[^eir:]*:(?:[^:]*:){7}([^:]+)(?::.*)?$/mg); if (@emails) { s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge foreach @emails; @emails = grep defined, map {email_valid(Encode::decode_utf8($_))} @emails; $email = shift @emails; # take the first valid address } unless (defined $email) { notice("Error: No email address was found using \"$CONFIG{gpg} --list-public-keys '$gecos'\"", 0); $email = $ENV{'LOGNAME'}.'@'.$hostname; $Cemail = '#'; } } else { $gecos = 'Unknown Caff User'; $email = $ENV{'LOGNAME'}.'@'.$hostname; @keys = qw{0123456789ABCDEF 89ABCDEF76543210}; ($Cgecos,$Cemail,$Ckeys) = ('#','#','#'); }; my $template = < # # If you have a v4 key, it will simply be the last 16 digits of # your fingerprint. # # Example: # \$CONFIG{'keyid'} = [ qw{FEDCBA9876543210} ]; # or, if you have more than one key: # \$CONFIG{'keyid'} = [ qw{0123456789ABCDEF 89ABCDEF76543210} ]; $Ckeys\$CONFIG{'keyid'} = [ qw{@keys} ]; # Select this/these keys to sign with #\$CONFIG{'local-user'} = [ qw{@keys} ]; # Additionally encrypt messages for these keyids #\$CONFIG{'also-encrypt-to'} = [ qw{@keys} ]; # Mail template to use for the encrypted part #\$CONFIG{'mail-template'} = << 'EOM'; EOT $template .= "#$_" foreach ; $template .= "#EOM\n"; return $template; } my @GNUPGOPTS; sub load_config() { my $config = $ENV{'HOME'} . '/.caffrc'; unless (-f $config) { notice "No configfile $config present, I will use this template:"; my $template = generate_config(); print $template, "\n"; notice "Please edit $config and run caff again."; open F, '>', $config or myerror(1, "$config: $!"); print F $template; close F; exit(1); } unless (scalar eval `cat $config`) { myerror(1, "Couldn't parse $config: $@") if $@; }; myerror(1, "$0: $_ is not defined in $config") for grep {!defined $CONFIG{$_}} qw/owner email keyid/; myerror(1, "$0: keyid is not an array ref in $config") unless ref $CONFIG{'keyid'} eq 'ARRAY'; myerror(1, "$0: key $_ is not specified as a long (16 digit) keyid or fingerprint in $config") for grep !/^((?:0x)?\p{AHex}{16}|\p{AHex}{40}|(?:\p{AHex}{4} ){5}(?: \p{AHex}{4}){5})$/, @{$CONFIG{'keyid'}}; $CONFIG{'caffhome'} //= $ENV{'HOME'}.'/.caff'; $KEYSBASE = $CONFIG{'caffhome'}.'/keys'; $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome'; foreach ($CONFIG{'caffhome'}, $KEYSBASE, $GNUPGHOME) { next if -d $_; debug("Creating $_"); mkdir $_, 0700 or myerror(1, "Cannot mkdir $_: $!"); } @{$CONFIG{'keyid'}} = map { s/^0x//; uc (substr y/ //dr, -16) } @{$CONFIG{'keyid'}}; # must be a list of long keyids $CONFIG{'export-sig-age'} //= 24*60*60; $CONFIG{'gpg'} //= $ENV{GNUPGBIN} // 'gpg'; $CONFIG{'secret-keyring'} //= ($ENV{'GNUPGHOME'} // "$ENV{'HOME'}/.gnupg") . '/secring.gpg'; $CONFIG{'no-download'} //= 0; $CONFIG{'no-sign'} //= 0; $CONFIG{'key-files'} //= []; $CONFIG{'mailer-send'} //= []; myerror(1, "$0: mailer-send is not an array ref in $config") unless ref $CONFIG{'mailer-send'} eq 'ARRAY'; $CONFIG{'mail-subject'} //= "Your signed PGP key 0x%k"; $CONFIG{'mail-template'} //= do { local $/; }; $CONFIG{'also-encrypt-to'} = [ $CONFIG{'also-encrypt-to'} ] if defined $CONFIG{'also-encrypt-to'} and !ref $CONFIG{'also-encrypt-to'}; if (defined $LOCALE) { $CONFIG{$_} = $LOCALE->decode($CONFIG{$_}) for qw/owner mail-template mail-subject/; $CONFIG{$_} = email_to_ascii($LOCALE->decode($CONFIG{$_})) for grep {defined $CONFIG{$_}} qw/email bcc reply-to/; } $CONFIG{'gpg-sign-type'} //= ''; myerror(1, "$0: $CONFIG{'gpg-sign-type'} is an invalid signature type") unless $CONFIG{'gpg-sign-type'} =~ /^(?:l|nr|t)*$/; $CONFIG{'also-lsign-in-gnupghome'} //= 'no'; $CONFIG{'also-lsign-in-gnupghome'} = 'no' if $CONFIG{'no-sign'}; myerror(1, "$0: invalid value for 'also-lsign-in-gnupghome': $CONFIG{'also-lsign-in-gnupghome'}") unless grep { $_ eq $CONFIG{'also-lsign-in-gnupghome'} } qw/auto ask no/; $CONFIG{'show-photos'} //= 0; $CONFIG{colors} //= { error => 'bold bright_red', warn => 'bright_red', notice => 'bold', info => '', success => 'green', fail => 'yellow' }; # Import some options from ~/.gnupghome/gpg.conf. We don't symlink # the whole file because the user could have set an option in # ~/.gnupg/gpg.conf which would break caff. my $gpgconf = ($ENV{'GNUPGHOME'} // "$ENV{'HOME'}/.gnupg") . '/gpg.conf'; if ( ! -f "$GNUPGHOME/gpg.conf" and -f $gpgconf and open my $fh, '<', $gpgconf) { # the list of options to import from ~/.gnupg/gpg.conf; only # safe (and useful) options for caff should be listed here my @gnupgopts_n = qw/ disable-ccid ask-cert-level no-ask-cert-level use-agent no-use-agent no-random-seed-file no-greeting expert no-expert gnupg no-pgp2 pgp6 no-pgp6 pgp7 no-pgp7 pgp8 no-pgp8 rfc2440 rfc4880 openpgp default-comment no-comments emit-version no-emit-version no-version allow-weak-digest-algos ask-cert-expire no-ask-cert-expire utf8-strings no-utf8-strings /; my @gnupgopts_i = qw/ default-cert-level limit-card-insert-tries /; my @gnupgopts_s = qw/ photo-viewer exec-path pcsc-driver reader-port display-charset charset keyserver keyserver-options gpg-agent-info personal-cipher-preferences personal-digest-preferences comment cert-notation set-notation cert-policy-url set-policy-url cipher-algo cert-digest-algo pinentry-mode weak-digest default-cert-expire disable-cipher-algo disable-pubkey-algo agent-program dirmngr-program /; push @GNUPGOPTS, '--no-options'; notice('Importing GnuPG options from '.($ENV{'GNUPGHOME'} // '~/.gnupg').'/gpg.conf:'); while (<$fh>) { s/(?:\r\n|\r|\n)\z// or next; if (/\A\s*([0-9a-zA-Z\-]+)\z/ and grep { $_ eq $1 } @gnupgopts_n) { push @GNUPGOPTS, "--$1"; } elsif (/\A\s*([0-9a-zA-Z\-]+)\s+(\d+)\z/ and grep { $_ eq $1 } @gnupgopts_i) { push @GNUPGOPTS, "--$1=$2"; } elsif (/\A\s*([0-9a-zA-Z\-]+)\s+(\P{Control}+)\z/ and grep { $_ eq $1 } @gnupgopts_s) { push @GNUPGOPTS, "--$1=$2"; } else { next; } notice(" $_"); } close $fh; } # depreacted options, will be removed in a future release mywarn("Deprecated option \$CONFIG{'$_'} = '$CONFIG{$_}'") foreach grep {defined $CONFIG{$_}} qw{gpg-sign gpg-delsig keyserver}; } # Create a new GnuPG::Interface object with common options sub mkGnuPG(%) { my %h = @_; my $gpg = GnuPG::Interface::->new(); $gpg->call( $CONFIG{'gpg'} ); $h{meta_interactive} //= 0; $h{always_trust} //= 1; $h{extra_args} //= []; unshift @{$h{extra_args}}, '--no-auto-check-trustdb'; unshift @{$h{extra_args}}, '--fixed-list-mode' if GnuPG_version('2.0.0') < 0; unshift @{$h{extra_args}}, '--no-autostart' if GnuPG_version('2.1.0') >= 0; # never autostart unshift @{$h{extra_args}}, @GNUPGOPTS if @GNUPGOPTS and defined $h{homedir}; $gpg->options->hash_init(%h); debug(join (' ', $gpg->call(), $gpg->options->get_args(), "...")); return $gpg; } # Create a GnuPG::Handles object. This function takes a hash where keys # are handle names, and values are either IO::Handle objects, in which # case the existing handle is used, or undefined, in which case a new # IO::Handle is created. sub mkGnuPG_fds(%) { my %fd = @_; my @direct; foreach (keys %fd) { push @direct, $_ if defined $fd{$_} and $fd{$_} !~ /^[<>]&/; $fd{$_} //= IO::Handle::->new(); } # Redirect the STDIN and STDOUT to /dev/null unless explicitely # redirected. Also redirect logger to /dev/null in non-debug mode, # but NEVER redirect STDERR! $fd{stdin} = "<&=$NULL" unless exists $fd{stdin}; $fd{stdout} = ">&=$NULL" unless exists $fd{stdout}; $fd{logger} = ">&=$NULL" unless exists $fd{logger} or $PARAMS->{debug}; my $handles = GnuPG::Handles::->new(%fd); $handles->options($_)->{direct} = 1 foreach @direct; debug(join (', ', map {"$_: " . ($handles->options($_)->{direct} ? $fd{$_}->fileno : $fd{$_})} keys %fd)); return $handles; } sub done_gpg($;$) { my ($pid, $handles) = @_; waitpid $pid, 0; mywarn("$CONFIG{gpg} exited with value ".($? >> 8)) if $?; return unless defined $handles; foreach (GnuPG::Handles::HANDLES) { next unless defined $handles->{$_} and $handles->{$_} !~ /^[<>]&/; $handles->{$_}->close if $handles->{$_}->opened; } } # Send some data on GnuPG handles, and retrieve output from all handles # at once using select(2) syscalls. Stop when some output matches a # given regex, or when there nothing more to read or write. A newline # '\n' character is automatically appended to the text to be send to the # 'command' handle; the prefix "[GNUPG:] " to the 'status' handle is # added as well. sub readwrite_gpg($%) { my $handles = shift; my %opts = @_; # ignore direct and dup handles my @infhs = grep {defined $opts{$_} and !$handles->options($_)->{direct} and $handles->{$_} !~ /^[<>]&/} qw/stdin passphrase command/; my @outfhs = grep {defined $handles->{$_} and !$handles->options($_)->{direct} and $handles->{$_} !~ /^[<>]&/} qw/stdout stderr status logger/; my %fh = map { $handles->{$_} => $_ } (@infhs, @outfhs); my %offset = map {$_ => 0} @infhs; my %output = map {$_ => ''} @outfhs; if (defined $opts{command}) { # automatically send the command chomp $opts{command}; $opts{command} .= "\n"; } $opts{status} = qr/^\[GNUPG:\] $opts{status}$/m if defined $opts{status}; $handles->{$_}->blocking(0) foreach (@infhs, @outfhs); my $sin = IO::Select::->new(map {$handles->{$_}} @infhs); my $sout = IO::Select::->new(map {$handles->{$_}} @outfhs); trace("entering readwrite_gpg."); trace("doing stuff until one of: ". join(', ', map {"$_ =~ $opts{$_}"} grep {defined $opts{$_}} @outfhs)) if grep {defined $opts{$_}} @outfhs; my $readwrote_stuff_this_time = 0; my $do_not_wait_on_select = 0; while ($sin->count() + $sout->count() > 0) { if (!$sin->count() and grep {defined $opts{$_} and $output{$_} =~ $opts{$_}} @outfhs) { if ($readwrote_stuff_this_time) { trace("read/write some more."); $do_not_wait_on_select = 1; } else { trace("that's it in our while loop."); last; } }; trace("select waiting for ".($sin->count()+$sout->count())." fds."); my ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1); trace("ready: write: ". join (',', map {$fh{$_}} @{$readyw // []}). "; read: ". join (',', map {$fh{$_}} @{$readyr // []})); $readwrote_stuff_this_time = 0; for my $fd (@{$readyw // []}) { $readwrote_stuff_this_time = 1; my $fh = $fh{$fd}; if ($offset{$fh} != length $opts{$fh}) { trace ("writing to '$fh'". ($offset{$fh} ? "" : ": ".(split /\n/, $opts{$fh}, 2)[0])); my $written = $fd->syswrite($opts{$fh}, length($opts{$fh}) - $offset{$fh}, $offset{$fh}); $offset{$fh} += $written; } if ($offset{$fh} == length $opts{$fh}) { trace "done writing to '$fh'."; $sin->remove($fd); $fd->close && trace "closed '$fh'." if $opts{autoclose}; } } for my $fd (@{$readyr // []}) { $readwrote_stuff_this_time = 1; my $fh = $fh{$fd}; if ($fd->eof) { trace "done reading from '$fh'."; $sout->remove($fd); next; } trace "reading from '$fh'."; $output{$fh} .= do { local $/; <$fd> }; trace2 "$fh is now:\n$output{$fh}\n================"; } } trace("readwrite_gpg done."); return %output; } sub ask($$;$$) { my ($question, $default, $forceyes, $forceno) = @_; my $answer; my $yn = $default ? '[Y/n]' : '[y/N]'; while (1) { print $question,' ',$yn, ' '; if ($forceyes && $forceno) { print "$default (from config/command line)\n"; return $default; }; if ($forceyes) { print "YES (from config/command line)\n"; return 1; }; if ($forceno) { print "NO (from config/command line)\n"; return 0; }; $answer = ; chomp $answer; last if ((length $answer == 0) || ($answer =~ m/^[yYnN]$/) ); print "What about $yn is so hard to understand?\nAnswer with either 'n' or 'y' or just press enter for the default.\n"; sleep 1; }; my $result = $default; $result = 1 if $answer =~ /y/i; $result = 0 if $answer =~ /n/i; return $result; } my $KEYEDIT_PROMPT = qr/GET_LINE keyedit\.prompt/; my $KEYEDIT_DELUID_PROMPT = qr/GET_BOOL keyedit\.remove\.uid\.okay/; my $KEYEDIT_DELSIG_PROMPT = qr/GET_BOOL keyedit\.delsig\.(?:unknown|invalid|valid)/; # we won't delete selfsigs my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = qr/$KEYEDIT_PROMPT|$KEYEDIT_DELSIG_PROMPT/; my $KEYEDIT_DELSUBKEY_PROMPT = qr/GET_BOOL keyedit\.remove\.subkey\.okay/; my $KEYEDIT_SIGNUID_CLASS_PROMPT = qr/GET_LINE sign_uid\.class/; my $KEYEDIT_SIGNUID_PROMPT = qr/GET_BOOL sign_uid\.okay/; sub version($) { my ($fd) = @_; print $fd "$0 $VERSION - (c) 2004, 2005, 2006 Peter Palfrader et al.\n"; } sub usage($$) { my ($fd, $exitcode) = @_; version($fd); print $fd "Usage: $0 [-eERS] [-m ] [-u ] [ ...]\n"; print $fd "Consult the manual page for more information.\n"; exit $exitcode; } ###### # export keys @$keyids from $gnupghome. In list context, fork and # return the pid and the file descriptor of its standard output; # otherwise, wait until the export is done and return the ASCII key. # # /!\ Failure to export a key will not be detected, unless *all* keys # couldn't be exported. Therefore for safe export/import, you need # to inspect '$asciikey' or the status FD on the import side. ###### sub export_keys($$@) { my ($gnupghome, $keyids, @export_options) = @_; myerror(1, "Nothing to export") unless defined $keyids and @$keyids; my @extra_args = ('--export-options', join (',', @export_options)) if @export_options; # don't armor when piping since it's faster my $gpg = mkGnuPG( homedir => $gnupghome, armor => (wantarray ? 0 : 1), extra_args => \@extra_args ); my $handles = mkGnuPG_fds( stdout => undef ); my $pid = $gpg->export_keys( handles => $handles, command_args => $keyids ); if (wantarray) { return ($pid, $handles->{stdout}); } else { my $asciikey = do { local $/; readline $handles->{stdout} }; done_gpg($pid, $handles); return $asciikey; } } ###### # Create an email to $address. If $can_encrypt is true then the mail # will be PGP/MIME encrypted to $longkeyid. # # $longkeyid, $uid, and @attached will be used in the email and the template. ###### # create_mail($address, $can_encrypt, $longkeyid, $uid, @attached); sub create_mail($$$@) { my ($address, $can_encrypt, $key_id, @keys) = @_; my $template = Text::Template::->new(TYPE => 'STRING', SOURCE => $CONFIG{'mail-template'}); myerror(1, "Cannot create template: $Text::Template::ERROR") unless defined $template; my $message = $template->fill_in(HASH => { key => $key_id, uids => [ map {$_->{'text'}} @keys ], owner => $CONFIG{'owner'}}); myerror(1, "Cannot fill in template: $Text::Template::ERROR") unless defined $message; my $message_entity = MIME::Entity->build( Type => "text/plain", Charset => "utf-8", Disposition => 'inline', Data => Encode::encode_utf8($message)); my @key_entities; for my $key (@keys) { $message_entity->attach( Type => "application/pgp-keys", Disposition => 'attachment', Encoding => "7bit", Description => "PGP Key 0x$key_id, uid ".Encode::encode_utf8($key->{text})." ($key->{serial}), signed by 0x$CONFIG{keyid}[0]", Data => $key->{key}, Filename => "0x$key_id.$key->{serial}.signed-by-0x$CONFIG{keyid}[0].asc"); }; if ($can_encrypt) { my $gpg = mkGnuPG( homedir => $GNUPGHOME, armor => 1, textmode => 1 ); $gpg->options->push_recipients($key_id); $gpg->options->push_recipients(@{$CONFIG{'also-encrypt-to'}}) if defined $CONFIG{'also-encrypt-to'}; my $handles = mkGnuPG_fds( stdin => undef, stdout => undef, status => undef ); my $pid = $gpg->encrypt(handles => $handles); my %output = readwrite_gpg($handles, stdin => $message_entity->stringify(), autoclose => 1); done_gpg($pid, $handles); my ($message, $status) = @output{qw/stdout status/}; if ($message eq '') { if ($status =~ /^\[GNUPG:\] INV_RECP ([0-9]+) ([0-9A-F]+)$/m and defined $CONFIG{'also-encrypt-to'}) { my $reason = $1; my $keyid = $2; if (grep { $_ eq $keyid } @{$CONFIG{'also-encrypt-to'}}) { mywarn "Could not encrypt to $keyid, specified in CONFIG{'also-encrypt-to'}"; mywarn "Try to update the key using gpg --homedir=$GNUPGHOME --import "; return; }; }; mywarn "No data from gpg for encrypting mail; status output was:\n$status"; return; }; $message_entity = MIME::Entity->build( Type => 'multipart/encrypted; protocol="application/pgp-encrypted"', Encoding => '7bit' ); $message_entity->attach( Type => "application/pgp-encrypted", Filename => "signedkey.msg", Disposition => 'attachment', Encoding => "7bit", Data => "Version: 1\n" ); $message_entity->attach( Type => "application/octet-stream", Filename => 'msg.asc', Disposition => 'inline', Encoding => "7bit", Data => $message ); }; my $from = Encode::encode('MIME-Q', $CONFIG{owner})." <$CONFIG{email}>"; $message_entity->head->add("From", $from); $message_entity->head->add("Date", strfCtime("%a, %e %b %Y %H:%M:%S %z", localtime)); $message_entity->head->add("Subject", Encode::encode('MIME-Q', $CONFIG{'mail-subject'} =~ s/%k/$key_id/gr)); $message_entity->head->add("To", $address); $message_entity->head->add("Sender", $from); $message_entity->head->add("Reply-To", $CONFIG{'reply-to'}) if defined $CONFIG{'reply-to'}; $message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'}; $message_entity->head->add("User-Agent", $USER_AGENT); return $message_entity; } ###### # send a mail message (MIME::Entity) ###### my $warned_about_broken_mailer_send = 0; sub send_message($) { my ($message_entity) = @_; if ((scalar @{$CONFIG{'mailer-send'}} > 0) && !$warned_about_broken_mailer_send) { mywarn("You have set arguments to pass to Mail::Mailer. Better fix your MTA. (Also, Mail::Mailer's error reporting is non existant, so it won't tell you when it doesn't work.)"); $warned_about_broken_mailer_send = 1; }; $message_entity->send(@{$CONFIG{'mailer-send'}}); } ###### # clean up a UID so that it can be used on the FS. ###### sub sanitize_uid($) { my ($uid) = @_; my $good_uid = $uid; $good_uid =~ tr#/:\\#_#; trace2("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid; return $good_uid; } # Delete all non self-sigs that are not made by one of the @$keyids, and # return the date of the most recent signature and a hash reference # {$signer => $level} of the keys in @$keyids that have an exportable # signature on that $uid. If $keep_lsigs_only, our exportable # signatures are removed as well. sub delete_signatures($$$$;$) { my ($handles, $longkeyid, $uid, $keyids, $keep_lsigs_only) = @_; readwrite_gpg($handles, command => "uid 0", status => $KEYEDIT_PROMPT); # unmark all uids from delsig readwrite_gpg($handles, command => "uid $uid", status => $KEYEDIT_PROMPT); # mark $uid for delsig my $last_signed_on = 0; my %xsigners; my %output = readwrite_gpg($handles, command => "delsig", status => $KEYEDIT_DELSIG_PROMPT); while($output{status} =~ /$KEYEDIT_DELSIG_PROMPT/m) { # sig:?::17:EA2199412477CAF8:1058095214:::::13x my @sigline = grep /^sig:/, (split /\n/, $output{stdout}); my $answer = "no"; if (!@sigline) { debug("[sigremoval] no sig line here, only got:\n".$output{stdout}); } else { # only if we found a sig here - we never remove revocation packets for instance my $sig = pop @sigline; $sig =~ /^sig:(?:[^:]*:){3}([0-9A-F]{16}):(\d+):(?:[^:]*:){4}(1[0-3]|30)([lx])(?::.*)?$/ or mywarn("I hit a bug, please report: Couldn't parse sigline $sig"); debug("[sigremoval] doing sigline $sig"); if ($1 eq $longkeyid) { debug("[sigremoval] selfsig ($1)"); $answer = "no"; } elsif (grep { $1 eq $_ } @$keyids and $3 != 30) { debug("[sigremoval] signed by us ($1)"); $answer = ($keep_lsigs_only and $4 eq 'x') ? "yes" : "no"; $last_signed_on = $2 if $last_signed_on < $2; $xsigners{$1} = $3-10 if $4 eq 'x'; } else { debug("[sigremoval] not interested in that sig ($1)"); $answer = "yes"; }; mywarn("I hit a bug, please report: Found the following ".($#sigline+2)." siglines in that part of the dialog:\n".$output{stdout}) if @sigline; } %output = readwrite_gpg($handles, command => $answer, status => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT); } return ($last_signed_on, \%xsigners); } ## # Check the local user keys. # # This function checks if the keyids defined through the --local-user # command line option or set in ~/.caffrc are valid and known to be one of the # keyids listed in ~/.caffrc. # # @return an array containing the local user keys\n # (undef) if no valid key has been found # sub get_local_user_keys() { # No user-defined key id has been specified by the user, no need for # further checks return @{$CONFIG{'keyid'}} unless $CONFIG{'local-user'}; # Parse the list of keys my @key_list = ref $CONFIG{'local-user'} ? @{$CONFIG{'local-user'}} : split /\s*,\s*/, $CONFIG{'local-user'}; my @local_user; # Check every key defined by the user... for my $user_key (@key_list) { unless ($user_key =~ m/^((?:0x)?\p{AHex}{8}|(?:0x)?\p{AHex}{16}|\p{AHex}{40}|(?:\p{AHex}{4} ){5}(?: \p{AHex}{4}){5})$/) { mywarn "Local-user $user_key is not a valid keyid"; next; } $user_key =~ s/^0x//; $user_key =~ y/ //d; if (my @matching_keyids = grep {$user_key =~ /\Q$_\E$/i or /\Q$user_key\E$/i} @{$CONFIG{'keyid'}}) { push @local_user, @matching_keyids; # @{$CONFIG{'keyid'}} is always a list of long keyids } else { mywarn "Local-user $user_key is not defined as one of your keyid in ~/.caffrc (it will not be used)"; } } # If no local-user key are valid, there is no need to go further myerror(1, "None of the local-user keys seem to be known as a keyid listed in ~/.caffrc") unless @local_user; return @local_user; } ## # Import keys from a gnupghome to another. # # @param keyids keyids of the OpenPGP keys to import # @param src_gnupghome gnupghome directory where to export the key from # @param dst_gnupghome gnupghome directory where to import the key into # @param die_on_error whether to die if some of the keyids couldn't be imported # @param import_options a list of import-options, see gpg(1) # # @ return a hash reference mapping each key ID to the list of matching # imported key fingerprint. # sub import_keys_from_gnupghome($$$$@) { my ($keyids, $src_gpghome, $dst_gpghome, $die_on_error, @import_options) = @_; my $src = $src_gpghome // "your normal GnuPGHOME"; my $dst = $dst_gpghome // "your normal GnuPGHOME"; my @extra_args; push @import_options, 'import-local-sigs' if $CONFIG{'gpg-sign-type'} =~ /l/ and !grep /import-local-sigs$/, @import_options; push @import_options, 'keep-ownertrust' unless defined $dst_gpghome or GnuPG_version('2.1.0') >= 0; # don't modify our own trustdb push @extra_args, '--min-cert-level=1' if grep { $_ eq 'import-clean' } @import_options; push @extra_args, '--import-options', join (',', @import_options) if @import_options; # export the (non-armored) keys to $pipe debug("Exporting key(s) ".(join ',', @$keyids)." from $src to $dst"); my @export_options = ('export-local-sigs') if grep {$_ eq 'import-local-sigs'} @import_options; my ($ePid, $pipe) = export_keys($src_gpghome, $keyids, @export_options); my $gpg = mkGnuPG( homedir => $dst_gpghome, quiet => 1, extra_args => \@extra_args ); my $handles = mkGnuPG_fds( stdin => $pipe, status => undef ); # import keys from $pipe my $iPid = $gpg->import_keys( handles => $handles ); my $status = import_loop($handles->{status}, defined $src_gpghome ? 0 : 1, $keyids); done_gpg($iPid, $handles); # import done waitpid $ePid => 0; # export done my @failed = grep { !@{$status->{$_}} } keys %$status; if (@failed) { my $msg = "Couldn't import key(s) ".(join ',', @failed)." from $src"; $die_on_error ? myerror(1, $msg) : info($msg, 0); } return $status; } ## # Import loop. # # @param fh the status file handle from GnuPG::Interface # @param verbose whether to list the status of each key as it # is being imported. # @param keyids an array of keyids to be imported # @param ignore_unexpected whether not to print a warning upon receiving # an unexpected key # # @ return a hash reference mapping each key ID to the list of matching # imported key fingerprint. # sub import_loop($$$;$) { my ($fh, $verbose, $keyids, $ignore_unexpected) = @_; # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F # [GNUPG:] NODATA 1 # [GNUPG:] NODATA 1 # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039 my %status = map { $_ => [] } @$keyids; while (<$fh>) { # inspect the $status FD as data gets out if (/^\[GNUPG:\] IMPORT_OK (\d+) ([0-9A-F]{40})$/) { my ($r, $fpr) = ($1, $2); my @matching_keyids = grep { $fpr =~ /\Q$_\E$/ } @$keyids; unless (@matching_keyids) { unless ($ignore_unexpected) { mywarn("Imported unexpected key $fpr. Are you trying to work on a subkey?"); } elsif ($verbose) { info( "Key $fpr ". ($r == 0 ? 'not changed' : 'imported'), ($r == 0 ? undef : 1) ); } next; } debug( "Imported $fpr for ".join(',', @matching_keyids)); info( "Key " .join(',', @matching_keyids).' '. ($r == 0 ? 'not changed' : 'imported'), ($r == 0 ? undef : 1) ) if $verbose; push @{$status{$_} //= []}, $fpr foreach @matching_keyids; } elsif (/^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{32})$/) { mywarn("Imported v3 key $1. Version 3 keys are obsolete, should not be used, and are not and will not be properly supported."); } elsif (!/^\[GNUPG:\]\ (?:NODATA\ \d | IMPORT_RES\ .+ | IMPORTED\ .+ | KEYEXPIRED\ \d+ | SIGEXPIRED\ (?:\ deprecated-use-keyexpired-instead)? | KEY_CONSIDERED\ [0-9A-F]{40}\ \d+ | FAILURE\ recv-keys\ \d+ )$/x) { mywarn("Got unknown reply from gpg: ".$_); } } return \%status; } ## # Import keys to be signed into caff gnupghome directory. # # This function imports the keys the user wants to sign into the caff gnupghome # directory. We looks for the keys in the the user gnupghome directory first, # and in the key files specified by the user if not all of the keys have been # found. # sub import_keys_to_sign($) { my $keyids = shift; return unless $CONFIG{'keys-from-gnupg'} or @{$CONFIG{'key-files'}} or !$CONFIG{'no-download'}; # map each keyid to a list of matching fingerprints; there is a # collision if a keyid is mapped to multiple fingerprints, but we'll # detect that later in the code my $status = { map { $_ => [] } @$keyids }; if ($CONFIG{'keys-from-gnupg'}) { notice("Importing keys from your normal GnuPGHOME (".($ENV{'GNUPGHOME'} // "~/.gnupg").")"); merge_import_status( $status, import_keys_from_gnupghome($keyids, undef, $GNUPGHOME, 0) ); } foreach my $keyfile (@{$CONFIG{'key-files'}}) { notice("Importing key file $keyfile"); my $gpg = mkGnuPG( homedir => $GNUPGHOME, quiet => 1 ); $gpg->options->push_extra_args(qw/--import-options import-local-sigs/) if $CONFIG{'gpg-sign-type'} =~ /l/; my $handles = mkGnuPG_fds( status => undef ); my $pid = $gpg->import_keys( handles => $handles, command_args => $keyfile ); merge_import_status( $status, import_loop($handles->{status}, 1, $keyids, 1) ); done_gpg($pid, $handles); } # Receive keys from keyserver unless ($CONFIG{'no-download'}) { notice("Fetching keys from a keyserver (this may take a while)..."); my @args = (extra_args => ['--keyserver='.$CONFIG{'keyserver'}]) if defined $CONFIG{'keyserver'}; my $gpg = mkGnuPG( homedir => $GNUPGHOME, @args ); # logger: requesting key ... from hkp # stdout: gpgkeys: key ... not found on keyserver my $handles = mkGnuPG_fds( status => undef ); my $pid = $gpg->recv_keys(handles => $handles, command_args => $keyids); my $s = import_loop($handles->{status}, 1, $keyids); merge_import_status($status, $s); done_gpg($pid, $handles); my @failed = grep { !@{$s->{$_}} } keys %$s; info("Couldn't import key(s) ".(join ',', @failed)." from the keyserver", 0) if @failed; } my @failed = grep { !@{$status->{$_}} } keys %$status; if (@failed) { exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0); mywarn("Assuming ". join(' ', @failed).' '.($#failed > 0 ? 'are' : 'is a')." fine keyid".($#failed > 0 ? 's' : '')); } } sub merge_import_status($$) { foreach my $keyid (keys %{$_[1]}) { push @{$_[0]->{$keyid} //= []}, @{$_[1]->{$keyid}}; } } ## # A non-localized version of POSIX::strftime. # sub strfCtime($@) { my $lc_time = setlocale(POSIX::LC_TIME); setlocale(POSIX::LC_TIME, 'C'); my $str = strftime(@_); setlocale(POSIX::LC_TIME, $lc_time); return $str; } ################### # argument handling ################### Getopt::Long::config('bundling'); if (!GetOptions ( '-h' => \$PARAMS->{'help'}, '--help' => \$PARAMS->{'help'}, '--version' => \$PARAMS->{'version'}, '-V' => \$PARAMS->{'version'}, '-u=s' => \$PARAMS->{'local-user'}, '--local-user=s' => \$PARAMS->{'local-user'}, '-e' => \$PARAMS->{'export-old'}, '--export-old' => \$PARAMS->{'export-old'}, '-E' => \$PARAMS->{'no-export-old'}, '--no-export-old' => \$PARAMS->{'no-export-old'}, '-m:s' => \$PARAMS->{'mail'}, '--mail:s' => \$PARAMS->{'mail'}, '-M' => \$PARAMS->{'no-mail'}, '--no-mail' => \$PARAMS->{'no-mail'}, '-R' => \$PARAMS->{'no-download'}, '--no-download' => \$PARAMS->{'no-download'}, '-S' => \$PARAMS->{'no-sign'}, '--no-sign' => \$PARAMS->{'no-sign'}, '--key-file=s@' => \$PARAMS->{'key-files'}, '--keys-from-gnupg' => \$PARAMS->{'keys-from-gnupg'}, '--debug' => \$PARAMS->{'debug'}, )) { usage(\*STDERR, 1); } if ($PARAMS->{'help'}) { usage(\*STDOUT, 0); } if ($PARAMS->{'version'}) { version(\*STDOUT); exit(0); } load_config(); my $NOW = time; my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW); my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday); if (-t \*STDIN) { # we're already talking to a TTY usage(\*STDERR, 1) unless @ARGV; *TTY = *STDIN; } else { my @checksums; my $goodblock; my $got_input; # detect xargs, /dev/null, ... while () { unless ($got_input) { notice("Reading gpgparticipants formatted input on STDIN"); $got_input = 1; } if (/^(\S+)\s+Checksum:\s+[_ 0-9A-F]+(?:\s+\[(.)\])?$/i) { # ensure the checksum is (claimed to be) verified my ($md, $r) = ($1, $2); while (!defined $r) { $_ = ; if (/^\s+[_ 0-9A-F]+\s+\[(.)\]$/i) { $r = $1; } elsif (!/^(:?\s+[_ 0-9A-F]+)?$/i) { myerror(1, "Unexpected input line: $_"); } } myerror(1, "$md checksum wasn't marked as verified!") unless lc $r eq 'x'; notice "Found $md checksum (marked as verified, assumed good)"; push @checksums, uc $md; } elsif (/^(?:-+|_+)$/) { $goodblock = 0; } elsif (/^(#*)\d*\s+\[(.)\] Fingerprint(?:\(s\)|s)? OK\s+\[(.)\] ID OK\s*$/) { $goodblock = (!$1 and lc $2 eq 'x' and lc $3 eq 'x') ? 1 : 0; } elsif (/^ {5,}Key fingerprint = ([A-F0-9]{32}|(?:[A-F0-9]{2} ){8}(?: [A-F0-9]{2}){8})$/) { mywarn("Ignoring v3 fingerprint ".($1 =~ y/ //dr).". v3 keys are obsolete."); } elsif (/^ {5,}(?:Key fingerprint = )?([A-F0-9]{40}|(?:[A-F0-9]{4} ){5}(?: [A-F0-9]{4}){5})$/) { my $fpr = ($1 =~ y/ //dr); if ($goodblock) { info("Adding fingerprint $fpr", 1); push @KEYIDS, $fpr; } else { info("Ignoring fingerprint $fpr", 0); } } } if ($got_input) { if (!@checksums) { mywarn "No checksum found!"; } elsif (!grep { my $x = $_; grep { $x eq $_ } qw/SHA256 SHA384 SHA512 SHA224/ } @checksums) { mywarn "No checksum of the SHA-2 family found!"; } } close STDIN; open TTY, '<', '/dev/tty' or myerror(1,"No TTY.") } for my $hashkey (qw{local-user no-download no-sign no-mail mail keys-from-gnupg}) { $CONFIG{$hashkey} = $PARAMS->{$hashkey} if defined $PARAMS->{$hashkey}; } # If old 'no-mail' parameter, or if the 'mail' parameter is set to 'no' if ( defined $CONFIG{'no-mail'} || (defined $CONFIG{'mail'} && $CONFIG{'mail'} eq 'no') ) { $CONFIG{'mail'} = 'no'; } elsif ( !defined $CONFIG{'mail'} ) { $CONFIG{'mail'} = 'ask-yes'; } $CONFIG{'mail-cant-encrypt'} //= $CONFIG{'mail'}; push @{$CONFIG{'key-files'}}, @{$PARAMS->{'key-files'}} if defined $PARAMS->{'key-files'}; for my $keyid (map { split /\n/ } @ARGV) { # caff "`cat txt`" is a single argument if ($keyid =~ /^(\p{AHex}{32}|(?:\p{AHex}{2} ){8}(?: \p{AHex}{2}){8})$/) { mywarn("Ignoring v3 fingerprint ".($keyid =~ y/ //dr).". v3 keys are obsolete."); next; } elsif ($keyid !~ /^((?:0x)?\p{AHex}{8}|(?:0x)?\p{AHex}{16}|\p{AHex}{40}|(?:\p{AHex}{4} ){5}(?: \p{AHex}{4}){5})$/) { print STDERR "$keyid is not a keyid.\n"; usage(\*STDERR, 1); } $keyid =~ s/^0x//; $keyid =~ y/ //d; # gpg --fingerprint includes spaces push @KEYIDS, uc($keyid); } if (GnuPG_version('2.1.0') >= 0) { my %sockets; unless ($CONFIG{'no-sign'}) { # Ensure we have a working agent for our secret key material my $secdir = $CONFIG{'secret-keyring'}; $secdir =~ s#/[^/]+$## unless -d $secdir; mysystem('gpg-connect-agent', '--homedir', $secdir, '/bye'); $sockets{'agent-socket'} = GnuPG_version('2.1.13') < 0 ? # gpgconf < 2.1.13 doesn't understand --homedir; but on # these versions the gpg-agent socket path is always # $GNUPGHOME/S.gpg-agent. "$secdir/S.gpg-agent" : gpgconf('--homedir', $secdir, '--list-dirs')->{'agent-socket'}; } unless ($CONFIG{'no-download'}) { # Ensure we have a working agent for the downloads my $homedir = $ENV{'GNUPGHOME'} // "$ENV{'HOME'}/.gnupg"; mysystem('gpg-connect-agent', '--homedir', $homedir, '--dirmngr', '/bye'); $sockets{'dirmngr-socket'} = GnuPG_version('2.1.13') < 0 ? # gpgconf < 2.1.13 doesn't understand --homedir; but on # these versions the gpg-agent socket path is always # $GNUPGHOME/S.dirmngr. "$homedir/S.dirmngr" : gpgconf('--homedir', $homedir, '--list-dirs')->{'dirmngr-socket'}; } foreach my $k (keys %sockets) { my $socket = $sockets{$k}; my $l = GnuPG_version('2.1.13') < 0 ? $socket =~ s#.*/#$GNUPGHOME/#r : gpgconf('--homedir', $GNUPGHOME, '--list-dirs')->{$k}; if (-l $l) { unlink $l } elsif (-S $l) { # don't run agents in caff's homedir myerror(1, "$l: socket exists; runaway gpg-agent?"); } elsif (! -S $socket) { myerror(1, "Missing socket $socket"); } debug "Creating symlink $l to $socket"; symlink $socket, $l or myerror(1, "Cannot symlink: $!"); } } elsif ($CONFIG{'also-lsign-in-gnupghome'} eq 'auto' and $CONFIG{'gpg-sign-type'} !~ /l/) { # Ensure there is a working gpg-agent if $CONFIG{'also-lsign-in-gnupghome'} is 'auto' system qw/gpg-agent -q/; unless ($? == 0) { mywarn("No gpg-agent running: set \$CONFIG{'also-lsign-in-gnupghome'} = 'ask'"); $CONFIG{'also-lsign-in-gnupghome'} = 'ask'; } } ################################## # import own keys and keys to sign ################################## import_keys_from_gnupghome($CONFIG{'keyid'}, undef, $GNUPGHOME, 1); import_keys_from_gnupghome($CONFIG{'also-encrypt-to'}, undef, $GNUPGHOME, 0) if defined $CONFIG{'also-encrypt-to'}; @LOCAL_USER = get_local_user_keys() unless $CONFIG{'no-sign'}; import_keys_to_sign(\@KEYIDS); if ($CONFIG{'ask-sign'} && ! $CONFIG{'no-sign'}) { $CONFIG{'no-sign'} = ! ask("Continue with signing?", 1); } my %KEYS; for my $keyid (@KEYIDS) { # get key listing (and ensure there is no collision) #################################################### my $gpg = mkGnuPG( homedir => $GNUPGHOME, extra_args => ['--with-fingerprint', '--with-colons'] ); my $handles = mkGnuPG_fds( stdout => undef ); # process the keys one by one so we can detect collisions my $pid = $gpg->list_public_keys( handles => $handles, command_args => [$keyid] ); my @matching_keys; while (readline $handles->{stdout}) { if (/^pub:([^:]+):(?:[^:]*:){2}([0-9A-F]{16}):(?:[^:]*:){6}([^:]+)/) { push @matching_keys, { validity => $1, longkeyid => $2, capability => $3, uids => [], subkeys => [] }; } elsif (/^fpr:(?:[^:]*:){8}([0-9A-F]{40}|[0-9A-F]{32})(?::.*)?$/) { $matching_keys[$#matching_keys]->{fpr} //= $1; } elsif (/^sub:[^:]+:(?:[^:]*:){2}([0-9A-F]{16}):/) { push @{$matching_keys[$#matching_keys]->{subkeys}}, $1; } elsif (/^(uid|uat):([^:]+):(?:[^:]*:){5}([0-9A-F]{40}):[^:]*:([^:]+)/) { my $uid = { type => $1 , validity => $2 , hash => $3 , text => $1 eq 'uid' ? $4 : '[attribute]' }; $uid->{text} =~ s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge; # --with-colons always outputs UTF-8 $uid->{text} = Encode::decode_utf8($uid->{text}); $uid->{address} = email_valid $uid->{text} if $uid->{type} eq 'uid'; push @{$matching_keys[$#matching_keys]->{uids}}, $uid; } elsif (!/^(?:rvk|tru):/) { chomp; mywarn("Got unknown reply from gpg: ".$_); } } done_gpg($pid, $handles); unless (@matching_keys) { mywarn("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME)"); next; } my $key; foreach (@matching_keys) { my $reason = $_->{fpr} =~ /^\p{AHex}{32}$/ ? 'obsolete v3' : $_->{validity} =~ /e/ ? 'expired' : $_->{validity} =~ /i/ ? 'invalid' : $_->{validity} =~ /r/ ? 'revoked' : $_->{capability} =~ /D/ ? 'disabled' : do { $key = $_; last }; mywarn("Ignoring $reason key $_->{fpr}"); } mywarn( "More than one key matched $keyid (assuming $key->{fpr}). " . "Try to specify the long keyid or full fingerprint to avoid collisions.") if $#matching_keys > 0 and defined $key; if (defined $key) { $KEYS{$keyid} = $key; } else { my $msg = "public key found with list-key $keyid"; mywarn( @matching_keys ? "No valid $msg" : "No $msg (note that caff uses its own keyring in $GNUPGHOME)" ); } } unless (keys %KEYS) { notice("No keys to sign found", 0); exit 0; } for my $keyid (@KEYIDS) { next unless exists $KEYS{$keyid}; my ($longkeyid, $fpr) = @{$KEYS{$keyid}}{qw/longkeyid fpr/}; ########### # sign keys ########### unless ($CONFIG{'no-sign'}) { notice("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key"); foreach my $local_user (@LOCAL_USER) { my @command = ($CONFIG{'gpg'}); push @command, "--homedir=$GNUPGHOME"; push @command, @GNUPGOPTS if @GNUPGOPTS; push @command, '--secret-keyring', $CONFIG{'secret-keyring'} if GnuPG_version('2.1.0') < 0; push @command, qw/--no-auto-check-trustdb --trust-model=always/; push @command, '--local-user', $local_user; push @command, '--edit-key', $fpr; push @command, 'showphoto' if $CONFIG{'show-photos'}; push @command, $CONFIG{'gpg-sign-type'}.'sign'; push @command, split ' ', $CONFIG{'gpg-sign-args'} || ""; print join(' ', @command),"\n"; mysystem(@command); }; }; ################## # export and prune ################## # export the key ################ my $keydir = File::Temp->newdir( "caff-$keyid-XXXXX", TMPDIR => 1 ); # we can't use only one import here because the cleaning is done as the # keys come and our keys might not be imported yet import_keys_from_gnupghome($CONFIG{'keyid'}, $GNUPGHOME, $keydir, 1, 'import-minimal', 'import-local-sigs'); import_keys_from_gnupghome([$fpr], $GNUPGHOME, $keydir, 1, 'import-clean', 'import-local-sigs'); # the first UID. we won't delete that one when pruning for UATs because a key has to have at least one UID my @uids = @{$KEYS{$keyid}->{uids}}; my $first_uid = (grep {$_->{type} eq 'uid'} @uids)[0]; for (my $uid_number = 1; $uid_number <= $#uids+1; $uid_number++) { debug("Doing key $keyid, uid $uid_number"); my $uid = $uids[$uid_number-1]; # /!\ this serial is valid in caff's GnuPGHOME only, and can't # be relied upon if the keyring is modified in the meantime. $uid->{serial} = $uid_number; next if $uid->{validity} =~ /[eir]/; # skip expired / invalid / revokey UIDs # copy pubring to temporary gpghome ################################### my $uiddir = File::Temp->newdir( "caff-$keyid-$uid_number-XXXXX", TMPDIR => 1 ); foreach (qw/pubring.gpg pubring.kbx/) { copy($keydir.'/'.$_, $uiddir.'/'.$_) if -e $keydir.'/'.$_; } # prune it ########## my $gpg = mkGnuPG( homedir => $uiddir, extra_args => ['--with-colons'] ); my $handles = mkGnuPG_fds( command => undef, stdout => undef, status => undef ); my $pid = $gpg->wrap_call( commands => [ '--edit-key' ], command_args => [ $fpr ], handles => $handles ); debug("Starting edit session"); my %output = readwrite_gpg($handles, status => $KEYEDIT_PROMPT); # delete other uids ################### my $delete_some = 0; for (my $i = 1; $i <= $#uids+1; $i++) { # it's quicker with gpg2: 'uid *' then 'uid $i' next if $i == $uid_number; next if $uid->{type} ne 'uid' and $uids[$i-1]->{hash} eq $first_uid->{hash}; # keep the first UID debug("Marking UID $i ($uids[$i-1]->{hash}) for deletion"); readwrite_gpg($handles, command => "uid $i", status => $KEYEDIT_PROMPT); $delete_some++; } if ($delete_some) { debug("Need to delete $delete_some uids"); readwrite_gpg($handles, command => "deluid", status => $KEYEDIT_DELUID_PROMPT); readwrite_gpg($handles, command => "yes", status => $KEYEDIT_PROMPT); }; # delete all subkeys #################### if (@{$KEYS{$keyid}->{subkeys}}) { for (my $i = 1; $i <= $#{$KEYS{$keyid}->{subkeys}} + 1; $i++) { debug("Marking subkey $i ($KEYS{$keyid}->{subkeys}->[$i-1]) for deletion"); readwrite_gpg($handles, command => "key $i", status => $KEYEDIT_PROMPT); }; readwrite_gpg($handles, command => "delkey", status => $KEYEDIT_DELSUBKEY_PROMPT); readwrite_gpg($handles, command => "yes", status => $KEYEDIT_PROMPT); }; # delete signatures ################### # this shouldn't delete anything as $longkeyid is already clean, but maybe we didn't sign that uid with all keys in @{$CONFIG{'keyid'}} my ($last_signed_on, $xsigners) = delete_signatures($handles, $longkeyid, $uid->{hash}, $CONFIG{'keyid'}); delete_signatures($handles, $longkeyid, $first_uid->{hash}, []) if $uid->{type} ne 'uid'; # delete all sigs on the first UID if $uid is an attribute readwrite_gpg($handles, command => "save"); done_gpg($pid, $handles); debug("Done editing"); my $asciikey = export_keys($uiddir, [$fpr], 'export-local-sigs'); undef $uiddir; # delete dir unless ($asciikey) { mywarn "No data from gpg for export $fpr"; next; }; if ($last_signed_on) { # it's a bit inefficient to store the $asciikey in memory, # but it has been pruned so it's shouldn't be too big $uid->{key} = $asciikey; $uid->{xsigners} = $xsigners; $uid->{last_signed_on} = $last_signed_on; }; }; unless ($CONFIG{'also-lsign-in-gnupghome'} eq 'no') { # remove all exportable sigs, and import into our GnuPGHOME ########################################################### my $gpg = mkGnuPG( homedir => $keydir, extra_args => ['--with-colons'] ); my $handles = mkGnuPG_fds( command => undef, stdout => undef, status => undef ); my $pid = $gpg->wrap_call( commands => [ '--edit-key' ], command_args => [ $fpr ], handles => $handles ); debug("Starting edit session on $keyid"); my %output = readwrite_gpg($handles, status => $KEYEDIT_PROMPT); delete_signatures($handles, $longkeyid, $uids[$_]->{hash}, $CONFIG{'keyid'}, 1) foreach (0 .. $#uids); readwrite_gpg($handles, command => "save"); done_gpg($pid, $handles); debug("Done editing"); # import the pruned keys with our own local sigs only; this is # required even if there are no lsigs, to ensure we've got all # UIDs in our own GnuPGHOME import_keys_from_gnupghome( [$fpr], $keydir, undef, 1, 'import-local-sigs' ); } undef $keydir; # delete dir if ($CONFIG{'also-lsign-in-gnupghome'} eq 'ask') { # manually lsign the key ######################## foreach my $local_user (@LOCAL_USER) { my @command = ($CONFIG{'gpg'}); push @command, '--secret-keyring', $CONFIG{'secret-keyring'} if GnuPG_version('2.1.0') < 0; push @command, qw/--no-auto-check-trustdb --trust-model=always/; push @command, '--local-user', $local_user; push @command, '--edit-key', $fpr; push @command, 'showphoto' if $CONFIG{'show-photos'}; push @command, 'lsign'; push @command, split ' ', $CONFIG{'gpg-sign-args'} || ""; print join(' ', @command),"\n"; mysystem(@command); } } elsif ($CONFIG{'also-lsign-in-gnupghome'} eq 'auto') { # auto lsign the uids we for which we have an exportable sig ############################################################ my @uids = grep {exists $_->{xsigners}} @{$KEYS{$keyid}->{uids}}; my @signers = map {keys %{$_->{xsigners}}} @uids; # which of @LOCAL_USER has signed at least one UID in this key? @signers = grep { my $u = $_; grep { $u eq $_ } @signers } @LOCAL_USER; @signers = keys %{{ map { $_ => 1 } @signers }}; # remove duplicates to avoid double signing foreach my $u (@signers) { my @signeduids; # uids signed by $u foreach my $uid (@uids) { # we use UIDs hashes to distinguish and select UIDs; it's the only reliable way to identify them accross keyrings push @signeduids, $uid if grep { $u eq $_ } (keys %{$uid->{xsigners}}) and !grep { $uid->{hash} eq $_->{hash} } @signeduids; } my $gpg = mkGnuPG( extra_args => ['--local-user' => $u, '--ask-cert-level', '--with-colons', '--no-batch'] ); $gpg->options->push_extra_args('--secret-keyring', $CONFIG{'secret-keyring'}) if GnuPG_version('2.1.0') < 0; $gpg->options->push_extra_args('--use-agent') if GnuPG_version('2.0.0') < 0; # we know there is a working agent my $handles = mkGnuPG_fds( command => undef, stdout => undef, status => undef ); my $pid = $gpg->wrap_call( commands => [ '--edit-key' ], command_args => [ $fpr ], handles => $handles ); debug("Starting edit session on $keyid, signer $u"); readwrite_gpg($handles, status => $KEYEDIT_PROMPT); foreach my $level (0..3) { my @signeduids_with_level = grep {$_->{xsigners}->{$u} eq $level} @signeduids; next unless @signeduids_with_level; notice("Key $longkeyid UID(s) #".(join ',', sort (map {$_->{serial}} @signeduids_with_level)).": lsign'ing with $u, cert level $level", 1); readwrite_gpg($handles, command => "uid 0", status => $KEYEDIT_PROMPT); # unselect UIDs readwrite_gpg($handles, command => "uid $_->{hash}", status => $KEYEDIT_PROMPT) for @signeduids_with_level; my %output = readwrite_gpg($handles, command => "lsign", status => qr/$KEYEDIT_SIGNUID_CLASS_PROMPT|$KEYEDIT_PROMPT/); next if $output{status} =~ /^\[GNUPG:\] $KEYEDIT_PROMPT/m; # already signed readwrite_gpg($handles, command => $level, status => $KEYEDIT_SIGNUID_PROMPT); readwrite_gpg($handles, command => "yes", status => $KEYEDIT_PROMPT); } readwrite_gpg($handles, command => "save"); done_gpg($pid, $handles); debug("Done editing"); } } } ############# # send emails ############# for my $keyid (@KEYIDS) { next unless exists $KEYS{$keyid}; my $longkeyid = $KEYS{$keyid}->{longkeyid}; my $can_encrypt = $KEYS{$keyid}->{capability} =~ /E/; my @uids = @{$KEYS{$keyid}->{uids}}; unless (grep {$_->{last_signed_on}} @uids) { info("Key 0x$longkeyid has no signed uids, skipping", 0); next; } my @attached; for my $uid (@uids) { my $text = defined $LOCALE ? $LOCALE->encode($uid->{text}) : $uid->{text}; trace("UID: $text\n"); if ($uid->{validity} =~ /[eir]/) { my $reason = $uid->{validity} =~ /e/ ? 'expired' : $uid->{validity} =~ /i/ ? 'invalid' : $uid->{validity} =~ /r/ ? 'revoked' : die; info("Key 0x$longkeyid ".(uc $uid->{type})." $uid->{serial} $text is $reason, skipping", 0); next; } unless ($uid->{last_signed_on}) { info("Key 0x$longkeyid ".(uc $uid->{type})." $uid->{serial} $text is not signed by me, skipping", 0); next; } if ($NOW - $uid->{last_signed_on} > $CONFIG{'export-sig-age'} and !ask("Signature on $text is old. Export?", 0, $PARAMS->{'export-old'}, $PARAMS->{'no-export-old'})) { next; } # save the armored key my $keydir = "$KEYSBASE/$DATE_STRING"; -d $keydir || mkdir $keydir, 0700 or myerror(1, "Cannot mkdir $keydir: $!"); my $keyfile = "$keydir/$longkeyid.key.$uid->{serial}.".sanitize_uid($text).".asc"; open my $KEY, '>', $keyfile or myerror(1, "Cannot open $keyfile: $!"); debug "Writing armored key 0x$longkeyid to $keyfile"; print $KEY $uid->{key}; close $KEY; if ($uid->{type} eq 'uat') { if (ask("UID $text is an attribute UID, attach it to every email?", 1)) { push @attached, $uid; $uid->{export} = 1; } } elsif (!defined $uid->{address}) { if (ask("UID $text is no email address, attach it to every email?", 1)) { push @attached, $uid; $uid->{export} = 1; } } else { $uid->{export} = 1; } info("Key 0x$longkeyid ".(uc $uid->{type})." $uid->{serial} $text done", 1); } @uids = grep {$_->{last_signed_on}} @uids; # ignore UIDs we didn't sign delete $_->{key} foreach grep {!$_->{export}} @uids; # delete non-exported keys if (!grep {defined $_->{address}} @uids) { mywarn "No signed RFC 5322 UID on $longkeyid; won't send other signed UID and attributes!" if @attached; } elsif (grep {$_->{export}} @uids) { notice("Key 0x$longkeyid has no encryption capabilities, mail(s) will be sent/stored unencrypted", 0) unless $can_encrypt; my $sendmail = $can_encrypt ? $CONFIG{'mail'} : $CONFIG{'mail-cant-encrypt'}; for my $uid (@uids) { next unless defined $uid->{address}; next unless $uid->{export} or @attached; my @keys = @attached; unshift @keys, $uid if exists $uid->{key}; my $mail = create_mail($uid->{address}, $can_encrypt, $longkeyid, @keys); if (defined $mail) { my @sentuids = map {$_->{text}} @attached; unshift @sentuids, $uid->{text} if $uid->{export}; do { $_ = $LOCALE->encode($_) foreach @sentuids; } if defined $LOCALE; my $text = join(', ', map {"'$_'"} @sentuids); my $should_send_mail = ask("Mail ".($can_encrypt ? '' : '*unencrypted* ')."signature for $text to '$uid->{address}'?", $sendmail ne 'ask-no', $sendmail eq 'yes', $sendmail eq 'no'); send_message($mail) if $should_send_mail; my $keydir = "$KEYSBASE/$DATE_STRING"; my $mailfile = "$keydir/$longkeyid.mail.".($should_send_mail ? '' : 'unsent.').$uid->{'serial'}.".".sanitize_uid($text); open my $MAILFILE, '>', $mailfile or myerror(1, "Cannot open $mailfile: $!"); debug "Writing message to $mailfile"; $mail->print($MAILFILE); close $MAILFILE; } else { mywarn "Generating mail failed"; } } } info("Key 0x$longkeyid done", 1); } ########################### # the default mail template ########################### __DATA__ Hi, please find attached the user id{(scalar @uids >= 2 ? 's' : '')} {foreach $uid (@uids) { $OUT .= "\t".$uid."\n"; };}of your key {$key} signed by me. If you have multiple user ids, I sent the signature for each user id separately to that user id's associated email address. You can import the signatures by running each through `gpg --import`. Note that I did not upload your key to any keyservers. If you want this new signature to be available to others, please upload it yourself. With GnuPG this can be done using gpg --keyserver hkp://pool.sks-keyservers.net --send-key {$key} If you have any questions, don't hesitate to ask. Regards, -- {$owner} ./caff/README.gpg-agent0000644000175000017500000000101013233575632014511 0ustar guilhemguilhemRunning caff with gpg-agent --------------------------- gpg-agent is part of GnuPG 2 (aka 1.9), but runs also with gpg 1.x binaries. 0. Debian users apt-get install gnupg-agent and one of the pinentry-* packages. 1. Put "use-agent" in ~/.caff/gnupghome/gpg.conf. 2. Optionally edit ~/.gnupg/gpg-agent.conf, e.g.: default-cache-ttl 600 pinentry-program /usr/bin/pinentry-gtk-2 2. Start the agent: $ eval `gpg-agent --daemon` 3. Run caff. -- Christoph Berg Mon, 27 Jun 2005 01:31:27 +0200 ./caff/pgp-clean0000755000175000017500000003376313233575735013605 0ustar guilhemguilhem#!/usr/bin/perl -w # pgp-clean -- remove all non-self signatures from key # $Id: pgp-clean 764 2015-01-22 22:48:30Z Guilhem Moulin $ # # Copyright (c) 2004, 2005 Peter Palfrader # Copyright (c) 2006 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. =pod =head1 NAME pgp-clean -- remove all non-self signatures from key =head1 SYNOPSIS =over =item B [B<-s>] I [I ...] =back =head1 DESCRIPTION B takes a list of keyids on the command line and outputs an ascii-armored keyring on stdout for each key with all signatures except self-signatures stripped. Its use is to reduce the size of keys sent out after signing (e.g. with B). =head1 OPTIONS =over =item B<-s> B<--export-subkeys> Do not remove subkeys. (Pruned by default.) =item I Use this key. =back =head1 ENVIRONMENT =over =item I The default home directory. =item I The gpg binary. Default: C<"gpg">. =item I The default working directory for gpg. Default: C<$HOME/.gnupg>. =back =head1 FILES =over =item $HOME/.gnupg/pubring.gpg - default GnuPG keyring =back =head1 SEE ALSO caff(1), gpg(1). =head1 AUTHOR Peter Palfrader This manpage was written in POD by Christoph Berg . =cut use strict; use IO::Handle; use English '-no_match_vars'; use File::Path; use File::Temp qw{tempdir}; use Fcntl; use IO::Select; use Getopt::Long; use GnuPG::Interface; my $REVISION = '$Rev: 764 $'; my ($REVISION_NUMER) = $REVISION =~ /(\d+)/; my $VERSION = "0.0.0.$REVISION_NUMER"; ########### # functions ########### sub notice($) { my ($line) = @_; print STDERR "[NOTICE] $line\n"; }; sub info($) { my ($line) = @_; print STDERR "[INFO] $line\n"; }; sub debug($) { my ($line) = @_; #print STDERR "[DEBUG] $line\n"; }; sub trace($) { my ($line) = @_; #print STDERR "[trace] $line\n"; }; sub trace2($) { my ($line) = @_; #print STDERR "[trace2] $line\n"; }; sub make_gpg_fds() { my %fds = ( stdin => IO::Handle->new(), stdout => IO::Handle->new(), stderr => IO::Handle->new(), status => IO::Handle->new() ); my $handles = GnuPG::Handles->new( %fds ); return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles); }; sub readwrite_gpg($$$$$%) { my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd, %options) = @_; trace("Entering readwrite_gpg."); my ($first_line, $dummy) = split /\n/, $in; debug("readwrite_gpg sends ".(defined $first_line ? $first_line : "")); local $INPUT_RECORD_SEPARATOR = undef; my $sout = IO::Select->new(); my $sin = IO::Select->new(); my $offset = 0; trace("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ? $statusfd : 'undef')."."); $inputfd->blocking(0); $stdoutfd->blocking(0); $statusfd->blocking(0) if defined $statusfd; $stderrfd->blocking(0); $sout->add($stdoutfd); $sout->add($stderrfd); $sout->add($statusfd) if defined $statusfd; $sin->add($inputfd); my ($stdout, $stderr, $status) = ("", "", ""); my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'}; trace("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches; my $readwrote_stuff_this_time = 0; my $do_not_wait_on_select = 0; my ($readyr, $readyw, $written); while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { if (defined $exitwhenstatusmatches) { if ($status =~ /$exitwhenstatusmatches/m) { trace("readwrite_gpg found match on $exitwhenstatusmatches"); if ($readwrote_stuff_this_time) { trace("read/write some more\n"); $do_not_wait_on_select = 1; } else { trace("that's it in our while loop.\n"); last; } }; }; $readwrote_stuff_this_time = 0; trace("select waiting for ".($sout->count())." fds."); ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1); trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0)); for my $wfd (@$readyw) { $readwrote_stuff_this_time = 1; if (length($in) != $offset) { trace("writing to $wfd."); $written = $wfd->syswrite($in, length($in) - $offset, $offset); $offset += $written; }; if ($offset == length($in)) { trace("writing to $wfd done."); unless ($options{'nocloseinput'}) { close $wfd; trace("$wfd closed."); }; $sin->remove($wfd); $sin = undef; } } next unless defined $readyr and @$readyr; # Wait some more. for my $rfd (@$readyr) { $readwrote_stuff_this_time = 1; if ($rfd->eof) { trace("reading from $rfd done."); $sout->remove($rfd); close($rfd); next; } trace("reading from $rfd."); if ($rfd == $stdoutfd) { $stdout .= <$rfd>; trace2("stdout is now $stdout\n================"); next; } if (defined $statusfd && $rfd == $statusfd) { $status .= <$rfd>; trace2("status is now $status\n================"); next; } if ($rfd == $stderrfd) { $stderr .= <$rfd>; trace2("stderr is now $stderr\n================"); next; } } } trace("readwrite_gpg done."); return ($stdout, $stderr, $status); }; sub export_key($$) { my ($gnupghome, $keyid) = @_; my $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; my %confighash = ( armor => 1 ); $confighash{'homedir'}=$gnupghome if (defined $gnupghome); $gpg->options->hash_init( %confighash ); $gpg->options->meta_interactive( 0 ); my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]); my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; return $stdout; }; ################## # global variables ################## my $KEYEDIT_PROMPT = '^\[GNUPG:\] GET_LINE keyedit.prompt'; my $KEYEDIT_DELUID_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.uid.okay'; my $KEYEDIT_DELSIG_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.delsig'; my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET_LINE keyedit.prompt)'; my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey'; my $params; ################### # argument handling ################### sub version($) { my ($fd) = @_; print $fd "pgp-clean $VERSION - (c) 2004, 2005, 2006 Peter Palfrader et al.\n"; }; sub usage($$) { my ($fd, $exitcode) = @_; version($fd); print $fd "Usage: $PROGRAM_NAME [-s] [ ...]\n"; print $fd "-s --export-subkeys do not remove subkeys\n"; exit $exitcode; }; Getopt::Long::config('bundling'); if (!GetOptions ( '-h' => \$params->{'help'}, '--help' => \$params->{'help'}, '-V' => \$params->{'version'}, '--version' => \$params->{'version'}, '-s' => \$params->{'export-subkeys'}, '--export-subkeys' => \$params->{'export-subkeys'}, )) { usage(\*STDERR, 1); }; if ($params->{'help'}) { usage(\*STDOUT, 0); }; if ($params->{'version'}) { version(\*STDOUT); exit(0); }; usage(\*STDERR, 1) unless scalar @ARGV >= 1; my @KEYIDS; for my $keyid (@ARGV) { $keyid =~ s/^0x//i; unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8})?$/) { print STDERR "$keyid is not a keyid.\n"; usage(\*STDERR, 1); }; push @KEYIDS, uc($keyid); }; ################## # export and prune ################## KEYS: for my $keyid (@KEYIDS) { # get key listing ################# my $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->meta_interactive( 0 ); my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] ); my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]); my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; if ($stdout eq '') { warn ("No data from gpg for list-key $keyid\n"); next; }; my $keyinfo = $stdout; my @publine = grep /^pub/, (split /\n/, $stdout); my ($dummy1, $dummy2, $dummy3, $dummy4, $longkeyid, $dummy6, $dummy7, $dummy8, $dummy9, $dummy10, $dummy11, $flags) = split /:/, pop @publine; my $can_encrypt = $flags =~ /E/; unless (defined $longkeyid) { warn ("Didn't find public keyid in edit dialog of key $keyid.\n"); next; }; # export the key ################ my $asciikey = export_key(undef, $keyid); if ($asciikey eq '') { warn ("No data from gpg for export $keyid\n"); next; }; my @UIDS; my $uid_number = 0; my $this_uid_text = ''; $uid_number++; debug("Doing key $keyid, uid $uid_number"); # import into temporary gpghome ############################### my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1); $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->hash_init( 'homedir' => $tempdir ); $gpg->options->meta_interactive( 0 ); ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); $pid = $gpg->import_keys(handles => $handles); ($stdout, $stderr, $status) = readwrite_gpg($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) { warn ("Could not import $keyid into temporary gnupg.\n"); next; }; # prune it ########## $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->hash_init( 'homedir' => $tempdir, 'extra_args' => [ '--with-colons', '--fixed-list-mode', '--command-fd=0', '--no-tty' ] ); ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); $pid = $gpg->wrap_call( commands => [ '--edit-key' ], command_args => [ $keyid ], handles => $handles ); debug("Starting edit session"); ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark all uids ################### my $number_of_subkeys = 0; my $i = 1; my $have_one = 0; my $is_uat = 0; my $delete_some = 0; debug("Parsing stdout output."); for my $line (split /\n/, $stdout) { debug("Checking line $line"); my ($type, $dummy2, $dummy3, $dummy4, $dummy5, $dummy6, $dummy7, $dummy8, $dummy9, $uidtext) = split /:/, $line; if ($type eq 'sub') { $number_of_subkeys++; }; next unless ($type eq 'uid' || $type eq 'uat'); debug("line is interesting."); debug("mark uid."); readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); $i++; }; debug("Parsing stdout output done."); # delete subkeys ################ if (!$params->{'export-subkeys'} and $number_of_subkeys > 0) { for (my $i=1; $i<=$number_of_subkeys; $i++) { readwrite_gpg("key $i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); }; readwrite_gpg("delkey\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSUBKEY_PROMPT, nocloseinput => 1); readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); }; # delete signatures ################### my $signed_by_me = 0; ($stdout, $stderr, $status) = readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1); while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) { # sig:?::17:EA2199412477CAF8:1058095214:::::13x: my @sigline = grep /^sig/, (split /\n/, $stdout); $stdout =~ s/\n/\\n/g; notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX my $line = pop @sigline; my $answer = "no"; if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance debug("[sigremoval] doing line $line."); my ($dummy1, $dummy2, $dummy3, $dummy4, $signer, $created, $dummy7, $dummy8, $dummy9) = split /:/, $line; if ($signer eq $longkeyid) { debug("[sigremoval] selfsig ($signer)."); $answer = "no"; } else { debug("[sigremoval] not interested in that sig ($signer)."); $answer = "yes"; }; } else { debug("[sigremoval] no sig line here, only got: ".$stdout); }; ($stdout, $stderr, $status) = readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1); }; readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd); waitpid $pid, 0; $asciikey = export_key($tempdir, $longkeyid); if ($asciikey eq '') { warn ("No data from gpg for export $longkeyid\n"); next; }; print $asciikey; } ./caff/TODO0000644000175000017500000000042113233575632012456 0ustar guilhemguilhemLegend: - Not done * Top priority . Partially done o Done D Deferred X Abandoned o need to import own keys (including public) into our gnupghome (else it complains about not finding the public key for the used secret key) ./caff/caffrc.sample0000644000175000017500000000443113233575735014426 0ustar guilhemguilhem# $Id: caffrc.sample 505 2013-10-18 08:16:20Z weasel $ # vim:ft=perl: $CONFIG{'owner'} = 'John Doe'; $CONFIG{'email'} = 'user@example.com'; # you can get your long keyid from # gpg --with-colons --list-key # # if you have a v4 key, it will simply be the last 16 digits of # your fingerprint. # This is the list of keys whose signatures you want to mail around. Usually # you would list all your keys here. To specify which keys to sign with, set # local-user. See the manpage for further details. $CONFIG{'keyid'} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ]; $CONFIG{'also-encrypt-to'} = [ qw{DE7AAF6E94C09C7F} ]; $CONFIG{'caffhome'} = $ENV{'HOME'}.'/.caff'; # The options below need not be changed for normal operation. # Paths to GnuPG binaries: # $CONFIG{'gpg'} = 'gpg'; # $CONFIG{'gpg-sign'} = $CONFIG{'gpg'}; # $CONFIG{'gpg-delsig'} = '/home/weasel/tmp/gpg/gnupg-1.3.92/g10/gpg'; # defaults to ~/.gnupg/secring.gpg # $CONFIG{'secret-keyring'} = '/tmp/gpg/secring.gpg'; # Don't export UIDs by default, on which your latest signature is older than this age. # $CONFIG{'export-sig-age'} = 24*60*60; # Keyserver to download keys from. Default: pool.sks-keyservers.net. # $CONFIG{'keyserver'} = 'pgp.surfnet.nl'; # Boolean options, all default to false (0). # # Skip fetching the keys from the keyserver. # $CONFIG{'no-download'} = 1; # Skip signing the keys. # $CONFIG{'no-sign'} = 1; # Ask to continue before starting the signing (for offline signing). # $CONFIG{'ask-sign'} = 1; # $CONFIG{'mail-template'} = <<'EOM' # Hi, # # please find attached the user id{(scalar @uids >= 2 ? 's' : '')} # {foreach $uid (@uids) { # $OUT .= "\t".$uid."\n"; # };}of your key {$key} signed by me. # # Note that I did not upload your key to any keyservers. # If you have multiple user ids, I sent the signature for each user id # separately to that user id's associated email address. You can import # the signatures by running each through `gpg --import`. # # If you want this new signature to be available to others, please upload # it yourself. With GnuPG this can be done using # gpg --keyserver pool.sks-keyservers.net --send-key {$key} # # If you have any questions, don't hesitate to ask. # # Regards, # {$owner} # EOM ./caff/README.v3-keys0000644000175000017500000000133413233575632014152 0ustar guilhemguilhemv3 keys are evil ---------------- The good thing about v4 keys is that the last 16 chars of the fingerprint are the same as the 8 byte keyid, and likewise the last 8 chars are the 4 byte keyid. For v3 keys that is not true. This, and some issues with HKP key servers make the handling of v3 a PITA. To sign v3 keys with caff, do the following: $ caff [note that caff does not accept fingerprints for v3 keys, use keyids] The key will be imported from the keyserver, but caff thinks it failed. Now run caff again with -R: $ caff -R Since the key is already there, caff will proceed. Of course, this could be automated... patches welcome :) -- Christoph Berg Sat, 2 Jul 2005 21:34:48 +0200 ./caff/Makefile0000644000175000017500000000015413233575632013431 0ustar guilhemguilhemMAN = caff.1 pgp-clean.1 pgp-fixkey.1 all: $(MAN) %.1: % pod2man $< > $@ install: clean: rm -f $(MAN) ./caff/README.many-keys0000644000175000017500000000750213233575632014571 0ustar guilhemguilhemUsing caff to sign lots of keys ------------------------------- If you have loads of keys to sign (sometimes, there are keysigning parties with more than 100 participants), keysigning can be awkward, even with caff. It gets worse if you have multiple local keys and want to sign with all. Some hints to get the signing done faster: * Use fingerprints instead of key ids. caff and gpg allow you to specify the full fingerprint. This will save you from having to check the fingerprint yourself. If you have a text file with all fingerprints, use that and then run $ xargs caff --no-download --key-file keyring.asc ,. $ caff -u , * Use gpg-agent. See README.gpg-agent. * Use gpg-sign-args. $CONFIG{'gpg-sign-args'} = "save"; This automatically saves the key after signing in gpg. The advantage is that you do not have to type "save" for each key. The disadvantage is that you cannot choose which UIDs to sign by answering "no" at the "Really sign?" prompt any more; you will have *not* to send out some mails. (And you have unwanted signatures lingering around in ~/.caff/gnupghome/pubring.gpg.) -- Guilhem Moulin Thu, 03 Apr 2014 12:43:13 +0200 ./gpgwrap/0000755000175000017500000000000013233575632012541 5ustar guilhemguilhem./gpgwrap/README0000644000175000017500000000167313233575632013430 0ustar guilhemguilhemAuthor: Karsten Scheibler Homepage: http://unusedino.de/gpgwrap/ eMail: gpgwrap@unusedino.de ======================= [1] GENERAL INFORMATION ======================= See doc/gpgwrap.1 for more ============================ [2] SHORT BUILD INSTRUCTIONS ============================ To build gpgwrap a simple 'make' should be enough. You may build gpgwrap with dietlibc, just set and export the environment variable DIET to the location of your 'diet' binary and run 'make'. Dietlibc is a libc that is optimized for small size, look at http://www.fefe.de/dietlibc/ for more. =================================== [3] SHORT INSTALLATION INSTRUCTIONS =================================== Copy the files from bin/ and doc/ to your favorite directories, for example: chown root bin/* doc/*.1 chgrp root bin/* doc/*.1 (cd bin && tar cf - *) | (cd /usr/bin && tar xvf -) (cd doc && tar cf - *.1) | (cd /usr/man/man1 && tar xvf -) ./gpgwrap/NEWS0000644000175000017500000000113513233575632013240 0ustar guilhemguilhemgpgwrap 0.04-20060904 * new option: -P * in previous versions the --passphrase-fd option was appended after --homedir or --options , now also --homedir= and --options= should be handled correctly gpgwrap 0.03-20050321 * new options: -o, -c * do not always ignore the exit code of child processes as in version 0.02 (see manpage for more) * format of verbose output changed gpgwrap 0.02-20041014 * new options: -V, -F, -v, -i, -a * the passphrase may also be given via stdin, via environment variable or via prompt gpgwrap 0.01-20040601 * first official release ./gpgwrap/src/0000755000175000017500000000000013233575632013330 5ustar guilhemguilhem./gpgwrap/src/Makefile0000644000175000017500000000074313233575632014774 0ustar guilhemguilhemCC=${DIET} gcc -s CFLAGS:=$(shell dpkg-buildflags --get CFLAGS) -fomit-frame-pointer CPPFLAGS:=$(shell dpkg-buildflags --get CPPFLAGS) LDFLAGS:=$(shell dpkg-buildflags --get LDFLAGS) STRIP=strip -R .note -R .comment RM=rm -f TARGET=../bin/gpgwrap .PHONY: all clean all: ${TARGET} ${TARGET}: gpgwrap.c [ -d $(dir ${TARGET}) ] || mkdir $(dir ${TARGET}) ${CC} ${CPPFLAGS} ${CFLAGS} ${LDFLAGS} -o ${TARGET} gpgwrap.c ${STRIP} ${TARGET} 2>/dev/null || true clean: ${RM} ${TARGET} ./gpgwrap/src/gpgwrap.c0000644000175000017500000005624313233575632015155 0ustar guilhemguilhem/**************************************************************************** **************************************************************************** * * gpgwrap.c * **************************************************************************** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "version.h" #define PROGRAM_NAME "gpgwrap" #define VERSION_STRING PROGRAM_NAME " " VERSION "-" VERSION_DATE #define EXEC_ARGV_SIZE 1024 #define PASSPHRASE_BUFFER_SIZE 0x10000 #define LIST_BUFFER_SIZE 0x10000 #define CMDLINE_MAX_FILES 1024 #define GPGWRAP_MODE_DEFAULT 0 #define GPGWRAP_MODE_VERSION 1 #define GPGWRAP_MODE_FILE 2 #define GPGWRAP_MODE_PRINT 3 static char program_name[] = PROGRAM_NAME; static char environ_name[] = "GPGWRAP_PASSPHRASE"; static int mode = GPGWRAP_MODE_DEFAULT; static int verbose = 0; static int interactive = 0; static int ask_twice = 0; static int check_exit_code = 0; static char *calling_path = NULL; static char *environ_var = NULL; static char *passphrase_file = NULL; static char *option_name = "--passphrase-fd"; static char *files[CMDLINE_MAX_FILES]; static int nfiles = 0; static char **gpg_cmd = NULL; /**************************************************************************** * do_perror ****************************************************************************/ static void do_perror( void) { perror(program_name); exit(1); } /**************************************************************************** * do_error ****************************************************************************/ #define do_error(args...) \ do \ { \ fprintf(stderr, "%s: ", program_name); \ fprintf(stderr, args); \ fprintf(stderr, "\n"); \ exit(1); \ } \ while (0) /**************************************************************************** * do_warning ****************************************************************************/ #define do_warning(args...) \ do \ { \ fprintf(stderr, "%s: ", program_name); \ fprintf(stderr, args); \ fprintf(stderr, "\n"); \ } \ while (0) /**************************************************************************** * do_error_oom ****************************************************************************/ static void do_error_oom( void) { do_error("could not allocate memory"); } /**************************************************************************** * do_error_too_long ****************************************************************************/ static void do_error_too_long( void) { do_error("passphrase too long"); } /**************************************************************************** * do_verbose ****************************************************************************/ #define do_verbose(level, args...) \ do \ { \ if (verbose < level) break; \ fprintf(stderr, "%s[%d]: ", program_name, getpid()); \ fprintf(stderr, args); \ fprintf(stderr, "\n"); \ } \ while (0) /**************************************************************************** * do_verbose_start ****************************************************************************/ #define do_verbose_start(level, args...) \ do \ { \ if (verbose < level) break; \ fprintf(stderr, "%s[%d] ", program_name, getpid()); \ fprintf(stderr, args); \ } \ while (0) /**************************************************************************** * do_verbose_append ****************************************************************************/ #define do_verbose_append(level, args...) \ do \ { \ if (verbose < level) break; \ fprintf(stderr, args); \ } \ while (0) /**************************************************************************** * do_snprintf ****************************************************************************/ #define do_snprintf(string, max, args...) do_snprintf2(snprintf(string, max, args), max) /**************************************************************************** * do_snprintf2 ****************************************************************************/ static int do_snprintf2( int len, int max) { if ((len == -1) || (len >= max)) do_error("do_snprintf() size exceeded"); return (len); } /**************************************************************************** * mangle_passphrase ****************************************************************************/ static int mangle_passphrase( char *buffer, int size, char *mbuffer, int msize) { char c; int i, j, c1; /* * look for "unusual" characters and convert them to * backslash escaped octal numbers */ for (i = j = 0, msize--; i < size; i++) { c = buffer[i]; if (j >= msize) goto error; if ((c < '+') || ((c > ';') && (c < 'A')) || ((c > 'Z') && (c != '_') && (c < 'a')) || ((c > 'z') && (c != '~'))) { c1 = (unsigned char) c; if (j >= msize - 4) goto error; mbuffer[j++] = '\\'; mbuffer[j++] = '0' + (c1 >> 6); mbuffer[j++] = '0' + ((c1 >> 3) & 7); mbuffer[j++] = '0' + (c1 & 7); } else mbuffer[j++] = c; } mbuffer[j] = '\0'; return (j); error: do_error("could not mangle passphrase"); } /**************************************************************************** * unmangle_passphrase ****************************************************************************/ static int unmangle_passphrase( char *buffer, int size) { char c; int i, j, c1, c2, c3; /* replace backslash escaped octal numbers */ for (i = j = 0; j < size; i++) { c = buffer[j++]; if (c == '\\') { if (j > size - 3) goto error; c1 = buffer[j++]; c2 = buffer[j++]; c3 = buffer[j++]; if ((c1 < '0') || (c1 > '3') || (c2 < '0') || (c2 > '7') || (c3 < '0') || (c3 > '7')) goto error; c1 -= '0'; c2 -= '0'; c3 -= '0'; c = (char) (((c1 << 6) | (c2 << 3) | c3) & 0xff); } buffer[i] = c; } return (i); error: do_error("could not unmangle passphrase"); } /**************************************************************************** * read_passphrase ****************************************************************************/ static int read_passphrase( char *buffer, int size) { int fd, len, i; do_verbose(2, "reading passphrase from file '%s'", passphrase_file); if (strcmp(passphrase_file, "-") == 0) fd = STDIN_FILENO; else fd = open(passphrase_file, O_RDONLY); if (fd == -1) do_perror(); for (len = 0; (i = read(fd, buffer, size)) > 0; len += i) { buffer += i; size -= i; if (size == 0) do_error_too_long(); } if (i == -1) do_perror(); if (close(fd) == -1) do_perror(); return (len); } /**************************************************************************** * prompt_passphrase ****************************************************************************/ static int prompt_passphrase( char *buffer, int size) { int len, len2; int fd; struct termios t, tt; char tty[] = "/dev/tty"; char pp[] = "Passphrase: "; char pp2[] = "\nPassphrase (again): "; char *buffer2; /* * don't touch stdin, just open the controlling tty and ask for the * passphrase */ do_verbose(2, "opening '%s' to prompt for passphrase", tty); fd = open(tty, O_RDWR); if (fd == -1) do_perror(); write(fd, pp, strlen(pp)); tcgetattr(fd, &t); tt = t; tt.c_lflag &= ~ECHO; tcsetattr(fd, TCSAFLUSH, &tt); len = read(fd, buffer, size); if (len == -1) do_perror(); if ((ask_twice) && (len < size)) { buffer2 = (char *) alloca(sizeof (char) * size); if (buffer2 == NULL) do_error_oom(); write(fd, pp2, strlen(pp2)); len2 = read(fd, buffer2, size); if (len2 == -1) do_perror(); write(fd, "\n", 1); tcsetattr(fd, TCSAFLUSH, &t); if ((len != len2) || (memcmp(buffer, buffer2, len) != 0)) do_error("passphrases are not the same"); } else { write(fd, "\n", 1); tcsetattr(fd, TCSAFLUSH, &t); /* * if the above read() returns with len == size, we don't * know if there are more bytes, so we assume passphrase is * too long */ if (len >= size) do_error_too_long(); } if (close(fd) == -1) do_perror(); /* ignore trailing \012 */ return (len - 1); } /**************************************************************************** * environ_or_prompt ****************************************************************************/ static int environ_or_prompt( char *buffer, int size) { int len, len2; char *env; env = getenv(environ_name); if ((env != NULL) && (! interactive)) { do_verbose(2, "got passphrase from environment variable: %s=%s", environ_name, env); /* * first unmangle the content of the environment * variable inplace, then clear the memory */ len2 = strlen(env); len = unmangle_passphrase(env, len2); if (len > size) do_error_too_long(); memcpy(buffer, env, len); memset(env, 0, len2); } else len = prompt_passphrase(buffer, size); return (len); } /**************************************************************************** * do_wait ****************************************************************************/ static void do_wait( void) { int status, value = 1; do_verbose(2, "waiting for child"); wait(&status); if (! check_exit_code) return; do_verbose(2, "checking child exit code"); if (! WIFEXITED(status)) goto out; value = WEXITSTATUS(status); if (value == 0) return; do_verbose(2, "child process terminated abnormal, exiting"); out: exit(value); } /**************************************************************************** * do_fork ****************************************************************************/ static int do_fork( char *buffer, int size) { int fds[2], i; /* * parent will write passphrase to the opened pipe, child will * pass the fd to gpg */ if (pipe(fds) == -1) do_perror(); do_verbose(2, "forking"); switch (fork()) { case -1: do_perror(); case 0: /* child */ if (close(fds[1]) == -1) do_perror(); return (fds[0]); default: break; } /* parent */ signal(SIGPIPE, SIG_IGN); if (close(fds[0]) == -1) do_perror(); while (size > 0) { i = write(fds[1], buffer, size); if ((i == -1) && (errno == EPIPE)) break; if (i == -1) do_perror(); buffer += i; size -= i; } if (size > 0) do_warning("only partial passphrase written"); if (close(fds[1]) == -1) do_perror(); do_wait(); return (-1); } /**************************************************************************** * get_passphrase_fd ****************************************************************************/ static int get_passphrase_fd( void) { int fd, len; char buffer[PASSPHRASE_BUFFER_SIZE]; if ((passphrase_file == NULL) || (interactive)) { len = environ_or_prompt(buffer, sizeof (buffer)); fd = do_fork(buffer, len); } else if (strcmp(passphrase_file, "-") == 0) { len = read_passphrase(buffer, sizeof (buffer)); fd = do_fork(buffer, len); } else { do_verbose(2, "opening file '%s' to pass fd", passphrase_file); fd = open(passphrase_file, O_RDONLY); if (fd == -1) do_perror(); } return (fd); } /**************************************************************************** * get_passphrase ****************************************************************************/ static int get_passphrase( char *buffer, int size) { int len; if ((passphrase_file == NULL) || (interactive)) len = environ_or_prompt(buffer, size); else len = read_passphrase(buffer, size); return (len); } /**************************************************************************** * do_putenv ****************************************************************************/ static void do_putenv( char *buffer, int len) { int size, len2; char *old_var; /* * putenv() only stores the given pointer in **environ, so we have * to use malloc here */ size = strlen(environ_name) + (4 * len) + 2; old_var = environ_var; environ_var = (char *) malloc(sizeof (char) * size); if (environ_var == NULL) do_error_oom(); len2 = do_snprintf(environ_var, size, "%s=", environ_name); if ((buffer != NULL) && (len > 0)) mangle_passphrase(buffer, len, &environ_var[len2], size - len2); do_verbose(2, "setting environment variable: %s", environ_var); if (putenv(environ_var) == -1) do_perror(); if (old_var != NULL) free(old_var); } /**************************************************************************** * do_exec ****************************************************************************/ static void do_exec( char **argv, int clear) { if (clear) do_putenv(NULL, 0); if (verbose > 0) { int i; do_verbose_start(1, "executing:"); for (i = 0; argv[i] != NULL; i++) do_verbose_append(1, " %s", argv[i]); do_verbose_append(1, "\n"); } execvp(argv[0], argv); /* only reached if execvp fails */ do_perror(); } /**************************************************************************** * exec_gpg ****************************************************************************/ static void exec_gpg( void) { int fd; int i, j, k; char fd_num[32]; char *argv[EXEC_ARGV_SIZE]; char homedir_eq[] = "--homedir="; char options_eq[] = "--options="; /* * get fd to read passphrase from, parent will return with fd == -1 * after fork */ fd = get_passphrase_fd(); if (fd == -1) return; /* create argv for execvp */ do_snprintf(fd_num, sizeof (fd_num), "%d", fd); for (i = 0, j = 0, k = 1; gpg_cmd[i] != NULL; i++, k--) { /* * check if there is enough space to store option_name * and fd_num */ if (i >= (EXEC_ARGV_SIZE - 4)) do_error("too many gpg arguments specified"); if (strcmp(gpg_cmd[i], option_name) == 0) do_error("gpg command already has a '%s' option", option_name); if (k == 0) { if ((strncmp(gpg_cmd[i], homedir_eq, sizeof (homedir_eq) - 1) == 0) || (strncmp(gpg_cmd[i], options_eq, sizeof (options_eq) - 1) == 0)) k = 1; else if ((strcmp(gpg_cmd[i], "--homedir") == 0) || (strcmp(gpg_cmd[i], "--options") == 0)) k = 2; else { argv[j++] = option_name; argv[j++] = fd_num; } } argv[j++] = gpg_cmd[i]; } if (k >= 0) { argv[j++] = option_name; argv[j++] = fd_num; } argv[j] = NULL; do_exec(argv, 1); } /**************************************************************************** * exec_line ****************************************************************************/ static void exec_line( char *line) { char shell_cmd[LIST_BUFFER_SIZE]; char verbose_string[128] = ""; char *argv[] = { "sh", "-c", NULL, NULL }; int fds[2], i; /* fork a child and disallow it to read stdin from parent */ if (pipe(fds) == -1) do_perror(); do_verbose(1, "forking"); switch (fork()) { case -1: do_perror(); case 0: break; default: /* parent */ if (close(fds[0]) == -1) do_perror(); if (close(fds[1]) == -1) do_perror(); do_wait(); return; } /* child */ if (close(fds[1]) == -1) do_perror(); if (fds[0] != STDIN_FILENO) dup2(fds[0], STDIN_FILENO); /* create argv for execvp */ for (i = 0; i < verbose; i++) { if (strlen(verbose_string) >= sizeof (verbose_string) - 4) break; strcat(verbose_string, " -v"); } do_snprintf(shell_cmd, sizeof (shell_cmd), "exec %s%s -o %s -- %s", calling_path, verbose_string, option_name, line); argv[2] = shell_cmd; do_exec(argv, 0); } /**************************************************************************** * exec_list ****************************************************************************/ static void exec_list( char *path, char *buffer, int len) { int fd; char lbuffer[LIST_BUFFER_SIZE]; int inuse, start, free, nread, llen; char *line, *next_line; /* open file */ do_verbose(1, "reading gpg commands from file: '%s'", path); if (strcmp(path, "-") == 0) fd = STDIN_FILENO; else fd = open(path, O_RDONLY); if (fd == -1) do_perror(); /* export passphrase to environment */ do_putenv(buffer, len); /* read gpg commands */ for (inuse = 0, free = LIST_BUFFER_SIZE; (nread = read(fd, &lbuffer[inuse], free)) > 0; ) { inuse += nread; for (line = lbuffer; (next_line = memchr(line, '\n', inuse)) != NULL; ) { *next_line = '\0'; llen = (int) (next_line - line) + 1; if (llen != strlen(line) + 1) do_error("line contains \\0 character"); exec_line(line); inuse -= llen; line = next_line + 1; } start = (int) (line - lbuffer); if ((start == 0) && (inuse == LIST_BUFFER_SIZE)) do_error("line too long"); if ((start > 0) && (inuse > 0)) memmove(lbuffer, &lbuffer[start], inuse); free = LIST_BUFFER_SIZE - inuse; } /* check for error while read() */ if (nread == -1) do_perror(); if (close(fd) == -1) do_perror(); /* check if there are bytes left */ if (inuse > 0) do_error("last line incomplete"); } /**************************************************************************** * cmdline_fill_space ****************************************************************************/ static void cmdline_fill_space( char *s) { while (*s != '\0') *s++ = ' '; } /**************************************************************************** * cmdline_usage ****************************************************************************/ static void cmdline_usage( void) { char space1[] = VERSION_STRING; char space2[] = PROGRAM_NAME; cmdline_fill_space(space1); cmdline_fill_space(space2); printf(VERSION_STRING " | written by Karsten Scheibler\n" "%s | http://unusedino.de/gpgwrap/\n" "%s | gpgwrap@unusedino.de\n\n" "Usage: %s -V\n" "or: %s -P [-v] [-i] [-a] [-p ]\n" "or: %s -F [-v] [-i] [-a] [-c] [-p ] [-o ]\n" " %s [--] [ ... ]\n" "or: %s [-v] [-i] [-a] [-p ] [-o ]\n" " %s [--] gpg [gpg options]\n\n" " -V print out version\n" " -P get the passphrase and print it mangled to stdout\n" " -F read gpg commands from file\n" " -v be more verbose\n" " -i be interactive, always prompt for passphrase\n" " -a ask twice if prompting for passphrase\n" " -c check exit code of child processes\n" " -p read passphrase from \n" " -o specify name of \"--passphrase-fd\" option\n" " -h this help\n", space1, space1, program_name, program_name, program_name, space2, program_name, space2); exit(0); } /**************************************************************************** * cmdline_check_arg ****************************************************************************/ static char * cmdline_check_arg( char *msg, char *file) { if (file == NULL) do_error("%s expects a file name", msg); return (file); } /**************************************************************************** * cmdline_check_stdin ****************************************************************************/ static char * cmdline_check_stdin( char *msg, char *file) { static int stdin_count = 0; cmdline_check_arg(msg, file); if (strcmp(file, "-") == 0) stdin_count++; if (stdin_count > 1) do_error("%s used stdin although already used before", msg); return (file); } /**************************************************************************** * cmdline_parse ****************************************************************************/ static void cmdline_parse( int argc, char **argv) { char *arg; int args; int ignore = 0; calling_path = argv[0]; for (args = 0, argv++; (arg = *argv++) != NULL; args++) { if ((arg[0] != '-') || (ignore)) { if (mode == GPGWRAP_MODE_FILE) goto get_file; gpg_cmd = argv - 1; break; } else if ((strcmp(arg, "-") == 0) && (mode == GPGWRAP_MODE_FILE)) { get_file: if (nfiles >= CMDLINE_MAX_FILES) do_error("too many files specified"); files[nfiles++] = cmdline_check_stdin("-F/--file", arg); } else if (strcmp(arg, "--") == 0) { ignore = 1; } else if ((strcmp(arg, "-h") == 0) || (strcmp(arg, "--help") == 0)) { cmdline_usage(); } else if (((strcmp(arg, "-V") == 0) || (strcmp(arg, "--version") == 0)) && (args == 0)) { mode = GPGWRAP_MODE_VERSION; } else if (((strcmp(arg, "-F") == 0) || (strcmp(arg, "--file") == 0)) && (args == 0)) { mode = GPGWRAP_MODE_FILE; } else if (((strcmp(arg, "-P") == 0) || (strcmp(arg, "--print") == 0)) && (args == 0)) { mode = GPGWRAP_MODE_PRINT; } else if (mode == GPGWRAP_MODE_VERSION) { goto bad_option; } else if ((strcmp(arg, "-v") == 0) || (strcmp(arg, "--verbose") == 0)) { verbose++; } else if ((strcmp(arg, "-i") == 0) || (strcmp(arg, "--interactive") == 0)) { interactive = 1; } else if ((strcmp(arg, "-a") == 0) || (strcmp(arg, "--ask-twice") == 0)) { ask_twice = 1; } else if ((strcmp(arg, "-p") == 0) || (strcmp(arg, "--passphrase-file") == 0)) { if (passphrase_file != NULL) do_error("-p/--passphrase-file specified more than once"); passphrase_file = cmdline_check_stdin("-p/--passphrase-file", *argv++); } else if (mode == GPGWRAP_MODE_PRINT) { goto bad_option; } else if ((strcmp(arg, "-o") == 0) || (strcmp(arg, "--option-name") == 0)) { option_name = cmdline_check_arg("-o/--option-name", *argv++); } else if (mode != GPGWRAP_MODE_FILE) { goto bad_option; } else if ((strcmp(arg, "-c") == 0) || (strcmp(arg, "--check-exit-code") == 0)) { check_exit_code = 1; } else { bad_option: do_error("unrecognized option '%s'", arg); } } if ((mode == GPGWRAP_MODE_DEFAULT) && (nfiles == 0) && (gpg_cmd == NULL)) do_error("no gpg command specified"); if ((mode == GPGWRAP_MODE_FILE) && (nfiles == 0)) do_error("no files to process"); if ((mode == GPGWRAP_MODE_PRINT) && (nfiles > 0)) do_error("no additional arguments allowed"); if (mode != GPGWRAP_MODE_FILE) check_exit_code = 1; } /**************************************************************************** * main ****************************************************************************/ int main( int argc, char **argv) { /* * we need setlinebuf(), because otherwise do_verbose() output of * parent and child processes may get mixed in some cases */ setlinebuf(stderr); /* parse cmdline */ cmdline_parse(argc, argv); /* do it */ if (mode == GPGWRAP_MODE_VERSION) { printf(VERSION_STRING "\n"); } else if (mode == GPGWRAP_MODE_FILE) { int i, len; char buffer[PASSPHRASE_BUFFER_SIZE]; len = get_passphrase(buffer, sizeof (buffer)); for (i = 0; i < nfiles; i++) exec_list(files[i], buffer, len); } else if (mode == GPGWRAP_MODE_PRINT) { char buffer[PASSPHRASE_BUFFER_SIZE]; char mbuffer[PASSPHRASE_BUFFER_SIZE]; int len; len = get_passphrase(buffer, sizeof (buffer)); mangle_passphrase(buffer, len, mbuffer, sizeof (mbuffer)); printf("%s\n", mbuffer); } else exec_gpg(); /* done */ return (0); } /******************************************************** Karsten Scheibler */ ./gpgwrap/src/version.h0000644000175000017500000000103413233575632015164 0ustar guilhemguilhem/**************************************************************************** **************************************************************************** * * version.h * **************************************************************************** ****************************************************************************/ #ifndef VERSION_H #define VERSION_H #define VERSION "0.04" #define VERSION_DATE "20060904" #endif /* !VERSION_H */ /******************************************************** Karsten Scheibler */ ./gpgwrap/LICENSE0000644000175000017500000003545313233575632013560 0ustar guilhemguilhem GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS ./gpgwrap/Makefile0000644000175000017500000000015213233575632014177 0ustar guilhemguilhemMAKE=make .PHONY: all clean all: cd src && ${MAKE} all DIET="${DIET}" clean: cd src && ${MAKE} clean ./gpgwrap/doc/0000755000175000017500000000000013233575632013306 5ustar guilhemguilhem./gpgwrap/doc/gpgwrap.10000644000175000017500000002270613233575632015046 0ustar guilhemguilhem.ds Q" "" .de Vb .ft CW .nf .ne \\$1 .. .de Ve .ft R .fi .. .TH gpgwrap 1 "gpgwrap 0.04" .SH NAME gpgwrap \- a small wrapper for gpg .SH SYNOPSIS .B gpgwrap \-V .B gpgwrap \-P [\-v] [\-i] [\-a] [\-p \fI\fR] .B gpgwrap \-F [\-v] [\-i] [\-a] [\-c] [\-p \fI\fR] [\-o \fI\fR] [\-\-] \fI\fR [\fI\fR ... ] .B gpgwrap [\-v] [\-i] [\-a] [\-p \fI\fR] [\-o \fI\fR] [\-\-] \fBgpg\fR [gpg options] .SH DESCRIPTION .PP The GNU Privacy Guard (\fBgpg\fR) supplies the option \-\-passphrase\-fd. This instructs \fBgpg\fR to read the passphrase from the given file descriptor. Usually this file descriptor is opened before \fBgpg\fR is executed via \fBexecvp(3)\fR. Exactly that is what \fBgpgwrap\fR is doing. The passphrase may be passed to \fBgpgwrap\fR in 4 ways: .RS .IP * 2 as file path, whereat the passphrase is stored as plain text in the file .IP * 2 it is piped from another program to the stdin of \fBgpgwrap\fR .IP * 2 through the \fBGPGWRAP_PASSPHRASE\fR environment variable .IP * 2 \fBgpgwrap\fR prompts for it .RE With no precautions the first point undermines the secure infrastructure \fBgpg\fR provides. But in pure batch oriented environments this may be what you want. Otherwise if you are willing to enter passphrases once and don't want them to be stored as plain text in a file \fBgpg\-agent\fR is what you are looking for. Another security objection could be the use of the environment variable \fBGPGWRAP_PASSPHRASE\fR which contains the passphrase and may be read by other processes of the same user. .SH OPTIONS .IP "\-V, \-\-version" 8 Print out version and exit. .IP "\-P, \-\-print" 8 Get the passphrase and print it mangled to stdout. .IP "\-F, \-\-file" 8 Read \fBgpg\fR commands from the given files. If \fI\fR is \- it is read from stdin. Exactly one command per line is expected. The given line is handled in the following way: .RS .IP * 2 In the first place the passphrase is mangled. This means that unusual characters are replaced by their backslash escaped octal numbers. .IP * 2 Secondly the mangled passphrase is stored in the environment variable \fBGPGWRAP_PASSPHRASE\fR. .IP * 2 \*(Q"exec gpgwrap \-\- \*(Q" is prepended to each line, before the result is passed as argument to \*(Q"sh \-c\*(Q". .RE .IP "\-h, \-\-help" 8 Print out usage information. .IP "\-v, \-\-verbose" 8 Increase verbosity level. .IP "\-i, \-\-interactive" 8 Always prompt for passphrase (ignores \-p and the environment variable). .IP "\-a, \-\-ask\-twice" 8 Ask twice if prompting for a passphrase. .IP "\-c, \-\-check\-exit\-code" 8 While reading gpg commands from a file, \fBgpgwrap\fR ignores per default the exit code of its child processes. This option enables the check of the exit code. If a child terminates abnormal or with an exit code not equal 0 \fBgpgwrap\fR stops immediately and does return with this exit code. See also section \fBBUGS\fR. .IP "\-p \fI\fR, \-\-passphrase\-file \fI\fR" 8 Read passphrase from \fI\fR. If \fI\fR is \- it is read from stdin. The passphrase is expected to be in plain text. If this option is not given the passphrase will be taken either from the environment variable \fBGPGWRAP_PASSPHRASE\fR or it will be prompted on the controlling tty if the environment variable is not set. .IP "\-o \fI\fR, \-\-option\-name \fI\fR" 8 Specify the name of the \*(Q"\-\-passphrase\-fd\*(Q" option understood by the program to be executed. This is useful if you want to use \fBgpgwrap\fR in combination with other programs than \fBgpg\fR. .SH LIMITATIONS The given passphrase is subject to several limitations depending on the way it was passed to \fBgpgwrap\fR: .RS .IP * 2 There is a size limitation: the passphrase should be not larger than some kilobytes (examine the source code for the exact limit). .IP * 2 \fBgpgwrap\fR allows you to use all characters in a passphrase even \\000, but this does not mean that \fBgpg\fR will accept it. \fBgpg\fR may reject your passphrase or may only read a part of it, if it contains characters like \\012 (in C also known as \\n). .IP * 2 If you set the environment variable \fBGPGWRAP_PASSPHRASE\fR you should take special care with the backslash character, because \fBgpgwrap\fR uses backslash to escape octal numbers, (see option \-F). Therefore write backslash itself as octal number: \\134. .RE .SH EXAMPLES .IP "1." 8 .Vb \&\fBgpgwrap\fR \-p /path/to/a/secret/file \\ \&\fBgpg\fR \-c \-z 0 \-\-batch \-\-no\-tty \\ \& \-\-cipher\-algo blowfish < infile > outfile .Ve Read passphrase from /path/to/a/secret/file and execute \fBgpg\fR to do symmetric encryption of infile and write it to outfile. .IP "2." 8 .Vb \&\fBgpgwrap\fR \-i \-a \\ \&\fBgpg\fR \-c \-z 0 \-\-batch \-\-no\-tty \\ \& \-\-cipher\-algo blowfish < infile > outfile .Ve Same as above except that \fBgpgwrap\fR prompts twice for the passphrase. .IP "3." 8 .Vb \&\fBgpgwrap\fR \-F \-i \- < \*(Q"$HOME/outfile1\*(Q" \&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile2\*(Q" > \*(Q"$HOME/outfile2\*(Q" \&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile3\*(Q" > \*(Q"$HOME/outfile3\*(Q" \&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile4\*(Q" > \*(Q"$HOME/outfile4\*(Q" \&EOL .Ve \fBgpgwrap\fR prompts for the passphrase and executes four instances of \fBgpg\fR to decrypt the given files. .IP "4." 8 .Vb \&\fBGPGWRAP_PASSPHRASE\fR=\*(Q"mysecretpassphrase\*(Q" \&export \fBGPGWRAP_PASSPHRASE\fR \&\fBgpgwrap\fR \-F \-c \-v /tmp/cmdfile1 \- /tmp/cmdfile2 < \*(Q"$HOME/outfile1\*(Q" \&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile2\*(Q" > \*(Q"$HOME/outfile2\*(Q" \&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile3\*(Q" > \*(Q"$HOME/outfile3\*(Q" \&\fBgpg\fR \-\-decrypt \-\-batch \-\-no\-tty < \*(Q"$HOME/infile4\*(Q" > \*(Q"$HOME/outfile4\*(Q" \&EOL .Ve Same as above except that \fBgpgwrap\fR gets the passphrase via the environment variable, reads commands additionally from other files and checks the exit code of every \fBgpg\fR instance. This means if one \fBgpg\fR command has a non zero exit code, no further commands are executed. Furthermore \fBgpgwrap\fR produces verbose output. .IP "5." 8 .Vb \&\fBGPGWRAP_PASSPHRASE\fR=\*(Q"$(\fBgpgwrap\fR \-P \-i \-a)\*(Q" \&export \fBGPGWRAP_PASSPHRASE\fR \& \&\fBfind\fR . \-maxdepth 1 \-type f | \&while read FILE; do \& FILE2=\*(Q"$FILE.bz2.gpg\*(Q" \& \fBbzip2\fR \-c \*(Q"$FILE\*(Q" | \& \fBgpgwrap\fR \fBgpg\fR \-c \-z 0 \-\-batch \-\-no\-tty \\ \& \-\-cipher\-algo blowfish > \*(Q"$FILE2\*(Q" && \& \fBtouch\fR \-r \*(Q"$FILE\*(Q" \*(Q"$FILE2\*(Q" && \& \fBrm\fR \-f \*(Q"$FILE\*(Q" \&done .Ve Read in passphrase, compress all files in the current directory, encrypt them and keep date from original file. .IP "6." 8 .Vb \&\fBfind\fR . \-maxdepth 1 \-type f \-name '*.bz2.gpg' | \&\fBawk\fR '{ \& printf(\*(Q"gpg \-\-decrypt \-\-batch \-\-no\-tty \-\-quiet \*(Q"); \& printf(\*(Q"\-\-no\-secmem\-warning < %s\\n\*(Q", $0); \& }' | \&\fBgpgwrap\fR \-F \-i \-c \- | \&\fBbzip2\fR \-d \-c \- | \&\fBgrep\fR \-i 'data' .Ve Decrypt all *.bz2.gpg files in the current directory, decompress them and print out all occurrences of data. If you pipe the result to \fBless\fR you get into trouble because \fBgpgwrap\fR and \fBless\fR try to read from the TTY at the same time. In such a case it is better to use the environment variable to give the passphrase (the example above shows how to do this). .IP "7." 8 .Vb \&\fBGPGWRAP_PASSPHRASE\fR=\*(Q"$(\fBgpgwrap\fR \-P \-i \-a)\*(Q" \&export \fBGPGWRAP_PASSPHRASE\fR \& \&\fBgpgwrap\fR \-P | \&\fBssh\fR \-C \-x \-P \-l user host \*(Q" \& \fBGPGWRAP_PASSPHRASE\fR=\\\*(Q"\\$(\fBcat\fR)\\\*(Q" \& ... \& \*(Q" .Ve Prompt for a passphrase twice and write it to the \fBGPGWRAP_PASSPHRASE\fR environment variable. .IP "8." 8 .Vb \&\fBecho\fR \-n \*(Q"Passphrase: \*(Q" \&\fBstty\fR \-echo \&read \fBGPGWRAP_PASSPHRASE\fR \&\fBecho\fR \&\fBstty\fR echo \&export \fBGPGWRAP_PASSPHRASE\fR .Ve Another way to prompt manually for the passphrase. It was needed in combination with older versions of \fBgpgwrap\fR, because they did not upport \-P. Be aware that with this method no automatic conversion to backslash escaped octal numbers takes place. .IP "9." 8 .Vb \&\fBecho\fR \*(Q"mysecretpassphrase\*(Q" | \&\fBgpg\fR \-\-batch \-\-no\-tty \-\-passphrase\-fd 0 \\ \& \-\-output outfile \-\-decrypt infile .Ve Cheap method to give passphrase to \fBgpg\fR without \fBgpgwrap\fR. Note that you can't use stdin to pass a file to \fBgpg\fR, because stdin is already used for the passphrase. .IP "10." 8 .Vb \&\fBgpg\fR \-\-batch \-\-no\-tty \\ \& \-\-passphrase\-fd 3 3< /path/to/a/secret/file \\ \& < infile > outfile .Ve This is a more advanced method to give the passphrase, it is equivalent to Option \-p of \fBgpgwrap\fR. This example should at least work with the bash. .IP "11." 8 .Vb \&\fBgpg\fR \-\-batch \-\-no\-tty \-\-passphrase\-fd 3 \\ \& 3< <(echo \*(Q"mysecretpassphrase\*(Q") \\ \& < infile > outfile .Ve Like above, but the passphrase is given directly. This example should at least work with the bash. .SH BUGS In version 0.02 of \fBgpgwrap\fR the exit code of \fBgpg\fR was only returned if \fBgpgwrap\fR read the passphrase from a file. Since version 0.03, only \-F omits exit code checking by default, but it can be enabled with \-c. .SH "SEE ALSO" \fBgpg\fR, \fBgpg\-agent\fR .SH AUTHOR Karsten Scheibler ./sig2dot/0000755000175000017500000000000013233575632012445 5ustar guilhemguilhem./sig2dot/sig2dot.10000644000175000017500000000504713233575632014110 0ustar guilhemguilhem.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH SIG2DOT 1 "May 31, 2006" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME sig2dot \- creates a .dot file from a GPG signature listing .SH SYNOPSIS .B sig2dot [OPTIONS] < input-file > output-file .br .SH DESCRIPTION This manual page documents briefly the .B sig2dot command. Sig2dot parses the output of "gpg \-\-list\-sigs" and produces a .dot file that can be used as input to a graphing program. .SH OPTIONS \fIsig2dot\fP accepts the following options: .TP 16 .B \-b Black and white / do not colorize. .TP 16 .B \-d Render graph as it appeared on (ignores more recent signatures). Date must be in the format "YYYY\-MM\-DD". Will also ignore keys that have since been revoked. .TP 16 .B \-s Produces a sory HTML statistics file with the number of signatures per node. .TP 16 .B \-a Graph all keys, even if they do not have a signature .TP 16 .B \-r "" Sets the text for the key-revoked string used by GnuPG. The default is what is used in the English version. This parameter can be set for versions of GnuPG localized to other languages. Default: "[revoked". .TP 16 .TP 16 .B \-t "" Sets the title for the graph generated. Default: "Keyring Statistics". .TP 16 .B \-u "<user-not-found-string>" Sets the text for the user-not-found string used by GnuPG. The default is what is used in the English version. This parameter can be set for versions of GnuPG localized to other languages. Default: "[user id not found]". .TP 16 .B \-q Be quiet. .TP 16 .B \-h Print help and exit. .TP 16 .B \-v Print version and exit. .SH EXAMPLE gpg \-\-list\-sigs | sig2dot > sigs.dot .sp 0 springgraph < sigs.dot > sigs.png .SH SEE ALSO .B springgraph(1) .B gnupg(1) .SH AUTHORS Darxus@ChaosReigns.com with modifications by Kevin Rosenberg. Versions 0.35 and later are by Christoph Berg <cb@df7cb.de>. This manual page was written by Kevin M. Rosenberg <kmr@debian.org>, for the Debian GNU/Linux system (but may be used by others). �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������./sig2dot/sig2dot�����������������������������������������������������������������������������������0000755�0001750�0001750�00000030437�13233575632�013755� 0����������������������������������������������������������������������������������������������������ustar �guilhem�������������������������guilhem����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������#!/usr/bin/perl -w # sig2dot v0.29 (c) Darxus@ChaosReigns.com, released under the GPL # Download from: http://www.chaosreigns.com/code/sig2dot/ # sig2dot v0.35-0.37 (c) 2005, 2006 Christoph Berg <cb@df7cb.de> # Download from: http://ftp.debian.org/debian/pool/main/s/sig2dot/ # 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 2 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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Parses output of "gpg --list-sigs" into a format # suitable for rendering into a graph by graphviz # (http://www.research.att.com/sw/tools/graphviz/) like so: # # $ gpg --list-sigs --keyring ./phillylinux.gpg | ./sig2dot.pl > phillylinux.dot # $ neato -Tps phillylinux.dot > phillylinux.ps # $ convert phillylinux.ps phillylinux.jpg # # Commandline options: # # -b # Black and white / do not colorize. # # -d <date> # Render graph as it appeared on <date> (ignores more recent # signatures). Date must be in the format "YYYY-MM-DD". # Will also ignore keys that have since been revoked. # # -a # Render all keys, even if they're not signed by any other key. # # -u <"string"> # Support localized output of GnuPG for unknown user IDs. For # example, German users have to write (with sh quotation marks!) # "[User-ID nicht gefunden]" if they use GnuPG with German # messages. Default is "[User id not found]". # # -r <"string"> # Support localized output of GnuPG for revoked keys. For # example, French users have to write "révoqué" if they use # GnuPG with French messages. Default is "[revoked". # # -s stats.html # Produces statistics file with number of signatures per node # # -t <"string"> # Graph title # # -h print help # -v print version # -q be quiet # # Changes: # # v0.9 2000-09-14 19:20 strip trailing whitespace from $id more cleanly # v0.10 2000-09-14 19:33 skip revoked keys at the request of Peter Palfrader <ppalfrad@cosy.sbg.ac.at> # v0.11 Nov 22 21:38 use ID for node name instead of username for uniqueness # v0.12 Dec 15 16:20 use names instead of IDs again in stats.html # v0.13 Jun 19 03:15 red is proportional to signatures # v0.14 Jun 19 03:25 blue is proportional to other keys signed # v0.15 Jun 20 17:16 fixed blue, green is proportional to ratio # v0.16 Jun 20 18:55 uniqed %signedby # v0.17 Jan 10 19:10 Use overlap=scale instead of fixed edge lengths. Requires new version of graphviz. # v0.18 Jan 23 11:53 stats.html is now valid html (v.01 transitional) # v0.23 May 3 18:52 bunch of new stuff, including -b flag (black & white), and fixes devision by zero error # v0.24 May 3 18:59 add black outline to nodes, prettier (changed node attribute "color" to "fillcolor") # v0.25 May 3 19:06 cleaned up anti- devision by zero code a little # v0.26 May 4 00:08 strip all non-digit characters from $renderdate # v0.27 May 10 00:23:49 2002 use {}'s to write 1 line per public key instead of one line per signature (much shorter) # v0.28 Feb 13 2003 Change regex to handle option trust digit # <kevin@rosenberg.net> # v0.29 Feb 18 2003 Add -s option to optionally produce statistics file # <kevin@rosenberg.net> # v0.30 Feb 18 2003 Make --list-sigs regex more robust # Marco Bodrato <bodrato@gulp.linux.it> # v0.31 Jul 28 2003 Add -u option for localized output of GnuPG # Marcus Frings <protagonist@gmx.net> # further changes are documented in debian/changelog use strict; my $version = "0.37"; my $chartchar = "*"; my $renderdate = ""; my ($stats, $color, $all, $not_found, $revokestr, $title); use Getopt::Std; my %opt; getopts('d:u:r:s:bahqvt:', \%opt); sub version { print <<EOT; sig2dot $version Copyright (c) 2002 Darxus\@ChaosReigns.com Copyright (c) 2005, 2006 Christoph Berg <cb\@df7cb.de> EOT } if ($opt{h}) { version(); print <<EOT; gpg --list-sigs | $0 [-abdhqsuv] > sigs.dot -a Graph all keys, even if they do not have a signature -b Black and white / do not colorize. -d YYYY-MM-DD Render graph as it appeared on date. -h Print this help and exit. -q Be quiet. -r sting key-is-revoked string (default: "[revoked"). -s stats.html Produces statistics file with number of signatures per node. -t title Graph title -u string user-id-not-found string (default: "[user id not found]"). -v Print version and exit. EOT exit 0; } if ($opt{v}) { version(); exit 0; } if ($opt{d}) { $renderdate = $opt{d}; print STDERR "Printing from date: $renderdate.\n"; $renderdate =~ s/\D+//g; } if ($opt{s}) { $stats = $opt{s}; print STDERR "Print statistics to $stats.\n"; } if ($opt{b}) { $color = 0; print STDERR "Black and White.\n" unless $opt{q}; } else { $color = 1; print STDERR "Color.\n" unless $opt{q}; } if ($opt{a}) { $all = 1; } else { $all = 0; } if ($opt{u}) { $not_found = lc $opt{u}; } else { $not_found = "[user id not found]"; # this changed from gpg 1.2 -> 1.4 } if ($opt{r}) { $revokestr = lc $opt{r}; } else { $revokestr = "[revoked"; # this changed from gpg 1.2 -> 1.4 } if ($opt{t}) { $title = lc $opt{t}; } else { $title = "Keyring Statistics"; } my ($owner, %name, %revlist, %sigstmp, %signedbytmp, %idlist, @names, %revs); while (my $line = <>) { chomp $line; # gpg 1.2 #pub 1024D/807CAC25 2003-08-01 Michael Ablassmeier (abi) <abi#grinser.de> #sig B3B2A12C 2004-01-28 [User id not found] #sig 3 9456ADE2 2004-02-07 Michael Schiansky <michael#schiansky.de> # gpg 1.4: #pub 1024D/807CAC25 2003-08-01 #uid Michael Ablassmeier (abi) <abi#grinser.de> #sig B3B2A12C 2004-01-28 [User ID not found] #sig 3 9456ADE2 2004-02-07 Michael Schiansky <michael#schiansky.de> # type id date name if ($line =~ m#(pub|uid|sig|rev|sub)[ !\?][ \dLNPRTX]{0,8} +([^ ]+) +([^ ]+)(?: +"?([^<"]*))?#) # differences: # " " -> "[ !\?]" (to use 'gpg --check-sigs|sig2dot.mio|springgraph|display') # "[ \d]" -> "[ \dLRXP]" (signature attributes) # "[^<]+" -> "[^<]*" (to recognise "pub" lines whitout a name) # if ($line =~ m#([\w]+) [ \d]? +([^ ]+) +([^ ]+) +([^<]+)#) # if ($line =~ m#([\w]+) +([^ ]+) +([^ ]+) +([^<]+)#) { my $type = $1; my $id = $2; my $date = $3; my $name = $4 || ""; $date =~ tr/-//d; if (grep {$type eq $_} qw/sig rev/ and $renderdate ne "" and $date > $renderdate) { print STDERR "Skipping due to date: $line\n"; } else { print STDERR "Using: $line\n" unless $opt{q}; # strip trailing whitespace more cleanly: $name =~ s/\s+$//g; #Remove re: http://bugs.debian.org/202484 #$name =~ s/[^a-zA-Z \.0-9]/_/g; # handle non-7bit names if ($type eq "pub") { $id = (split('/',$id))[1]; $owner = $id; $idlist{$id} = 1 if (index($name, $revokestr) < 0); } # remove comment field $name{$id} = (split ' \(', $name)[0] if $name; # gpg 1.4 fixup # skip revoked keys if (index($name, $revokestr) >= 0) { $revlist{$id} = 1; next; } if ($type eq "uid") { $name{$owner} = $id; # gpg 1.4 fixup } # unless (@{$sigs{$owner}}) # { # @{$sigs{$owner}} = (); # } if ($type eq "sig" and lc $name ne $not_found) { if ($id ne $owner) { push (@{$sigstmp{$owner}},$id); push (@{$signedbytmp{$id}},$owner); } if ($all or $id ne $owner) { push (@names,$id,$owner); } } if ($type eq "rev" and lc $name ne $not_found) { if ($id ne $owner) { push (@{$revs{$owner}},$id); #push (@{$revokedby{$id}},$owner); } } } } else { print STDERR "Skipping due to regex: $line\n" if $line ne ""; } } my (%sigs, %signedby); for my $id (sort {$sigstmp{$a} <=> $sigstmp{$b}} keys %sigstmp) { next if (defined $revlist{$id}); foreach my $owner (@{$signedbytmp{$id}}) { next if (defined $revlist{$owner}); my $revoke = 0; foreach my $revid (@{$revs{$owner}}) { if ($revid eq $id) { $revoke = 1; } } #$res = $revlist{$id}; if (($revoke == 0)) { push (@{$sigs{$owner}},$id); push (@{$signedby{$id}},$owner); } } } print "digraph \"$title\" {\noverlap=scale\nsplines=true\nsep=.1\n"; my %saw; @saw{@names} = (); @names = keys %saw; undef %saw; my $maxsigcount = 0; my (%sigcount); for my $owner (sort {$sigs{$a} <=> $sigs{$b}} keys %sigs) { undef %saw; @saw{@{$sigs{$owner}}} = (); @{$sigs{$owner}} = keys %saw; undef %saw; undef %saw; $signedby{$owner} ||= []; @saw{@{$signedby{$owner}}} = (); @{$signedby{$owner}} = keys %saw; undef %saw; $sigcount{$owner} = scalar(@{$sigs{$owner}}); if ($sigcount{$owner} > $maxsigcount) { $maxsigcount = $sigcount{$owner}; } } my %signedbycount; my ($maxsignedbycount, $maxratio) = (0, 0); for my $owner (sort {$signedby{$a} <=> $signedby{$b}} keys %signedby) { $signedbycount{$owner} = scalar(@{$signedby{$owner}}); if ($signedbycount{$owner} > $maxsignedbycount) { $maxsignedbycount = $signedbycount{$owner}; } if ($sigcount{$owner} and $sigcount{$owner} > 0) { if ($signedbycount{$owner} / $sigcount{$owner} > $maxratio) { $maxratio = $signedbycount{$owner} / $sigcount{$owner}; } } } print "//$maxratio\n"; if ($stats) { open (STATS,">$stats"); print STATS "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<html><head><title>$title\n"; for my $owner (sort {$sigcount{$b} <=> $sigcount{$a}} keys %sigs) { print STATS "
$name{$owner}$sigcount{$owner}\"".\n"; } print STATS "
\n"; close STATS; } print "node [style=filled]\n"; for my $id (@names) { if ((not exists $sigcount{$id}) and (not exists $signedbycount{$id}) and not $all) { next; } next unless (defined $idlist{$id}); if ($color) { my ($red, $green, $blue) = (0, 1/3, 1/3); if ($sigcount{$id}) { $red = $sigcount{$id} / $maxsigcount; } if ($sigcount{$id} && $maxratio != 0) { $green = ($signedbycount{$id} / $sigcount{$id} / $maxratio * .75) * 2/3 + 1/3; } if ($signedbycount{$id} and $maxsignedbycount != 0) { $blue = ($signedbycount{$id} / $maxsignedbycount) * 2/3 + 1/3; } my ($hue,$saturation,$value) = rgb2hsv($red,$green,$blue); printf "//%d %d $red,$green,$blue\n", $sigcount{$id} || 0, $signedbycount{$id} || 0; print "\"$id\" [fillcolor=\"$hue,$saturation,$value\",label=\"$name{$id}\"]\n"; } else { print "\"$id\" [label=\"$name{$id}\"]\n"; } } #print "node [style=solid]\n"; for my $owner (sort keys %sigs) { print "{ "; for my $id (@{$sigs{$owner}}) { print "\"$id\" "; } print "} -> \"$owner\"\n"; } print "}\n"; # Converts rgb to hsv. All numbers are within range 0 to 1 # from http://twiki.org/cgi-bin/view/Codev/WebMap sub rgb2hsv { my ($r, $g ,$b) = @_; my $max = maxof($r, maxof($g, $b)); my $min = minof($r, minof($g, $b)); my $v = $max; my ($s, $h); if ($max > 0.0) { $s = ($max - $min) / $max; } else { $s = 0; } if ($s > 0.0) { my ($rc, $gc, $bc, $diff); $diff = $max - $min; $rc = ($max - $r) / $diff; $gc = ($max - $g) / $diff; $bc = ($max - $b) / $diff; if ($r == $max) { $h = ($bc - $gc) / 6.0; } elsif ($g == $max) { $h = (2.0 + $rc - $bc) / 6.0; } else { $h = (4.0 + $gc - $rc) / 6.0; } } else { $h = 0.0; } if ($h < 0.0) { $h += 1.0; } return ($h, $s, $v); } sub maxof { my ($a, $b) = @_; return $a>$b?$a:$b; } sub minof { my ($a, $b) = @_; return $a<$b?$a:$b; } # vim:sw=2: ./sig2dot/README.sig2dot0000644000175000017500000000065013233575632014700 0ustar guilhemguilhemsig2dot ------- Sig2dot parses the output of "gpg --list-sigs" into a format suitable for rendering into a graph by springgraph or graphviz. The syntax definition of the .dot files which sig2dot produces can be found in the graphviz man pages. A copy is here: * http://www.graphviz.org/Documentation.php * http://www.graphviz.org/cvs/doc/info/lang.html -- Christoph Berg Sun, 6 Mar 2005 18:11:08 +0100 ./sig2dot/Makefile0000644000175000017500000000034513233575632014107 0ustar guilhemguilhemall: install: install -D sig2dot $(DESTDIR)/usr/bin/sig2dot install -D -m644 sig2dot.1 $(DESTDIR)/usr/share/man/man1/sig2dot.1 install -D -m644 README.sig2dot \ $(DESTDIR)/usr/share/doc/signing-party/README.sig2dot clean: ./gpg-mailkeys/0000755000175000017500000000000013233575735013467 5ustar guilhemguilhem./gpg-mailkeys/README0000644000175000017500000000270013233575632014342 0ustar guilhemguilhemgpg-mailkeys ------------ Given one or more key-ids, gpg-mailkeys mails these keys to their owners. You use this after you've signed them. By default, the mails contain a standard text and your name and address as the From (as determined by the sendmail command). You can modify how this script behaves by putting a .gpg-mailkeysrc file in your homedir. An example of this file is provided with this document. CREDITS ------- This script comes from the original Signing-Party package which has been originally created and maintained by Simon Richter and Thijs Kinkhorst . COPYRIGHT & LICENSE ------------------- Copyright (C) 2001-2005 Simon Richter and Thijs Kinkhorst 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 2 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ./gpg-mailkeys/example.gpg-mailkeysrc0000644000175000017500000000106013233575632017753 0ustar guilhemguilhem# ~/.gpg-mailkeysrc # Sender name NAME="Your name here" # Sender address (don't use "<...>"!) EMAIL="your.address@example.org" # Subject SUBJECT="Your signed key" # Text to be included in the message body TEXT=" Hi, here you are: your signed key. See you! " # You can add here whatever arguments you want to pass to sendmail. # For example, if you use ssmtp, you may want to set your credentials this way # SENDMAIL_ARGS="-au myusername -aps3cr3t" # to authenticate to the smtp server with username myusername and password # s3cr3t. SENDMAIL_ARGS="" ./gpg-mailkeys/gpg-mailkeys0000755000175000017500000000553213233575735016013 0ustar guilhemguilhem#! /bin/sh # # gpg-mailkeys: mail out just signed keys to their owners # # $Id: gpg-mailkeys 764 2015-01-22 22:48:30Z Guilhem Moulin $ set -e VERSION='$Rev: 764 $' # Define the charset used in the text message of the mail LOCAL_CHARSET="" ## # Get the local charset. # # The local charset is deduced from the charset used by both ~/.gpg-mailkeysrc # and ~/.signature. If none of these files exist, the local charset is assumed # to be us-ascii. get_local_charset () { local charset="us-ascii" local file_list="$HOME/.signature $HOME/.gpg-mailkeysrc" for filename in $file_list; do if [ -e $filename ]; then charset=`file --mime-encoding $filename | cut -d ' ' -f 2` break fi done; LOCAL_CHARSET=$charset } if [ -z "$*" ]; then printf "Send people their newly signed GPG key by mail.\n" printf "Usage: $0 keyid ...\n" exit 1 fi if [ -e ~/.gpg-mailkeysrc ] ; then . ~/.gpg-mailkeysrc fi if [ -n "$EMAIL" ]; then FROM="$EMAIL" fi if [ -z "$FROM" ]; then echo "Error: No EMAIL set in ~/.gpg-mailkeysrc and no FROM set in environment." exit 1 fi if [ -z "$SUBJECT" ]; then SUBJECT="Your signed GPG key" fi if [ -z "$NAME" ]; then NAME=`getent passwd $USER | cut -d: -f5 | cut -d, -f1` fi if [ -z "$TEXT" ]; then TEXT="Hi, Here is your signed GPG key. Enjoy, $NAME" fi get_local_charset FAILKEYS= while [ -n "$1" ]; do printf "[$1] " TEMPFILE=`mktemp -t gpg2mail.XXXXXX` ADDR=`${GNUPGBIN:-gpg} --with-colons --fixed-list-mode --list-key $1 | sed -e 's/^uid:[^rei:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:\([^:<]*<[^:>]*>\):.*/@@uid@@ \1/' -e '/^@@uid@@ /!d' -e 's/([^)]*)//g' -e 's/ */ /g' -e 's/^@@uid@@ //' | head -1` if [ -z "$ADDR" ]; then printf "(no usable user ids)\n" FAILKEYS="$FAILKEYS:$1" shift 1 continue fi NANOTIME=`date +%s-%N` BOUNDARY="ksp-$$-boundary-$NANOTIME" printf "$ADDR:" printf >$TEMPFILE "From: $NAME <$FROM>\n" cat << EOM >> $TEMPFILE To: $ADDR Subject: $SUBJECT User-Agent: gpg-mailkeys/$VERSION MIME-Version: 1.0 Content-Type: multipart/mixed; micalg=pgp-sha1; boundary="$BOUNDARY" Content-Disposition: inline --$BOUNDARY Content-Type: text/plain; charset=$LOCAL_CHARSET Content-Disposition: inline Content-Transfer-Encoding: quoted-printable `echo "$TEXT" | qprint -e` EOM if [ -f ~/.signature ]; then printf -- "--=20\n" >> $TEMPFILE qprint -e ~/.signature >> $TEMPFILE fi cat << EOM >> $TEMPFILE --$BOUNDARY Content-Type: application/pgp-keys Content-Disposition: attachment; filename="$1.asc" `${GNUPGBIN:-gpg} --armor --export $1` --$BOUNDARY-- EOM printf " sending" ${SENDMAIL:-/usr/sbin/sendmail} $SENDMAIL_ARGS -ti <$TEMPFILE rm $TEMPFILE printf " done.\n" shift 1 done if [ -n "$FAILKEYS" ]; then printf "\nNote: The following keys could not be sent:\n" printf "$FAILKEYS\n" | tr ':' '\n' | sed -e '/^ *$/d' -e 's/^/ /' fi ./gpg-mailkeys/gpg-mailkeys.10000644000175000017500000000321513233575632016137 0ustar guilhemguilhem.\" .TH GPG-MAILKEYS 1 "Nov 23, 2010" .SH NAME .B gpg\-mailkeys \- sends emails containing keys to their owners .SH SYNOPSIS .B gpg-mailkeys .RI id ... .SH DESCRIPTION .B gpg-mailkeys sends the owners of the keys listed on the command line a mail each, containing their public key. After signing, you can use this tool to send everyone the new signatures they just got. If you're interested in encrypting the signature before it's sent, in order to verify that the associated address actually belongs to the key owner, you should check out .B caff .SH OPTIONS None .SH ENVIRONMENT .TP 13 .I NAME Set the name to use when sending messages .TP 13 .I EMAIL Set the email address to use when sending messages .TP 13 .I SUBJECT Set the subject of the messages you send .TP 13 .I TEXT Set the body text of the messages .TP 13 .I SENDMAIL Set the path to sendmail. Default: /usr/sbin/sendmail .TP 13 .I SENDMAIL_ARGS Set arguments to pass to $SENDMAIL. This can be useful to set credentials in order to authenticate to an smtp server when using ssmtp for example. .TP 13 .I HOME Set the default home directory. .TP 13 .I GNUPGBIN Set the gpg binary. Default: "gpg". .TP 13 .I GNUPGHOME Set the default working directory for gpg. Default: "~/.gnupg". .SH FILES The behavior of .B gpg-mailkeys can be updated through some files available in the user home directory. Thus, .B ~/.gpg-mailkeysrc allows the user to overwrite the default value for the above environment variables, and any text in .B ~/.signature will be appended to the end of the body text of the message. .SH SEE ALSO .BR gpg (1), caff (1) .SH AUTHOR This manual page was written by Simon Richter . ./gpgdir/0000755000175000017500000000000013233575632012346 5ustar guilhemguilhem./gpgdir/VERSION0000644000175000017500000000000613233575632013412 0ustar guilhemguilhem1.9.5 ./gpgdir/README0000644000175000017500000000252013233575632013225 0ustar guilhemguilhemFile: gpgdir Author: Michael Rash Download: http://www.cipherdyne.org/gpgdir License: GNU General Public License Version: 0.9.8 gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to encrypt and decrypt directories using a gpg key specified in ~/.gpgdirrc. Gpgdir recursively descends through a directory in order to make sure it encrypts or decrypts every file in a directory and all of its subdirectories. By default the mtime and atime values of all files will be preserved upon encryption and decryption (this can be disabled with the --no-preserve-times option). Note that in --encrypt mode, gpgdir will delete the original files that it successfully encrypts (unless the --no-delete option is given). However, upon startup gpgdir first asks for the decryption password to be sure that a dummy file can successfully be encrypted and decrypted. The initial test can be disabled with the --skip-test option so that a directory can easily be encrypted without having to also specify a password (this is consistent with gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and directories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file to be encrypted. Installation: Just run the install.pl script (as root) that comes with the gpgdir sources. ./gpgdir/install.pl0000755000175000017500000003355413233575632014366 0ustar guilhemguilhem#!/usr/bin/perl -w # #################################################################### # # File: install.pl # # Purpose: To install gpgdir on a Linux system. # # Author: Michael Rash (mbr@cipherdyne.org) # # Copyright (C) 2002-2008 Michael Rash (mbr@cipherdyne.org) # # License (GNU Public License): # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # #################################################################### # # $Id: install.pl 311 2008-08-31 23:11:12Z mbr $ # use Cwd; use File::Copy; use Getopt::Long; use strict; #======================= config ======================= my $install_dir = '/usr/bin'; my $libdir = '/usr/lib/gpgdir'; my $manpage = 'gpgdir.1'; ### only used it $ENV{'HOME'} is not set for some reason my $config_homedir = ''; ### system binaries my $gzipCmd = '/usr/bin/gzip'; my $perlCmd = '/usr/bin/perl'; my $makeCmd = '/usr/bin/make'; #===================== end config ===================== my $print_help = 0; my $uninstall = 0; my $force_mod_re = ''; my $exclude_mod_re = ''; my $skip_module_install = 0; my $cmdline_force_install = 0; my $locale = 'C'; ### default LC_ALL env variable my $no_locale = 0; my $deps_dir = 'deps'; my %cmds = ( 'gzip' => $gzipCmd, 'perl' => $perlCmd, 'make' => $makeCmd ); ### map perl modules to versions my %required_perl_modules = ( 'Class::MethodMaker' => { 'force-install' => 0, 'mod-dir' => 'Class-MethodMaker' }, 'GnuPG::Interface' => { 'force-install' => 0, 'mod-dir' => 'GnuPG-Interface' }, 'Term::ReadKey' => { 'force-install' => 0, 'mod-dir' => 'TermReadKey' } ); ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); &usage(1) unless (GetOptions( 'force-mod-install' => \$cmdline_force_install, ### force install of all modules 'Force-mod-regex=s' => \$force_mod_re, ### force specific mod install with regex 'Exclude-mod-regex=s' => \$exclude_mod_re, ### exclude a particular perl module 'Skip-mod-install' => \$skip_module_install, 'home-dir=s' => \$config_homedir, ### force a specific home dir 'LC_ALL=s' => \$locale, 'locale=s' => \$locale, 'no-LC_ALL' => \$no_locale, 'no-locale' => \$no_locale, ### synonym 'uninstall' => \$uninstall, # Uninstall gpgdir. 'help' => \$print_help # Display help. )); &usage(0) if $print_help; ### set LC_ALL env variable $ENV{'LC_ALL'} = $locale unless $no_locale; $force_mod_re = qr|$force_mod_re| if $force_mod_re; $exclude_mod_re = qr|$exclude_mod_re| if $exclude_mod_re; ### check to see if we are installing in a Cygwin environment my $non_root_user = 0; if (&is_cygwin()) { print "[+] It looks like you are installing gpgdir in a Cygwin environment.\n"; $non_root_user = 1; } else { unless ($< == 0 && $> == 0) { print "[+] It looks like you are installing gpgdir as a non-root user, so gpgdir\n", " will be installed in your local home directory.\n\n"; $non_root_user = 1; } } if ($non_root_user) { ### we are installing as a normal user instead of root, so see ### if it is ok to install within the user's home directory my $homedir = ''; if ($config_homedir) { $homedir = $config_homedir; } else { $homedir = $ENV{'HOME'} or die '[*] Could not get home ', "directory, set the $config_homedir var."; } print " gpgdir will be installed at $homedir/bin/gpgdir, and a few\n", " perl modules needed by gpgdir will be installed in $homedir/lib/gpgdir/.\n\n", mkdir "$homedir/lib" unless -d "$homedir/lib"; $libdir = "$homedir/lib/gpgdir"; $install_dir = "$homedir/bin"; } ### make sure we can find the system binaries ### in the expected locations. &check_commands(); my $src_dir = getcwd() or die "[*] Could not get current working directory."; ### create directories, make sure executables exist, etc. &setup(); print "[+] Installing gpgdir in $install_dir\n"; &install_gpgdir(); ### install perl modules unless ($skip_module_install) { for my $module (keys %required_perl_modules) { &install_perl_module($module); } } chdir $src_dir or die "[*] Could not chdir $src_dir: $!"; print "[+] Installing man page.\n"; &install_manpage(); print "\n It is highly recommended to run the test suite in the test/\n", " directory to ensure proper gpgdir operation.\n", "\n[+] gpgdir has been installed!\n"; exit 0; #===================== end main ======================= sub install_gpgdir() { die "[*] gpgdir does not exist. Download gpgdir from " . "http://www.cipherdyne.org/gpgdir" unless -e 'gpgdir'; copy 'gpgdir', "${install_dir}/gpgdir" or die "[*] Could not copy " . "gpgdir to $install_dir: $!"; if ($non_root_user) { open F, '<', "$install_dir/gpgdir" or die "[*] Could not open ", "${install_dir}/gpgdir: $!"; my @lines = ; close F; open P, '>', "$install_dir/gpgdir.tmp" or die "[*] Could not open ", "${install_dir}/gpgdir.tmp: $!"; for my $line (@lines) { ### change the lib dir to new homedir path if ($line =~ m|^\s*use\s+lib\s+\'/usr/lib/gpgdir\';|) { print P "use lib '", $libdir, "';\n"; } else { print P $line; } } close P; move "${install_dir}/gpgdir.tmp", "${install_dir}/gpgdir" or die "[*] Could not move ${install_dir}/gpgdir.tmp -> ", "${install_dir}/gpgdir: $!"; chmod 0700, "${install_dir}/gpgdir" or die "[*] Could not set " . "permissions on gpgdir to 0755"; } else { chmod 0755, "${install_dir}/gpgdir" or die "[*] Could not set " . "permissions on gpgdir to 0755"; chown 0, 0, "${install_dir}/gpgdir" or die "[*] Could not chown 0,0,${install_dir}/gpgdir: $!"; } return; } sub install_perl_module() { my $mod_name = shift; chdir $src_dir or die "[*] Could not chdir $src_dir: $!"; chdir $deps_dir or die "[*] Could not chdir($deps_dir): $!"; die '[*] Missing force-install key in required_perl_modules hash.' unless defined $required_perl_modules{$mod_name}{'force-install'}; die '[*] Missing mod-dir key in required_perl_modules hash.' unless defined $required_perl_modules{$mod_name}{'mod-dir'}; if ($exclude_mod_re and $exclude_mod_re =~ /$mod_name/) { print "[+] Excluding installation of $mod_name module.\n"; return; } my $version = '(NA)'; my $mod_dir = $required_perl_modules{$mod_name}{'mod-dir'}; if (-e "$mod_dir/VERSION") { open F, '<', "$mod_dir/VERSION" or die "[*] Could not open $mod_dir/VERSION: $!"; $version = ; close F; chomp $version; } else { print "[-] Warning: VERSION file does not exist in $mod_dir\n"; } my $install_module = 0; if ($required_perl_modules{$mod_name}{'force-install'} or $cmdline_force_install) { ### install regardless of whether the module may already be ### installed $install_module = 1; } elsif ($force_mod_re and $force_mod_re =~ /$mod_name/) { print "[+] Forcing installation of $mod_name module.\n"; $install_module = 1; } else { if (has_perl_module($mod_name)) { print "[+] Module $mod_name is already installed in the ", "system perl tree, skipping.\n"; } else { ### install the module in the /usr/lib/gpgdir directory because ### it is not already installed. $install_module = 1; } } if ($install_module) { unless (-d $libdir) { print "[+] Creating $libdir\n"; mkdir $libdir, 0755 or die "[*] Could not mkdir $libdir: $!"; } print "[+] Installing the $mod_name $version perl " . "module in $libdir/\n"; my $mod_dir = $required_perl_modules{$mod_name}{'mod-dir'}; chdir $mod_dir or die "[*] Could not chdir to ", "$mod_dir: $!"; unless (-e 'Makefile.PL') { die "[*] Your $mod_name source directory appears to be incomplete!\n", " Download the latest sources from ", "http://www.cipherdyne.org/\n"; } system "$cmds{'make'} clean" if -e 'Makefile'; system "$cmds{'perl'} Makefile.PL PREFIX=$libdir LIB=$libdir"; system $cmds{'make'}; # system "$cmds{'make'} test"; system "$cmds{'make'} install"; chdir $src_dir or die "[*] Could not chdir $src_dir: $!"; print "\n\n"; } chdir $src_dir or die "[*] Could not chdir $src_dir: $!"; return; } sub has_perl_module() { my $module = shift; # 5.8.0 has a bug with require Foo::Bar alone in an eval, so an # extra statement is a workaround. my $file = "$module.pm"; $file =~ s{::}{/}g; eval { require $file }; return $@ ? 0 : 1; } sub install_manpage() { if ($non_root_user) { print "[+] Because this is a non-root install, the man page will not be installed\n", " but you can download it here: http://www.cipherdyne.org/gpgdir\n\n"; return; } die "[*] man page: $manpage does not exist. Download gpgdir " . "from http://www.cipherdyne.org/gpgdir" unless -e $manpage; ### default location to put the gpgdir man page, but check with ### /etc/man.config my $mpath = '/usr/share/man/man1'; if (-e '/etc/man.config') { ### prefer to install $manpage in /usr/local/man/man1 if ### this directory is configured in /etc/man.config open M, '<', '/etc/man.config' or die "[*] Could not open /etc/man.config: $!"; my @lines = ; close M; ### prefer the path "/usr/share/man" my $found = 0; for my $line (@lines) { chomp $line; if ($line =~ m|^MANPATH\s+/usr/share/man|) { $found = 1; last; } } ### try to find "/usr/local/man" if we didn't find /usr/share/man unless ($found) { for my $line (@lines) { chomp $line; if ($line =~ m|^MANPATH\s+/usr/local/man|) { $mpath = '/usr/local/man/man1'; $found = 1; last; } } } ### if we still have not found one of the above man paths, ### just select the first one out of /etc/man.config unless ($found) { for my $line (@lines) { chomp $line; if ($line =~ m|^MANPATH\s+(\S+)|) { $mpath = $1; last; } } } } mkdir $mpath, 0755 unless -d $mpath; my $mfile = "${mpath}/${manpage}"; print "[+] Installing $manpage man page as: $mfile\n"; copy $manpage, $mfile or die "[*] Could not copy $manpage to " . "$mfile: $!"; chmod 0644, $mfile or die "[*] Could not set permissions on ". "$mfile to 0644"; chown 0, 0, $mfile or die "[*] Could not chown 0,0,$mfile: $!"; print "[+] Compressing man page: $mfile\n"; ### remove the old one so gzip doesn't prompt us unlink "${mfile}.gz" if -e "${mfile}.gz"; system "$cmds{'gzip'} $mfile"; return; } ### check paths to commands and attempt to correct if any are wrong. sub check_commands() { my @path = qw( /bin /sbin /usr/bin /usr/sbin /usr/local/bin /usr/local/sbin ); CMD: for my $cmd (keys %cmds) { unless (-x $cmds{$cmd}) { my $found = 0; PATH: for my $dir (@path) { if (-x "${dir}/${cmd}") { $cmds{$cmd} = "${dir}/${cmd}"; $found = 1; last PATH; } } unless ($found) { die "[*] Could not find $cmd anywhere!!! ", "Please edit the config section to include the path to ", "$cmd.\n"; } } unless (-x $cmds{$cmd}) { die "[*] $cmd is located at ", "$cmds{$cmd} but is not executable by uid: $<\n"; } } return; } sub is_cygwin() { my $rv = 0; ### get OS output from uname open UNAME, '-|', qw/uname -o/ or return $rv; while () { $rv = 1 if /Cygwin/; } close UNAME; return $rv; } sub setup() { unless (-d $libdir) { mkdir $libdir, 0755 or die "[*] Could not create $libdir: $!" } return; } sub usage() { my $exit_status = shift; print <<_HELP_; Usage: install.pl [options] -u, --uninstall - Uninstall gpgdir. -f, --force-mod-install - Force all perl modules to be installed even if some already exist in the system /usr/lib/perl5 tree. -F, --Force-mod-regex - Specify a regex to match a module name and force the installation of such modules. -E, --Exclude-mod-regex - Exclude a perl module that matches this regular expression. -S, --Skip-mod-install - Do not install any perl modules. -L, --LANG - Specify LANG env variable (actually the LC_ALL variable). -h --help - Prints this help message. _HELP_ exit $exit_status; } ./gpgdir/test/0000755000175000017500000000000013233575632013325 5ustar guilhemguilhem./gpgdir/test/conf/0000755000175000017500000000000013233575632014252 5ustar guilhemguilhem./gpgdir/test/conf/test.pw0000644000175000017500000000001313233575632015573 0ustar guilhemguilhemgpgdirtest ./gpgdir/test/conf/test-gpg/0000755000175000017500000000000013233575632016004 5ustar guilhemguilhem./gpgdir/test/conf/test-gpg/trustdb.gpg0000644000175000017500000000240013233575632020166 0ustar guilhemguilhemgpgG¹œõ  å‰oLõ0;.ž6Ò â7]}¹ ¬SVŒ›ìßì&p $é Ø…µ./gpgdir/test/conf/test-gpg/secring.gpg0000644000175000017500000000250013233575632020132 0ustar guilhemguilhem•áG¹œÝ•Û~¢ÅN/Ð!lè•UjgQ‡´“/ç£q’R'ò¡)”â·yX‰ Ì+ðM&ma%ì[$Ds*£à¯‘Ó+‰Þ9™[Åé¶Ç‡ÿŽ=ú«|â³%ðsÁò>#SŸj‚±™‰­pÓxúrùIЩײ)BF#ÓîÀƳ; £É)ž1<ÿ${ÌŸ»Ã[{‰Sý™Ð?œ"†¯1v¾ž)%9Ãâ‘h!ÛSyý¨ì>Ç#c¯¨;E?rÄ¢K»tå‰dá¼iVâu¬Õé.æ'“Ø…‹(×›TÂölˆÓáÎñœlñ‡¢€¬PG}Üeßï“ùàjÜæ½Ö”ðþ\;ѧâÆÿwßð fÛÐ:O¼f•¿ž‚¬47Á+÷NŒ=V·PEQ&_‰>Ÿ8´–òMÞdy{†&ÚF¸«QØ8nÌBVҿωÚwšÓ ¾íž ŸÃ3ñNÞo˜ì{_¦ÈKcÿ“å¼u§•´ØéÇåÚ?ÃÕäÃm]²þ-–„òêš`ñÝõD5¨)0µ5Åà ¨¦»~ï’~×ß’”Þl`-Ñ%`0üÐW`´RºÌ";ØÞ.Tzδ?gpgdir (gpgdir test key; DO NOT USE for production deployments)ˆ` G¹œÝ € Ò â7]}¹¡Ö ‰@„ÙªUh"÷–ä+€e?pSÍÝ2α”Á†£ÿk)t^“×ø°cG¹œõŒ«+ŸÅzûÍKg~¶;Ò6IFÅ”›‘} JBàQë×êÞÑçž>ˆ¥-XXÆ&2rX^¸2ñxè­fk:ûdÄ!¶ÇšbÞÆÛƒ{áÙ´A ¦ÝuvyeVV‹ß1BåYj’-ˆ»±Ãô§î· .èVqÈ´d8dq¡×Š-Õ%xÂ'ÆÇ”ƒ úöÕKBoRÄÚ`;C1[¢]¨f.1nSµ¢%ÑÅã]ÍxìL®L>%iùôUQ¥6à Jj„g?–cXéé¿z¸åŽIÏi_<q­´×2Áu‚Wb€VÖÏ9zÑ—lv7wÝ3þ6'‚Ñë§©Ið*àO"ˆ ‡„ö ~jC?C~Y»G\ydgi·º°ø‚nù:?SQç^P•-aþ"~“ÊÝ€ÏhAÃl VD|.Cw¶äÆú‰7ÝÃ^б²B=8òÞ²iÖn±Óù\dF;}R³ˆ ôõƪYÝžÓŒ:#³¦%ÒbÝÊÀ®ë&’†GòôŠ0:øv!†“ìídS³|)6ý©€sÄÝ8·Ô=)U™MÖ ”'y¶í—J¶æ1ðåU¤Tã97I¡zήð®K¨â€"AKòƒá5¥Vøo×x!‘ÞÎu•°]]ïQÀkVü2 êqi®{®°./gpgdir/test/conf/test-gpg/pubring.gpg0000644000175000017500000000225313233575632020153 0ustar guilhemguilhem™¢G¹œÝ•Û~¢ÅN/Ð!lè•UjgQ‡´“/ç£q’R'ò¡)”â·yX‰ Ì+ðM&ma%ì[$Ds*£à¯‘Ó+‰Þ9™[Åé¶Ç‡ÿŽ=ú«|â³%ðsÁò>#SŸj‚±™‰­pÓxúrùIЩײ)BF#ÓîÀƳ; £É)ž1<ÿ${ÌŸ»Ã[{‰Sý™Ð?œ"†¯1v¾ž)%9Ãâ‘h!ÛSyý¨ì>Ç#c¯¨;E?rÄ¢K»tå‰dá¼iVâu¬Õé.æ'“Ø…‹(×›TÂölˆÓáÎñœlñ‡¢€¬PG}Üeßï“ùàjÜæ½Ö”ðþ\;ѧâÆÿwßð fÛÐ:O¼f•¿ž‚¬47Á+÷NŒ=V·PEQ&_‰>Ÿ8´–òMÞdy{†&ÚF¸«QØ8nÌBVҿωÚwšÓ ¾íž ŸÃ3ñNÞo˜ì{_¦ÈKcÿ“å¼u§•´ØéÇåÚ?ÃÕäÃm]²´?gpgdir (gpgdir test key; DO NOT USE for production deployments)ˆ` G¹œÝ € Ò â7]}¹¡Ö ‰@„ÙªUh"÷–ä+€e?pSÍÝ2α”Á†£ÿk)t^“×ø°¹ G¹œõŒ«+ŸÅzûÍKg~¶;Ò6IFÅ”›‘} JBàQë×êÞÑçž>ˆ¥-XXÆ&2rX^¸2ñxè­fk:ûdÄ!¶ÇšbÞÆÛƒ{áÙ´A ¦ÝuvyeVV‹ß1BåYj’-ˆ»±Ãô§î· .èVqÈ´d8dq¡×Š-Õ%xÂ'ÆÇ”ƒ úöÕKBoRÄÚ`;C1[¢]¨f.1nSµ¢%ÑÅã]ÍxìL®L>%iùôUQ¥6à Jj„g?–cXéé¿z¸åŽIÏi_<q­´×2Áu‚Wb€VÖÏ9zÑ—lv7wÝ3þ6'‚Ñë§©Ið*àO"ˆ ‡„ö ~jC?C~Y»G\ydgi·º°ø‚nù:?SQç^P•-aþ"~“ÊÝ€ÏhAÃl VD|.Cw¶äÆú‰7ÝÃ^б²B=8òÞ²iÖn±Óù\dF;}R³ˆ ôõƪYÝžÓŒ:#³¦%ÒbÝÊÀ®ë&’†GòôŠ0:øv!†“ìídS³|)6ý©€sÄÝ8·Ô=)U™MÖ ”'y¶í—J¶æ1ðåU¤Tã97I¡zήð®K¨â€"AKòƒá5¥Vøo×x!‘ÞÎu•°]]ïQÀk \$prepare_results, 'help' => \$help ); exit &prepare_results() if $prepare_results; &setup(); &collect_md5sums(); &logr("\n[+] ==> Running gpgdir test suite <==\n\n"); ### execute the tests &test_driver('(Setup) gpgdir program compilation', \&perl_compilation); &test_driver('(Setup) Command line argument processing', \&getopt_test); &test_driver('(Test mode) gpgdir basic test mode', \&test_mode); ### encrypt/decrypt &test_driver('(Encrypt dir) gpgdir directory encryption', \&encrypt); &test_driver('(Encrypt dir) Files recursively encrypted', \&recursively_encrypted); &test_driver('(Encrypt dir) Exclude hidden files/dirs', \&skipped_hidden_files_dirs); &test_driver('(Decrypt dir) gpgdir directory decryption', \&decrypt); &test_driver('(Decrypt dir) Files recursively decrypted', \&recursively_decrypted); &test_driver('(MD5 digest) match across encrypt/decrypt cycle', \&md5sum_validation); ### ascii encrypt/decrypt &test_driver('(Ascii-armor dir) gpgdir directory encryption', \&ascii_encrypt); &test_driver('(Ascii-armor dir) Files recursively encrypted', \&ascii_recursively_encrypted); &test_driver('(Ascii-armor dir) Exclude hidden files/dirs', \&skipped_hidden_files_dirs); &test_driver('(Decrypt dir) gpgdir directory decryption', \&decrypt); &test_driver('(Decrypt dir) Files recursively decrypted', \&ascii_recursively_decrypted); &test_driver('(MD5 digest) match across encrypt/decrypt cycle', \&md5sum_validation); ### obfuscate filenames encrypt/decrypt cycle &test_driver('(Obfuscate filenames) gpgdir directory encryption', \&obf_encrypt); &test_driver('(Obfuscate filenames) Files recursively encrypted', \&obf_recursively_encrypted); &test_driver('(Obfuscate filenames) Exclude hidden files/dirs', \&obf_skipped_hidden_files_dirs); &test_driver('(Decrypt dir) gpgdir directory decryption', \&obf_decrypt); &test_driver('(Decrypt dir) Files recursively decrypted', \&obf_recursively_decrypted); ### same as ascii_recursively_decrypted() &test_driver('(MD5 digest) match across encrypt/decrypt cycle', \&md5sum_validation); ### sign/verify cycle &test_driver('(Sign/verify dir) gpgdir directory signing', \&sign); &test_driver('(Sign/verify dir) Files recursively signed', \&recursively_signed); &test_driver('(Sign/verify dir) Exclude hidden files/dirs', \&skipped_hidden_files_dirs); &test_driver('(Sign/verify dir) Broken signature detection', \&broken_sig_detection); &test_driver('(Sign/verify dir) gpgdir directory verification', \&verify); &test_driver('(Sign/verify dir) Files recursively verified', \&recursively_verified); ### bad password detection &test_driver('(Bad passphrase) detect broken passphrase', \&broken_passphrase); &logr("\n"); if ($successful_tests) { &logr("[+] ==> Passed $successful_tests/$test_num tests " . "against gpgdir. <==\n"); } if ($failed_tests) { &logr("[+] ==> Failed $failed_tests/$test_num tests " . "against gpgdir. <==\n"); } &logr("[+] This console output has been stored in: $logfile\n\n"); exit 0; #======================== end main ========================= sub test_driver() { my ($msg, $func_ref) = @_; my $test_status = 'pass'; &dots_print($msg); if (&{$func_ref}) { &pass(); } else { $test_status = 'fail'; $failed_tests++; } open C, ">> $current_test_file" or die "[*] Could not open $current_test_file: $!"; print C "\nTEST: $msg, STATUS: $test_status\n"; close C; $previous_test_file = $current_test_file; $test_num++; $current_test_file = "$output_dir/$test_num.test"; return; } sub broken_passphrase() { if (not &run_cmd("$gpgdirCmd --gnupg-dir $gpg_dir " . " --pw-file $broken_pw_file --Key-id $key_id -e $data_dir", $NO_APPEND)) { my $found_bad_pass = 0; open F, "< $current_test_file" or die $!; while () { if (/BAD_?PASS/) { $found_bad_pass = 1; } } close F; if ($found_bad_pass) { return 1; } } return &print_errors("[-] Accepted broken passphrase"); } sub encrypt() { if (&run_cmd("$gpgdirCmd $default_args -e $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory encryption"); } sub ascii_encrypt() { if (&run_cmd("$gpgdirCmd $default_args --Plain-ascii -e $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory encryption"); } sub obf_encrypt() { if (&run_cmd("$gpgdirCmd $default_args -O -e $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory encryption"); } sub sign() { if (&run_cmd("$gpgdirCmd $default_args --sign $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory signing"); } sub decrypt() { if (&run_cmd("$gpgdirCmd $default_args -d $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory decryption"); } sub obf_decrypt() { if (&run_cmd("$gpgdirCmd $default_args -O -d $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory decryption"); } sub verify() { if (&run_cmd("$gpgdirCmd $default_args --verify $data_dir", $NO_APPEND)) { return 1; } return &print_errors("[-] Directory verification"); } sub recursively_encrypted() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { unless ($file =~ m|\.gpg$|) { return &print_errors("[-] File $file not encrypted"); } } } return 1; } sub recursively_signed() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { if ($file !~ m|\.asc$|) { unless (-e "$file.asc") { return &print_errors("[-] File $file not signed"); } } } } return 1; } sub recursively_decrypted() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { if ($file =~ m|\.gpg$| or $file =~ m|\.pgp$|) { return &print_errors("[-] File $file not encrypted"); } } } return 1; } sub broken_sig_detection() { move "$data_dir/multi-line-ascii", "$data_dir/multi-line-ascii.orig" or die $!; open F, "> $data_dir/multi-line-ascii" or die $!; print F "bogus data\n"; close F; &run_cmd("$gpgdirCmd $default_args --verify $data_dir", $NO_APPEND); my $found_bad_sig = 0; open F, "< $current_test_file" or die $!; while () { if (/BADSIG/) { $found_bad_sig = 1; } } close F; if ($found_bad_sig) { unlink "$data_dir/multi-line-ascii"; move "$data_dir/multi-line-ascii.orig", "$data_dir/multi-line-ascii" or die $!; return 1; } return &print_errors("[-] Could not find bad signature"); } sub recursively_verified() { ### search for signature verification errors here my $found_bad_sig = 0; open F, "< $previous_test_file" or die $!; while () { if (/BADSIG/) { $found_bad_sig = 1; } } close F; if ($found_bad_sig) { return &print_errors("[-] Bad signature generated"); } ### now remove signature files @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { if ($file =~ m|\.asc$|) { unlink $file; } } } return 1; } sub ascii_recursively_encrypted() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { unless ($file =~ m|\.asc$|) { return &print_errors("[-] File $file not encrypted"); } } } return 1; } sub obf_recursively_encrypted() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { ### gpgdir_1.gpg unless ($file =~ m|gpgdir_\d+\.gpg$|) { return &print_errors("[-] File $file not " . "encrypted and obfuscated"); } } } return 1; } sub ascii_recursively_decrypted() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { if ($file =~ m|\.asc$|) { return &print_errors("[-] File $file not encrypted"); } } } return 1; } sub obf_recursively_decrypted() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file and not ($file =~ m|^\.| or $file =~ m|/\.|)) { if ($file =~ m|\.asc$|) { return &print_errors("[-] File $file not encrypted"); } } } return 1; } sub skipped_hidden_files_dirs() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if ($file =~ m|^\.| or $file =~ m|/\.|) { ### check for any .gpg or .asc extensions except ### for the gpgdir_map_file if ($file =~ m|\.gpg$| or $file =~ m|\.asc$| or $file =~ m|\.pgp$|) { return &print_errors("[-] Encrypted hidden file"); } } } return 1; } sub obf_skipped_hidden_files_dirs() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if ($file =~ m|^\.| or $file =~ m|/\.|) { ### check for any .gpg or .asc extensions except ### for the gpgdir_map_file if ($file !~ m|gpgdir_map_file| and ($file =~ m|\.gpg$| or $file =~ m|\.asc$| or $file =~ m|\.pgp$|)) { return &print_errors("[-] Encrypted hidden file"); } } } return 1; } sub find_files() { my $file = $File::Find::name; push @data_dir_files, $file; return; } sub collect_md5sums() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file) { $md5sums{$file} = md5_base64($file); } } return 1; } sub md5sum_validation() { @data_dir_files = (); find(\&find_files, $data_dir); for my $file (@data_dir_files) { if (-f $file) { if (not defined $md5sums{$file} or $md5sums{$file} ne md5_base64($file)) { return &print_errors("[-] MD5 sum mis-match for $file"); } } } return 1; } sub test_mode() { if (&run_cmd("$gpgdirCmd $default_args --test", $NO_APPEND)) { my $found = 0; open F, "< $current_test_file" or die "[*] Could not open $current_test_file: $!"; while () { if (/Decrypted\s+content\s+matches\s+original/i) { $found = 1; last; } } close F; return 1 if $found; } return &print_errors("[-] Encrypt/decrypt basic --test mode"); } sub perl_compilation() { unless (&run_cmd("perl -c $gpgdirCmd", $NO_APPEND)) { return &print_errors("[-] $gpgdirCmd does not compile"); } return 1; } sub getopt_test() { if (&run_cmd("$gpgdirCmd --no-such-argument", $NO_APPEND)) { return &print_errors("[-] $gpgdirCmd " . "allowed --no-such-argument on the command line"); } return 1; } sub dots_print() { my $msg = shift; &logr($msg); my $dots = ''; for (my $i=length($msg); $i < $PRINT_LEN; $i++) { $dots .= '.'; } &logr($dots); return; } sub print_errors() { my $msg = shift; &logr("fail ($test_num)\n$msg\n"); if (-e $current_test_file) { &logr(" STDOUT and STDERR available in: " . "$current_test_file file.\n"); open F, ">> $current_test_file" or die "[*] Could not open $current_test_file: $!"; print F "MSG: $msg\n"; close F; } return 0; } sub run_cmd() { my ($cmd, $append) = @_; if ($append == $APPEND) { open F, ">> $current_test_file" or die "[*] Could not open $current_test_file: $!"; print F "CMD: $cmd\n"; close F; } else { open F, "> $current_test_file" or die "[*] Could not open $current_test_file: $!"; print F "CMD: $cmd\n"; close F; } my $rv = ((system "$cmd >> $current_test_file 2>&1") >> 8); if ($rv == 0) { return 1; } return 0; } sub prepare_results() { my $rv = 0; die "[*] $output_dir does not exist" unless -d $output_dir; die "[*] $logfile does not exist, has gpgdir_test.pl been executed?" unless -e $logfile; if (-e $tarfile) { unlink $tarfile or die "[*] Could not unlink $tarfile: $!"; } ### create tarball system "tar cvfz $tarfile $logfile $output_dir"; print "[+] Test results file: $tarfile\n"; if (-e $tarfile) { $rv = 1; } return $rv; } sub setup() { $|++; ### turn off buffering die "[*] $conf_dir directory does not exist." unless -d $conf_dir; unless (-d $output_dir) { mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!"; } die "[*] Password file $pw_file does not exist" unless -f $pw_file; die "[*] Broken password file $broken_pw_file does not exist" unless -f $broken_pw_file; die "[*] $data_dir/multi-line-ascii file does not exist" unless -f "$data_dir/multi-line-ascii"; for my $file (glob("$output_dir/cmd*")) { unlink $file or die "[*] Could not unlink($file)"; } for my $file (glob("$output_dir/*.test")) { unlink $file or die "[*] Could not unlink($file)"; } for my $file (glob("$output_dir/*.warn")) { unlink $file or die "[*] Could not unlink($file)"; } for my $file (glob("$output_dir/*.die")) { unlink $file or die "[*] Could not unlink($file)"; } die "[*] $gpgdirCmd does not exist" unless -e $gpgdirCmd; die "[*] $gpgdirCmd not executable" unless -x $gpgdirCmd; if (-e $logfile) { unlink $logfile or die $!; } return; } sub pass() { &logr("pass ($test_num)\n"); $successful_tests++; return; } sub logr() { my $msg = shift; print STDOUT $msg; open F, ">> $logfile" or die $!; print F $msg; close F; return; } ./gpgdir/test/data-dir/0000755000175000017500000000000013233575632015012 5ustar guilhemguilhem./gpgdir/test/data-dir/multi-line-ascii.txt0000644000175000017500000000036513233575632020724 0ustar guilhemguilhemThis is a file that contains multiple lines of ascii text, and this file has a .txt extension (which gpgdir should handle without issues). gppdir should encrypt this file under the test suite. This file is in the top-level data-dir directory. ./gpgdir/test/data-dir/files_with_spaces/0000755000175000017500000000000013233575632020505 5ustar guilhemguilhem./gpgdir/test/data-dir/files_with_spaces/file2 space20000644000175000017500000000002713233575632022626 0ustar guilhemguilhemtwo spaces in filename ./gpgdir/test/data-dir/files_with_spaces/file1 space10000644000175000017500000000002613233575632022563 0ustar guilhemguilhemone space in filename ./gpgdir/test/data-dir/files_with_spaces/file3 -dash0000644000175000017500000000001713233575632022405 0ustar guilhemguilhemspace and dash ./gpgdir/test/data-dir/dir2/0000755000175000017500000000000013233575632015652 5ustar guilhemguilhem./gpgdir/test/data-dir/dir2/new-ascii0000644000175000017500000000032113233575632017450 0ustar guilhemguilhemThis is a file that contains multiple lines of ascii text, but there is no file extension on this one. gppdir should encrypt this file under the test suite. This file is in the top-level data-dir directory. ./gpgdir/test/data-dir/dir2/dir4/0000755000175000017500000000000013233575632016514 5ustar guilhemguilhem./gpgdir/test/data-dir/dir2/dir4/.hidden0000644000175000017500000000000013233575632017736 0ustar guilhemguilhem./gpgdir/test/data-dir/dir2/dir4/somefile0000644000175000017500000000032113233575632020236 0ustar guilhemguilhemThis is a file that contains multiple lines of ascii text, but there is no file extension on this one. gppdir should encrypt this file under the test suite. This file is in the top-level data-dir directory. ./gpgdir/test/data-dir/dir2/dir4/somefile.txt0000644000175000017500000000036513233575632021064 0ustar guilhemguilhemThis is a file that contains multiple lines of ascii text, and this file has a .txt extension (which gpgdir should handle without issues). gppdir should encrypt this file under the test suite. This file is in the top-level data-dir directory. ./gpgdir/test/data-dir/dir2/.hidden0000644000175000017500000000000013233575632017074 0ustar guilhemguilhem./gpgdir/test/data-dir/dir2/new-ascii.txt0000644000175000017500000000036513233575632020276 0ustar guilhemguilhemThis is a file that contains multiple lines of ascii text, and this file has a .txt extension (which gpgdir should handle without issues). gppdir should encrypt this file under the test suite. This file is in the top-level data-dir directory. ./gpgdir/test/data-dir/dir3/0000755000175000017500000000000013233575632015653 5ustar guilhemguilhem./gpgdir/test/data-dir/dir3/dir4/0000755000175000017500000000000013233575632016515 5ustar guilhemguilhem./gpgdir/test/data-dir/dir3/dir4/gpgdir-copy.pl0000755000175000017500000012010713233575632021302 0ustar guilhemguilhem#!/usr/bin/perl -w # ########################################################################### # # File: gpgdir # # URL: http://www.cipherdyne.org/gpgdir/ # # Purpose: To encrypt/decrypt whole directories # # Author: Michael Rash (mbr@cipherdyne.com) # # Version: 1.7 # # Copyright (C) 2002-2007 Michael Rash (mbr@cipherdyne.org) # # License (GNU General Public License): # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ########################################################################### # # $Id: gpgdir 246 2008-02-18 14:29:16Z mbr $ # use lib '/usr/lib/gpgdir'; use File::Find; use File::Copy; use Term::ReadKey; use GnuPG::Interface; use IO::File; use IO::Handle; use Getopt::Long; use Cwd; use strict; ### set the current gpgdir version and file revision numbers my $version = '1.7'; my $revision_svn = '$Revision: 246 $'; my $rev_num = '1'; ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|; ### establish some defaults my $encrypt_user = ''; my $gpg_homedir = ''; my $dir = ''; my $pw = ''; my $encrypt_dir = ''; my $decrypt_dir = ''; my $homedir = ''; my $exclude_pat = ''; my $exclude_file = ''; my $include_pat = ''; my $include_file = ''; my $total_encrypted = 0; my $total_decrypted = 0; my $norecurse = 0; my $printver = 0; my $no_delete = 0; my $no_fs_times = 0; my $test_and_exit = 0; my $trial_run = 0; my $skip_test_mode = 0; my $verbose = 0; my $quiet = 0; my $use_gpg_agent = 0; ### use gpg-agent for passwords my $gpg_agent_info = ''; my $force_mode = 0; my $help = 0; my $wipe_mode = 0; my $encrypt_mode = 0; my $use_default_key = 0; my $pw_file = ''; my $wipe_cmd = '/usr/bin/wipe'; my $wipe_cmdline = ''; my $wipe_interactive = 0; my $interactive_mode = 0; my $ascii_armor_mode = 0; my @exclude_patterns = (); my @include_patterns = (); my %files = (); my %options = (); my %obfuscate_ctrs = (); my %obfuscated_dirs = (); my $have_obfuscated_file = 0; my $cmdline_no_password = 0; my $obfuscate_mode = 0; my $obfuscate_map_filename = '.gpgdir_map_file'; my $overwrite_encrypted = 0; my $overwrite_decrypted = 0; my $symmetric_mode = 0; my $DEL_SOURCE_FILE = 1; my $NO_DEL_SOURCE_FILE = 0; ### for user answers my $ACCEPT_YES_DEFAULT = 1; my $ACCEPT_NO_DEFAULT = 2; unless ($< == $>) { die "[*] Real and effective uid must be the same. Make sure\n", " gpgdir has not been installed as a SUID binary.\n", "Exiting."; } my @args_cp = @ARGV; ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); die "[-] Use --help for usage information.\n" unless(GetOptions ( 'encrypt=s' => \$encrypt_dir, # Encrypt files in this directory. 'decrypt=s' => \$decrypt_dir, # Decrypt files in this directory. 'gnupg-dir=s' => \$gpg_homedir, # Path to /path/to/.gnupg directory. 'pw-file=s' => \$pw_file, # Read password out of this file. 'agent' => \$use_gpg_agent, # Use gpg-agent for passwords. 'Agent-info=s' => \$gpg_agent_info, # Specify GnuPG agent connection # information. 'Wipe' => \$wipe_mode, # Securely delete unencrypted files. 'wipe-path=s' => \$wipe_cmd, # Path to wipe command. 'wipe-interactive' => \$wipe_interactive, # Disable "wipe -I" 'wipe-cmdline=s' => \$wipe_cmdline, # Specify wipe command line. 'Obfuscate-filenames' => \$obfuscate_mode, # substitute real filenames # with manufactured ones. 'obfuscate-map-file=s' => \$obfuscate_map_filename, # path to mapping file. 'Force' => \$force_mode, # Continue if files can't be deleted. 'overwrite-encrypted' => \$overwrite_encrypted, # Overwrite encrypted files # even if they exist. 'overwrite-decrypted' => \$overwrite_decrypted, # Overwrite decrypted files # even if they exist. 'Exclude=s' => \$exclude_pat, # Exclude a pattern from encrypt/decrypt # cycle. 'Exclude-from=s' => \$exclude_file, # Exclude patterns in from # encrypt decrypt cycle. 'Include=s' => \$include_pat, # Specify a pattern used to restrict # encrypt/decrypt operation to. 'Include-from=s' => \$include_file, # Specify a file of include patterns to # restrict all encrypt/decrypt # operations to. 'test-mode' => \$test_and_exit, # Run encrypt -> decrypt test only and # exit. 'Trial-run' => \$trial_run, # Don't modify any files; just show what # would have happened. 'quiet' => \$quiet, # Print as little as possible to # stdout. 'Interactive' => \$interactive_mode, # Query the user before encrypting/ # decrypting/deleting any files. 'Key-id=s' => \$encrypt_user, # Specify encrypt/decrypt key 'Default-key' => \$use_default_key, # Assume that default-key is set within # ~/.gnupg/options. 'Symmetric' => \$symmetric_mode, # encrypt using symmetric cipher. # (this option is not required to # also decrypt, GnuPG handles # that automatically). 'Plain-ascii' => \$ascii_armor_mode, # Ascii armor mode (creates non-binary # encrypted files). 'skip-test' => \$skip_test_mode, # Skip encrypt -> decrypt test. 'no-recurse' => \$norecurse, # Don't encrypt/decrypt files in # subdirectories. 'no-delete' => \$no_delete, # Don't delete files once they have # been encrypted. 'no-password' => \$cmdline_no_password, # Do not query for a password (only # useful for when the gpg literally # has no password). 'user-homedir=s' => \$homedir, # Path to home directory. 'no-preserve-times' => \$no_fs_times, # Don't preserve mtimes or atimes. 'verbose' => \$verbose, # Verbose mode. 'Version' => \$printver, # Print version 'help' => \$help # Print help )); &usage_and_exit() if $help; print "[+] gpgdir v$version (file revision: $rev_num)\n", " by Michael Rash \n" and exit 0 if $printver; if ($symmetric_mode and ($use_gpg_agent or $gpg_agent_info)) { die "[*] gpg-agent incompatible with --Symmetric mode"; } if ($encrypt_dir and $overwrite_decrypted) { die "[*] The -e and --overwrite-decrypted options are incompatible."; } if ($decrypt_dir and $overwrite_encrypted) { die "[*] The -d and --overwrite-encrypted options are incompatible."; } if ($wipe_mode) { unless (-e $wipe_cmd) { die "[*] Can't find wipe command at: $wipe_cmd,\n", " use --wipe-path to specify path."; } unless (-e $wipe_cmd) { die "[*] Can't execute $wipe_cmd"; } } ### build up GnuPG options hash if ($verbose) { %options = ('homedir' => $gpg_homedir); } else { %options = ( 'batch' => 1, 'homedir' => $gpg_homedir ); } $options{'armor'} = 1 if $ascii_armor_mode; ### get the path to the user's home directory $homedir = &get_homedir() unless $homedir; unless ($symmetric_mode) { if ($gpg_homedir) { ### specified on the command line with --gnupg-dir unless ($gpg_homedir =~ /\.gnupg$/) { die "[*] Must specify the path to a user .gnupg directory ", "e.g. /home/username/.gnupg\n"; } } else { if (-d "${homedir}/.gnupg") { $gpg_homedir = "${homedir}/.gnupg"; } } unless (-d $gpg_homedir) { die "[*] GnuPG directory: ${homedir}/.gnupg does not exist. Please\n", " create it by executing: \"gpg --gen-key\". Exiting.\n"; } ### get the key identifier from ~/.gnupg $encrypt_user = &get_key() unless $encrypt_user or $use_default_key; } if ($decrypt_dir and $encrypt_dir) { die "[*] You cannot encrypt and decrypt the same directory.\n"; &usage_and_exit(); } unless ($decrypt_dir or $encrypt_dir or $test_and_exit) { print "[*] Please specify -e , -d , or --test-mode\n"; &usage_and_exit(); } ### exclude file pattern push @exclude_patterns, $exclude_pat if $exclude_pat; if ($exclude_file) { open P, '<', $exclude_file or die "[*] Could not open file: $exclude_file"; my @lines =

; close P; for my $line (@lines) { next unless $line =~ /\S/; chomp $line; push @exclude_patterns, qr{$line}; } } ### include file pattern push @include_patterns, $include_pat if $include_pat; if ($include_file) { open P, '<', $include_file or die "[*] Could not open file: $include_file"; my @lines =

; close P; for my $line (@lines) { next unless $line =~ /\S/; chomp $line; push @include_patterns, qr{$line}; } } if ($encrypt_dir) { $dir = $encrypt_dir; $encrypt_mode = 1; } elsif ($decrypt_dir) { $dir = $decrypt_dir; $encrypt_mode = 0; } if ($dir) { die "[*] Directory does not exist: $dir" unless -e $dir; die "[*] Not a directory: $dir" unless -d $dir; } ### don't need to test encrypt/decrypt ability if we are running ### in --Trial-run mode. $skip_test_mode = 1 if $trial_run; my $initial_dir = cwd or die "[*] Could not get CWD: $!"; if ($symmetric_mode) { &get_password(); } else { &get_password() unless $encrypt_mode and $skip_test_mode; } if ($dir eq '.') { $dir = $initial_dir; } elsif ($dir !~ m|^/|) { $dir = $initial_dir . '/' . $dir; } $dir =~ s|/$||; ### remove any trailing slash ### run a test to make sure gpgdir and encrypt and decrypt a file unless ($skip_test_mode) { my $rv = &test_mode(); exit $rv if $test_and_exit; } if ($encrypt_mode) { print "[+] Encrypting directory: $dir\n" unless $quiet; } else { print "[+] Decrypting directory: $dir\n" unless $quiet; } ### build a hash of file paths to work against &get_files($dir); ### perform the gpg operation (encrypt/decrypt) &gpg_operation(); &obfuscated_mapping_files() if $obfuscate_mode; unless ($obfuscate_mode) { if ($have_obfuscated_file) { print "[-] Obfuscated filenames detected, try decrypting with -O.\n" unless $quiet; } } if ($encrypt_mode) { print "[+] Total number of files encrypted: " . "$total_encrypted\n" unless $quiet; } else { print "[+] Total number of files decrypted: " . "$total_decrypted\n" unless $quiet; } exit 0; #==================== end main ===================== sub encrypt_file() { my ($in_file, $out_file, $del_flag) = @_; my $gpg = GnuPG::Interface->new(); $gpg->options->hash_init(%options); die "[*] Could not create new gpg object with ", "homedir: $gpg_homedir" unless $gpg; unless ($symmetric_mode or $use_default_key) { $gpg->options->default_key($encrypt_user); $gpg->options->push_recipients($encrypt_user); } my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) = (IO::File->new($in_file), IO::File->new("> $out_file"), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); my $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, passphrase => $pw_fh, status => $status_fh ); $handles->options('stdin')->{'direct'} = 1; $handles->options('stdout')->{'direct'} = 1; my $pid; if ($use_gpg_agent or $gpg_agent_info) { ### set environment explicitly if --Agent was specified if ($gpg_agent_info) { $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info; } $pid = $gpg->encrypt('handles' => $handles, 'command_args' => [ qw( --use-agent ) ]); } else { if ($symmetric_mode) { $pid = $gpg->encrypt_symmetrically('handles' => $handles); } else { $pid = $gpg->encrypt('handles' => $handles); } } print $pw_fh $pw; close $pw_fh; my @errors = <$error_fh>; if ($verbose) { print for @errors; } else { for (@errors) { print if /bad\s+pass/; } } close $input_fh; close $output_fh; close $error_fh; close $status_fh; waitpid $pid, 0; if (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($use_gpg_agent) { die "[*] Created zero-size file: $out_file\n", " Maybe gpg-agent does not yet have the password for that key?\n", " Try re-running with -v."; } else { die "[*] Created zero-size file: $out_file\n", " Bad password? Try re-running with -v."; } } return; } sub decrypt_file() { my ($in_file, $out_file, $del_flag) = @_; my $gpg = GnuPG::Interface->new(); $gpg->options->hash_init(%options); die "[*] Could not create new gpg object with ", "homedir: $gpg_homedir" unless $gpg; unless ($symmetric_mode or $use_default_key) { $gpg->options->default_key($encrypt_user); $gpg->options->push_recipients($encrypt_user); } my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) = (IO::File->new($in_file), IO::File->new("> $out_file"), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); my $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, passphrase => $pw_fh, status => $status_fh ); $handles->options('stdin')->{'direct'} = 1; $handles->options('stdout')->{'direct'} = 1; my $pid; if ($use_gpg_agent) { $pid = $gpg->decrypt('handles' => $handles, 'command_args' => [ qw( --use-agent ) ]); } else { $pid = $gpg->decrypt('handles' => $handles); } print $pw_fh $pw; close $pw_fh; my @errors = <$error_fh>; if ($verbose) { print for @errors; } else { for (@errors) { print if /bad\s+pass/; } } close $input_fh; close $output_fh; close $error_fh; close $status_fh; waitpid $pid, 0; if (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($use_gpg_agent) { die "[*] Created zero-size file: $out_file\n", " Maybe gpg-agent does not yet have the password for that key?\n", " Try re-running with -v."; } else { die "[*] Created zero-size file: $out_file\n", " Bad password? Try re-running with -v."; } } return; } sub delete_file() { my $file = shift; return if $no_delete; return unless -e $file; if ($wipe_mode) { my $cmd = $wipe_cmd; if ($wipe_cmdline) { $cmd .= " $wipe_cmdline "; } else { if ($wipe_interactive) { $cmd .= ' -i '; } else { $cmd .= ' -I -s '; } } $cmd .= $file; if ($verbose) { print " Executing: $cmd\n"; } ### wipe the file system $cmd; } else { unlink $file; } if (-e $file) { my $msg = "[-] Could not delete file: $file\n"; if ($force_mode) { print $msg unless $quiet; } else { die $msg unless $quiet; } } return; } sub gpg_operation() { ### sort by oldest to youngest mtime FILE: for my $file (sort {$files{$a}{'mtime'} <=> $files{$b}{'mtime'}} keys %files) { ### see if we have an exclusion pattern that implies ### we should skip this file if (@exclude_patterns and &exclude_file($file)) { print "[+] Skipping excluded file: $file\n" if $verbose and not $quiet; next FILE; } ### see if we have an inclusion pattern that implies ### we should process this file if (@include_patterns and not &include_file($file)) { print "[+] Skipping non-included file: $file\n" if $verbose and not $quiet; next FILE; } ### dir is always a full path my ($dir, $filename) = ($file =~ m|(.*)/(.*)|); unless (chdir($dir)) { print "[-] Could not chdir $dir, skipping.\n" unless $quiet; next FILE; } my $mtime = $files{$file}{'mtime'}; my $atime = $files{$file}{'atime'}; if ($encrypt_mode) { my $encrypt_filename = "$filename.gpg"; if ($obfuscate_mode) { unless (defined $obfuscate_ctrs{$dir}) { ### create a new gpgdir mapping file for obfuscated file ### names, but preserve any previously encrypted file ### name mappings &handle_old_obfuscated_map_file(); ### make obfuscated file names start at 1 for each ### directory $obfuscate_ctrs{$dir} = 1; } $encrypt_filename = 'gpgdir_' . $$ . '_' . $obfuscate_ctrs{$dir} . '.gpg'; } if ($ascii_armor_mode) { $encrypt_filename = "$filename.asc"; } if (-e $encrypt_filename and not $overwrite_encrypted) { print "[-] Encrypted file $dir/$encrypt_filename already ", "exists, skipping.\n" unless $quiet; next FILE; } if ($interactive_mode) { next FILE unless (&query_yes_no( " Encrypt: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT)); } print "[+] Encrypting: $file\n" unless $quiet; unless ($trial_run) { &encrypt_file($filename, $encrypt_filename, $NO_DEL_SOURCE_FILE); if (-e $encrypt_filename && -s $encrypt_filename != 0) { ### set the atime and mtime to be the same as the ### original file. unless ($no_fs_times) { if (defined $mtime and $mtime and defined $atime and $atime) { utime $atime, $mtime, $encrypt_filename; } } ### only delete the original file if ### the encrypted one exists if ($wipe_mode and not $quiet) { print " Securely deleting file: $file\n"; } &delete_file($filename); if ($obfuscate_mode) { ### record the original file name mapping &append_obfuscated_mapping($filename, $encrypt_filename); $obfuscate_ctrs{$dir}++; } $total_encrypted++; } else { print "[-] Could not encrypt file: $file\n" unless $quiet; next FILE; } } } else { ### allow filenames with spaces my $decrypt_filename = ''; if ($filename =~ /^(.+)\.gpg$/) { $decrypt_filename = $1; } elsif ($filename =~ /^(.+)\.asc$/) { $decrypt_filename = $1; } if ($obfuscate_mode) { &import_obfuscated_file_map($dir) unless defined $obfuscated_dirs{$dir}; if (defined $obfuscated_dirs{$dir}{$filename}) { $decrypt_filename = $obfuscated_dirs{$dir}{$filename}; } else { ### print "[-] Obfuscated file map does not exist for $filename in\n", " $obfuscate_map_filename, skipping.\n"; next FILE; } } else { if (not $force_mode and $file =~ /gpgdir_\d+_\d+.gpg/) { ### be careful not to decrypt obfuscated file unless we ### are running in -O mode. This ensures that the ### original file names will be acquired from the ### /some/dir/.gpgdir_map_file $have_obfuscated_file = 1; next FILE; } } ### length() allows files named "0" next FILE unless length($decrypt_filename) > 0; ### don't decrypt a file on top of a normal file of ### the same name if (-e $decrypt_filename and not $overwrite_decrypted) { print "[-] Decrypted file $dir/$decrypt_filename ", "already exists. Skipping.\n" unless $quiet; next FILE; } if ($interactive_mode) { next FILE unless (&query_yes_no( " Decrypt: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT)); } unless ($trial_run) { print "[+] Decrypting: $dir/$filename\n" unless $quiet; &decrypt_file($filename, $decrypt_filename, $NO_DEL_SOURCE_FILE); if (-e $decrypt_filename && -s $decrypt_filename != 0) { ### set the atime and mtime to be the same as the ### original file. unless ($no_fs_times) { if (defined $mtime and $mtime and defined $atime and $atime) { utime $atime, $mtime, $decrypt_filename; } } if ($wipe_mode and not $quiet) { print " Securely deleting file: $file\n"; } ### only delete the original encrypted ### file if the decrypted one exists &delete_file($filename); $total_decrypted++; } else { print "[-] Could not decrypt file: $file\n" unless $quiet; next FILE; } } } } print "\n" unless $quiet; chdir $initial_dir or die "[*] Could not chdir: $initial_dir\n"; return; } sub get_files() { my $dir = shift; print "[+] Building file list...\n" unless $quiet; if ($norecurse) { opendir D, $dir or die "[*] Could not open $dir: $!"; my @files = readdir D; closedir D; for my $file (@files) { next if $file eq '.'; next if $file eq '..'; &check_file_criteria("$dir/$file"); } } else { ### get all files in all subdirectories find(\&find_files, $dir); } return; } sub exclude_file() { my $file = shift; for my $pat (@exclude_patterns) { if ($file =~ m|$pat|) { print "[+] Skipping $file (matches exclude pattern: $pat)\n" if $verbose and not $quiet; return 1; } } return 0; } sub include_file() { my $file = shift; for my $pat (@include_patterns) { if ($file =~ m|$pat|) { print "[+] Including $file (matches include pattern: $pat)\n" if $verbose and not $quiet; return 1; } } return 0; } sub obfuscated_mapping_files() { my $dirs_href; if ($encrypt_mode) { $dirs_href = \%obfuscate_ctrs; } else { $dirs_href = \%obfuscated_dirs; } DIR: for my $dir (keys %$dirs_href) { unless (chdir($dir)) { print "[-] Could not chdir $dir, skipping.\n" unless $quiet; next DIR; } if ($encrypt_mode) { next DIR unless -e $obfuscate_map_filename; ### encrypt the map file now that we have encrypted ### the directory print "[+] Encrypting mapping file: ", "$dir/$obfuscate_map_filename\n" unless $quiet; unless ($trial_run) { &encrypt_file($obfuscate_map_filename, "$obfuscate_map_filename.gpg", $NO_DEL_SOURCE_FILE); unlink $obfuscate_map_filename; } } else { next DIR unless -e "$obfuscate_map_filename.gpg"; ### delete the map file since we have decrypted ### the directory print "[+] Decrypting mapping file: ", "$dir/$obfuscate_map_filename.gpg\n" unless $quiet; unless ($trial_run) { &decrypt_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); unlink "$obfuscate_map_filename.gpg"; } } } return; } sub handle_old_obfuscated_map_file() { return unless -e "$obfuscate_map_filename.gpg"; &decrypt_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); unlink "$obfuscate_map_filename.gpg"; my @existing_obfuscated_files = (); open F, '<', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; while () { if (/^\s*.*\s+(gpgdir_\d+_\d+.gpg)/) { if (-e $1) { push @existing_obfuscated_files, $_; } } } close F; if (@existing_obfuscated_files) { ### there are some obfuscated files from a previous gpgdir ### execution open G, '>', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; print G for @existing_obfuscated_files; close G; } return; } sub append_obfuscated_mapping() { my ($filename, $encrypt_filename) = @_; open G, '>>', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; print G "$filename $encrypt_filename\n"; close G; return; } sub import_obfuscated_file_map() { my $dir = shift; $obfuscated_dirs{$dir} = {}; return unless -e "$obfuscate_map_filename.gpg"; &decrypt_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); open G, '<', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; while () { if (/^\s*(.*)\s+(gpgdir_\d+_\d+.gpg)/) { $obfuscated_dirs{$dir}{$2} = $1; } } close G; return; } sub get_homedir() { my $uid = $<; my $homedir = ''; if (-e '/etc/passwd') { open P, '<', '/etc/passwd' or die "[*] Could not open /etc/passwd. Exiting.\n"; my @lines =

; close P; for my $line (@lines) { ### mbr:x:222:222:Michael Rash:/home/mbr:/bin/bash chomp $line; if ($line =~ /^(?:.*:){2}$uid:(?:.*:){2}(\S+):/) { $homedir = $1; last; } } } else { $homedir = $ENV{'HOME'} if defined $ENV{'HOME'}; } die "[*] Could not determine home directory. Use the -u option." unless $homedir; return $homedir; } sub get_key() { if (-e "${homedir}/.gpgdirrc") { open F, '<', "$homedir/.gpgdirrc" or die "[*] Could not open ", "${homedir}/.gpgdirrc. Exiting.\n"; my @lines = ; close F; my $key = ''; for my $line (@lines) { chomp $line; if ($line =~ /^\s*default_key/) { ### prefer to use the default GnuPG key $use_default_key = 1; return ''; } elsif ($line =~ /^\s*use_key\s+(.*)$/) { ### GnuPG accepts strings to match the key, so we don't ### have to strictly require a key ID... just a string ### that matches the key return $1; } } die "[*] Please edit ${homedir}/.gpgdirrc to include your gpg key identifier\n", " (e.g. \"D4696445\"; see the output of \"gpg --list-keys\"), or use the\n", " default GnuPG key defined in ~/.gnupg/options"; } print "[+] Creating gpgdir rc file: $homedir/.gpgdirrc\n"; open F, '>', "$homedir/.gpgdirrc" or die "[*] Could not open " . "${homedir}/.gpgdirrc. Exiting.\n"; print F <<_CONFIGRC_; # Config file for gpgdir. # # Set the key to use to encrypt files with "use_key ", e.g. # "use_key D4696445". See "gpg --list-keys" for a list of keys on your # GnuPG key ring. Alternatively, if you want gpgdir to always use the # default key that is defined by the "default-key" variable in # ~/.gnupg/options, then uncomment the "default_key" line below. # Uncomment to use the GnuPG default key defined in ~/.gnupg/options: #default_key # If you want to use a specific GnuPG key, Uncomment the next line and # replace "KEYID" with your real key id: #use_key KEYID _CONFIGRC_ close F; print "[*] Please edit $homedir/.gpgdirrc to include your gpg key identifier,\n", " or use the default GnuPG key defined in ~/.gnupg/options. Exiting.\n"; exit 0; } sub find_files() { my $file = $File::Find::name; &check_file_criteria($file); return; } sub check_file_criteria() { my $file = shift; ### skip all links, zero size files, all hidden ### files (includes .gnupg files), etc. return if -d $file; if (-e $file and not -l $file and -s $file != 0 and $file !~ m|/\.|) { if ($encrypt_mode) { if ($file =~ m|\.gpg| or $file =~ m|\.asc|) { print "[-] Skipping encrypted file: $file\n" unless $quiet; return; } } else { unless ($file =~ m|\.gpg| or $file =~ m|\.asc|) { print "[-] Skipping unencrypted file: $file\n" unless $quiet; return; } } my ($atime, $mtime) = (stat($file))[8,9]; $files{$file}{'atime'} = $atime; $files{$file}{'mtime'} = $mtime; } else { print "[-] Skipping file: $file\n" if $verbose and not $quiet; } return; } sub get_password() { ### this is only useful if the gpg key literally has no password ### (usually this is not the case, but gpgdir will support it if ### so). return if $cmdline_no_password; ### if we are using gpg-agent for passwords, then return return if $use_gpg_agent; if ($pw_file) { open PW, '<', $pw_file or die "[*] Could not open $pw_file: $!"; $pw = ; close PW; chomp $pw; } else { print "[+] Executing: gpgdir @args_cp\n" unless $quiet; if ($symmetric_mode) { print " [Symmetric mode]\n" unless $quiet; } else { if ($use_default_key) { print " Using default GnuPG key.\n" unless $quiet; } else { print " Using GnuPG key: $encrypt_user\n" unless $quiet; } } if ($test_and_exit) { print " *** test_mode() ***\n" unless $quiet; } if ($encrypt_mode) { print ' Enter password (for initial ' . "encrypt/decrypt test)\n" unless $quiet; } my $msg = 'Password: '; ### get the password without echoing the chars back to the screen ReadMode 'noecho'; while (! $pw) { print $msg; $pw = ReadLine 0; chomp $pw; } ReadMode 'normal'; if ($quiet) { print "\n"; } else { print "\n\n"; } } return; } sub test_mode() { chdir $dir or die "[*] Could not chdir($dir): $!"; my $test_file = "gpgdir_test.$$"; print "[+] test_mode(): Encrypt/Decrypt test of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); if (-e $test_file) { &delete_file($test_file) or die "[*] test_mode(): Could not remove $test_file: $!"; } if (-e "$test_file.gpg") { &delete_file("$test_file.gpg") or die "[*] test_mode(): Could not remove $test_file.gpg: $!"; } open G, '>', $test_file or die "[*] test_mode(): Could not create $test_file: $!"; print G "gpgdir test\n"; close G; if (-e $test_file) { print "[+] test_mode(): Created $test_file\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Could not create $test_file\n"; } &encrypt_file($test_file, "${test_file}.gpg", $DEL_SOURCE_FILE); if (-e "$test_file.gpg" and (-s $test_file != 0)) { print "[+] test_mode(): Successful encrypt of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); &delete_file($test_file) if -e $test_file; } else { die "[*] test_mode(): not encrypt $test_file (try adding -v).\n"; } &decrypt_file("${test_file}.gpg", $test_file, $DEL_SOURCE_FILE); if (-e $test_file and (-s $test_file != 0)) { print "[+] test_mode(): Successful decrypt of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Could not decrypt $test_file.gpg ", "(try adding -v).\n"; } open F, '<', $test_file or die "[*] test_mode(): Could not open $test_file: $!"; my $line = ; close F; if (defined $line and $line =~ /\S/) { chomp $line; if ($line eq 'gpgdir test') { print "[+] test_mode(): Decrypted content matches original.\n", "[+] test_mode(): Success!\n\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Decrypted content does not match ", "original (try adding -v)."; } } else { die "[*] test_mode(): Fail (try adding -v).\n"; } &delete_file($test_file) if -e $test_file; &delete_file("$test_file.gpg") if -e "$test_file.gpg"; chdir $initial_dir or die "[*] Could not chdir($initial_dir)"; return 1; } sub query_yes_no() { my ($msg, $style) = @_; my $ans = ''; while ($ans ne 'y' and $ans ne 'n') { print $msg; $ans = lc(); if ($style == $ACCEPT_YES_DEFAULT) { return 1 if $ans eq "\n"; } elsif ($style == $ACCEPT_NO_DEFAULT) { return 0 if $ans eq "\n"; } chomp $ans; } return 1 if $ans eq 'y'; return 0; } sub usage_and_exit() { print <<_HELP_; gpgdir; Recursive direction encryption and decryption with GnuPG [+] Version: $version (file revision: $rev_num) By Michael Rash (mbr\@cipherdyne.org) URL: http://www.cipherdyne.org/gpgdir/ Usage: gpgdir -e|-d [options] Options: -e, --encrypt - Encrypt and all of its subdirectories. -d, --decrypt - Decrypt and all of its subdirectories. -a, --agent - Acquire password information from a running instance of gpg-agent. -A, --Agent-info - Specify the value for the GPG_AGENT_INFO environment variable as returned by 'gpg-agent --daemon'. -g, --gnupg-dir

- Specify a path to a .gnupg directory for gpg keys (the default is ~/.gnupg if this option is not used). -p, --pw-file - Read password in from . -s, --skip-test - Skip encrypt -> decrypt test. -t, --test-mode - Run encrypt -> decrypt test and exit. -T, --Trial-run - Show what filesystem actions would take place without actually doing them. -P, --Plain-ascii - Ascii armor mode (creates non-binary encrypted files). --Interactive - Query the user before encrypting, decrypting, or deleting any files. --Exclude - Skip all filenames that match . --Exclude-from - Skip all filenames that match any pattern contained within . --Include - Include only those filenames that match . --Include-from - Include only those filenames that match a pattern contained within . -K, --Key-id - Specify GnuPG key ID, or key-matching string. This overrides the use_key value in ~/.gpgdirrc -D, --Default-key - Use the key that GnuPG defines as the default (i.e. the key that is specified by the default-key option in ~/.gnupg/options). -O, --Obfuscate-filenames - Substitute all real filenames in a directory with manufactured ones (the original filenames are preserved in a mapping file and restored when the directory is decrypted). --obfuscate-map_file - Specify path to obfuscated mapping file (in -O mode). -F, --Force - Continue to run even if files cannot be deleted (because of permissions problems for example). --overwrite-encrypted - Overwrite encrypted files even if a previous .gpg file already exists. --overwrite-decrypted - Overwrite decrypted files even if the previous unencrypted file already exists. -q, --quiet - Print as little to the screen as possible -W, --Wipe - Use the 'wipe' command to securely delete unencrypted copies of files after they have been encrypted. --wipe-path - Specify path to the wipe command. --wipe-interactive - Force interactive mode with the wipe command. --wipe-cmdline - Manually specify command line arguments to the wipe command. --no-recurse - Don't recursively encrypt/decrypt subdirectories. --no-delete - Don't delete original unencrypted files. --no-preserve-times - Don't preserve original mtime and atime values on encrypted/decrypted files. --no-password - Assume the gpg key has no password at all (this is not common). -u, --user-homedir - Path to home directory. -v, --verbose - Run in verbose mode. -V, --Version - print version. -h, --help - print help. _HELP_ exit 0; } ./gpgdir/test/data-dir/dir3/dir4/gpgdir-copy0000755000175000017500000012010713233575632020670 0ustar guilhemguilhem#!/usr/bin/perl -w # ########################################################################### # # File: gpgdir # # URL: http://www.cipherdyne.org/gpgdir/ # # Purpose: To encrypt/decrypt whole directories # # Author: Michael Rash (mbr@cipherdyne.com) # # Version: 1.7 # # Copyright (C) 2002-2007 Michael Rash (mbr@cipherdyne.org) # # License (GNU General Public License): # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ########################################################################### # # $Id: gpgdir 246 2008-02-18 14:29:16Z mbr $ # use lib '/usr/lib/gpgdir'; use File::Find; use File::Copy; use Term::ReadKey; use GnuPG::Interface; use IO::File; use IO::Handle; use Getopt::Long; use Cwd; use strict; ### set the current gpgdir version and file revision numbers my $version = '1.7'; my $revision_svn = '$Revision: 246 $'; my $rev_num = '1'; ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|; ### establish some defaults my $encrypt_user = ''; my $gpg_homedir = ''; my $dir = ''; my $pw = ''; my $encrypt_dir = ''; my $decrypt_dir = ''; my $homedir = ''; my $exclude_pat = ''; my $exclude_file = ''; my $include_pat = ''; my $include_file = ''; my $total_encrypted = 0; my $total_decrypted = 0; my $norecurse = 0; my $printver = 0; my $no_delete = 0; my $no_fs_times = 0; my $test_and_exit = 0; my $trial_run = 0; my $skip_test_mode = 0; my $verbose = 0; my $quiet = 0; my $use_gpg_agent = 0; ### use gpg-agent for passwords my $gpg_agent_info = ''; my $force_mode = 0; my $help = 0; my $wipe_mode = 0; my $encrypt_mode = 0; my $use_default_key = 0; my $pw_file = ''; my $wipe_cmd = '/usr/bin/wipe'; my $wipe_cmdline = ''; my $wipe_interactive = 0; my $interactive_mode = 0; my $ascii_armor_mode = 0; my @exclude_patterns = (); my @include_patterns = (); my %files = (); my %options = (); my %obfuscate_ctrs = (); my %obfuscated_dirs = (); my $have_obfuscated_file = 0; my $cmdline_no_password = 0; my $obfuscate_mode = 0; my $obfuscate_map_filename = '.gpgdir_map_file'; my $overwrite_encrypted = 0; my $overwrite_decrypted = 0; my $symmetric_mode = 0; my $DEL_SOURCE_FILE = 1; my $NO_DEL_SOURCE_FILE = 0; ### for user answers my $ACCEPT_YES_DEFAULT = 1; my $ACCEPT_NO_DEFAULT = 2; unless ($< == $>) { die "[*] Real and effective uid must be the same. Make sure\n", " gpgdir has not been installed as a SUID binary.\n", "Exiting."; } my @args_cp = @ARGV; ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); die "[-] Use --help for usage information.\n" unless(GetOptions ( 'encrypt=s' => \$encrypt_dir, # Encrypt files in this directory. 'decrypt=s' => \$decrypt_dir, # Decrypt files in this directory. 'gnupg-dir=s' => \$gpg_homedir, # Path to /path/to/.gnupg directory. 'pw-file=s' => \$pw_file, # Read password out of this file. 'agent' => \$use_gpg_agent, # Use gpg-agent for passwords. 'Agent-info=s' => \$gpg_agent_info, # Specify GnuPG agent connection # information. 'Wipe' => \$wipe_mode, # Securely delete unencrypted files. 'wipe-path=s' => \$wipe_cmd, # Path to wipe command. 'wipe-interactive' => \$wipe_interactive, # Disable "wipe -I" 'wipe-cmdline=s' => \$wipe_cmdline, # Specify wipe command line. 'Obfuscate-filenames' => \$obfuscate_mode, # substitute real filenames # with manufactured ones. 'obfuscate-map-file=s' => \$obfuscate_map_filename, # path to mapping file. 'Force' => \$force_mode, # Continue if files can't be deleted. 'overwrite-encrypted' => \$overwrite_encrypted, # Overwrite encrypted files # even if they exist. 'overwrite-decrypted' => \$overwrite_decrypted, # Overwrite decrypted files # even if they exist. 'Exclude=s' => \$exclude_pat, # Exclude a pattern from encrypt/decrypt # cycle. 'Exclude-from=s' => \$exclude_file, # Exclude patterns in from # encrypt decrypt cycle. 'Include=s' => \$include_pat, # Specify a pattern used to restrict # encrypt/decrypt operation to. 'Include-from=s' => \$include_file, # Specify a file of include patterns to # restrict all encrypt/decrypt # operations to. 'test-mode' => \$test_and_exit, # Run encrypt -> decrypt test only and # exit. 'Trial-run' => \$trial_run, # Don't modify any files; just show what # would have happened. 'quiet' => \$quiet, # Print as little as possible to # stdout. 'Interactive' => \$interactive_mode, # Query the user before encrypting/ # decrypting/deleting any files. 'Key-id=s' => \$encrypt_user, # Specify encrypt/decrypt key 'Default-key' => \$use_default_key, # Assume that default-key is set within # ~/.gnupg/options. 'Symmetric' => \$symmetric_mode, # encrypt using symmetric cipher. # (this option is not required to # also decrypt, GnuPG handles # that automatically). 'Plain-ascii' => \$ascii_armor_mode, # Ascii armor mode (creates non-binary # encrypted files). 'skip-test' => \$skip_test_mode, # Skip encrypt -> decrypt test. 'no-recurse' => \$norecurse, # Don't encrypt/decrypt files in # subdirectories. 'no-delete' => \$no_delete, # Don't delete files once they have # been encrypted. 'no-password' => \$cmdline_no_password, # Do not query for a password (only # useful for when the gpg literally # has no password). 'user-homedir=s' => \$homedir, # Path to home directory. 'no-preserve-times' => \$no_fs_times, # Don't preserve mtimes or atimes. 'verbose' => \$verbose, # Verbose mode. 'Version' => \$printver, # Print version 'help' => \$help # Print help )); &usage_and_exit() if $help; print "[+] gpgdir v$version (file revision: $rev_num)\n", " by Michael Rash \n" and exit 0 if $printver; if ($symmetric_mode and ($use_gpg_agent or $gpg_agent_info)) { die "[*] gpg-agent incompatible with --Symmetric mode"; } if ($encrypt_dir and $overwrite_decrypted) { die "[*] The -e and --overwrite-decrypted options are incompatible."; } if ($decrypt_dir and $overwrite_encrypted) { die "[*] The -d and --overwrite-encrypted options are incompatible."; } if ($wipe_mode) { unless (-e $wipe_cmd) { die "[*] Can't find wipe command at: $wipe_cmd,\n", " use --wipe-path to specify path."; } unless (-e $wipe_cmd) { die "[*] Can't execute $wipe_cmd"; } } ### build up GnuPG options hash if ($verbose) { %options = ('homedir' => $gpg_homedir); } else { %options = ( 'batch' => 1, 'homedir' => $gpg_homedir ); } $options{'armor'} = 1 if $ascii_armor_mode; ### get the path to the user's home directory $homedir = &get_homedir() unless $homedir; unless ($symmetric_mode) { if ($gpg_homedir) { ### specified on the command line with --gnupg-dir unless ($gpg_homedir =~ /\.gnupg$/) { die "[*] Must specify the path to a user .gnupg directory ", "e.g. /home/username/.gnupg\n"; } } else { if (-d "${homedir}/.gnupg") { $gpg_homedir = "${homedir}/.gnupg"; } } unless (-d $gpg_homedir) { die "[*] GnuPG directory: ${homedir}/.gnupg does not exist. Please\n", " create it by executing: \"gpg --gen-key\". Exiting.\n"; } ### get the key identifier from ~/.gnupg $encrypt_user = &get_key() unless $encrypt_user or $use_default_key; } if ($decrypt_dir and $encrypt_dir) { die "[*] You cannot encrypt and decrypt the same directory.\n"; &usage_and_exit(); } unless ($decrypt_dir or $encrypt_dir or $test_and_exit) { print "[*] Please specify -e , -d , or --test-mode\n"; &usage_and_exit(); } ### exclude file pattern push @exclude_patterns, $exclude_pat if $exclude_pat; if ($exclude_file) { open P, '<', $exclude_file or die "[*] Could not open file: $exclude_file"; my @lines =

; close P; for my $line (@lines) { next unless $line =~ /\S/; chomp $line; push @exclude_patterns, qr{$line}; } } ### include file pattern push @include_patterns, $include_pat if $include_pat; if ($include_file) { open P, '<', $include_file or die "[*] Could not open file: $include_file"; my @lines =

; close P; for my $line (@lines) { next unless $line =~ /\S/; chomp $line; push @include_patterns, qr{$line}; } } if ($encrypt_dir) { $dir = $encrypt_dir; $encrypt_mode = 1; } elsif ($decrypt_dir) { $dir = $decrypt_dir; $encrypt_mode = 0; } if ($dir) { die "[*] Directory does not exist: $dir" unless -e $dir; die "[*] Not a directory: $dir" unless -d $dir; } ### don't need to test encrypt/decrypt ability if we are running ### in --Trial-run mode. $skip_test_mode = 1 if $trial_run; my $initial_dir = cwd or die "[*] Could not get CWD: $!"; if ($symmetric_mode) { &get_password(); } else { &get_password() unless $encrypt_mode and $skip_test_mode; } if ($dir eq '.') { $dir = $initial_dir; } elsif ($dir !~ m|^/|) { $dir = $initial_dir . '/' . $dir; } $dir =~ s|/$||; ### remove any trailing slash ### run a test to make sure gpgdir and encrypt and decrypt a file unless ($skip_test_mode) { my $rv = &test_mode(); exit $rv if $test_and_exit; } if ($encrypt_mode) { print "[+] Encrypting directory: $dir\n" unless $quiet; } else { print "[+] Decrypting directory: $dir\n" unless $quiet; } ### build a hash of file paths to work against &get_files($dir); ### perform the gpg operation (encrypt/decrypt) &gpg_operation(); &obfuscated_mapping_files() if $obfuscate_mode; unless ($obfuscate_mode) { if ($have_obfuscated_file) { print "[-] Obfuscated filenames detected, try decrypting with -O.\n" unless $quiet; } } if ($encrypt_mode) { print "[+] Total number of files encrypted: " . "$total_encrypted\n" unless $quiet; } else { print "[+] Total number of files decrypted: " . "$total_decrypted\n" unless $quiet; } exit 0; #==================== end main ===================== sub encrypt_file() { my ($in_file, $out_file, $del_flag) = @_; my $gpg = GnuPG::Interface->new(); $gpg->options->hash_init(%options); die "[*] Could not create new gpg object with ", "homedir: $gpg_homedir" unless $gpg; unless ($symmetric_mode or $use_default_key) { $gpg->options->default_key($encrypt_user); $gpg->options->push_recipients($encrypt_user); } my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) = (IO::File->new($in_file), IO::File->new("> $out_file"), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); my $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, passphrase => $pw_fh, status => $status_fh ); $handles->options('stdin')->{'direct'} = 1; $handles->options('stdout')->{'direct'} = 1; my $pid; if ($use_gpg_agent or $gpg_agent_info) { ### set environment explicitly if --Agent was specified if ($gpg_agent_info) { $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info; } $pid = $gpg->encrypt('handles' => $handles, 'command_args' => [ qw( --use-agent ) ]); } else { if ($symmetric_mode) { $pid = $gpg->encrypt_symmetrically('handles' => $handles); } else { $pid = $gpg->encrypt('handles' => $handles); } } print $pw_fh $pw; close $pw_fh; my @errors = <$error_fh>; if ($verbose) { print for @errors; } else { for (@errors) { print if /bad\s+pass/; } } close $input_fh; close $output_fh; close $error_fh; close $status_fh; waitpid $pid, 0; if (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($use_gpg_agent) { die "[*] Created zero-size file: $out_file\n", " Maybe gpg-agent does not yet have the password for that key?\n", " Try re-running with -v."; } else { die "[*] Created zero-size file: $out_file\n", " Bad password? Try re-running with -v."; } } return; } sub decrypt_file() { my ($in_file, $out_file, $del_flag) = @_; my $gpg = GnuPG::Interface->new(); $gpg->options->hash_init(%options); die "[*] Could not create new gpg object with ", "homedir: $gpg_homedir" unless $gpg; unless ($symmetric_mode or $use_default_key) { $gpg->options->default_key($encrypt_user); $gpg->options->push_recipients($encrypt_user); } my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) = (IO::File->new($in_file), IO::File->new("> $out_file"), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); my $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, passphrase => $pw_fh, status => $status_fh ); $handles->options('stdin')->{'direct'} = 1; $handles->options('stdout')->{'direct'} = 1; my $pid; if ($use_gpg_agent) { $pid = $gpg->decrypt('handles' => $handles, 'command_args' => [ qw( --use-agent ) ]); } else { $pid = $gpg->decrypt('handles' => $handles); } print $pw_fh $pw; close $pw_fh; my @errors = <$error_fh>; if ($verbose) { print for @errors; } else { for (@errors) { print if /bad\s+pass/; } } close $input_fh; close $output_fh; close $error_fh; close $status_fh; waitpid $pid, 0; if (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($use_gpg_agent) { die "[*] Created zero-size file: $out_file\n", " Maybe gpg-agent does not yet have the password for that key?\n", " Try re-running with -v."; } else { die "[*] Created zero-size file: $out_file\n", " Bad password? Try re-running with -v."; } } return; } sub delete_file() { my $file = shift; return if $no_delete; return unless -e $file; if ($wipe_mode) { my $cmd = $wipe_cmd; if ($wipe_cmdline) { $cmd .= " $wipe_cmdline "; } else { if ($wipe_interactive) { $cmd .= ' -i '; } else { $cmd .= ' -I -s '; } } $cmd .= $file; if ($verbose) { print " Executing: $cmd\n"; } ### wipe the file system $cmd; } else { unlink $file; } if (-e $file) { my $msg = "[-] Could not delete file: $file\n"; if ($force_mode) { print $msg unless $quiet; } else { die $msg unless $quiet; } } return; } sub gpg_operation() { ### sort by oldest to youngest mtime FILE: for my $file (sort {$files{$a}{'mtime'} <=> $files{$b}{'mtime'}} keys %files) { ### see if we have an exclusion pattern that implies ### we should skip this file if (@exclude_patterns and &exclude_file($file)) { print "[+] Skipping excluded file: $file\n" if $verbose and not $quiet; next FILE; } ### see if we have an inclusion pattern that implies ### we should process this file if (@include_patterns and not &include_file($file)) { print "[+] Skipping non-included file: $file\n" if $verbose and not $quiet; next FILE; } ### dir is always a full path my ($dir, $filename) = ($file =~ m|(.*)/(.*)|); unless (chdir($dir)) { print "[-] Could not chdir $dir, skipping.\n" unless $quiet; next FILE; } my $mtime = $files{$file}{'mtime'}; my $atime = $files{$file}{'atime'}; if ($encrypt_mode) { my $encrypt_filename = "$filename.gpg"; if ($obfuscate_mode) { unless (defined $obfuscate_ctrs{$dir}) { ### create a new gpgdir mapping file for obfuscated file ### names, but preserve any previously encrypted file ### name mappings &handle_old_obfuscated_map_file(); ### make obfuscated file names start at 1 for each ### directory $obfuscate_ctrs{$dir} = 1; } $encrypt_filename = 'gpgdir_' . $$ . '_' . $obfuscate_ctrs{$dir} . '.gpg'; } if ($ascii_armor_mode) { $encrypt_filename = "$filename.asc"; } if (-e $encrypt_filename and not $overwrite_encrypted) { print "[-] Encrypted file $dir/$encrypt_filename already ", "exists, skipping.\n" unless $quiet; next FILE; } if ($interactive_mode) { next FILE unless (&query_yes_no( " Encrypt: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT)); } print "[+] Encrypting: $file\n" unless $quiet; unless ($trial_run) { &encrypt_file($filename, $encrypt_filename, $NO_DEL_SOURCE_FILE); if (-e $encrypt_filename && -s $encrypt_filename != 0) { ### set the atime and mtime to be the same as the ### original file. unless ($no_fs_times) { if (defined $mtime and $mtime and defined $atime and $atime) { utime $atime, $mtime, $encrypt_filename; } } ### only delete the original file if ### the encrypted one exists if ($wipe_mode and not $quiet) { print " Securely deleting file: $file\n"; } &delete_file($filename); if ($obfuscate_mode) { ### record the original file name mapping &append_obfuscated_mapping($filename, $encrypt_filename); $obfuscate_ctrs{$dir}++; } $total_encrypted++; } else { print "[-] Could not encrypt file: $file\n" unless $quiet; next FILE; } } } else { ### allow filenames with spaces my $decrypt_filename = ''; if ($filename =~ /^(.+)\.gpg$/) { $decrypt_filename = $1; } elsif ($filename =~ /^(.+)\.asc$/) { $decrypt_filename = $1; } if ($obfuscate_mode) { &import_obfuscated_file_map($dir) unless defined $obfuscated_dirs{$dir}; if (defined $obfuscated_dirs{$dir}{$filename}) { $decrypt_filename = $obfuscated_dirs{$dir}{$filename}; } else { ### print "[-] Obfuscated file map does not exist for $filename in\n", " $obfuscate_map_filename, skipping.\n"; next FILE; } } else { if (not $force_mode and $file =~ /gpgdir_\d+_\d+.gpg/) { ### be careful not to decrypt obfuscated file unless we ### are running in -O mode. This ensures that the ### original file names will be acquired from the ### /some/dir/.gpgdir_map_file $have_obfuscated_file = 1; next FILE; } } ### length() allows files named "0" next FILE unless length($decrypt_filename) > 0; ### don't decrypt a file on top of a normal file of ### the same name if (-e $decrypt_filename and not $overwrite_decrypted) { print "[-] Decrypted file $dir/$decrypt_filename ", "already exists. Skipping.\n" unless $quiet; next FILE; } if ($interactive_mode) { next FILE unless (&query_yes_no( " Decrypt: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT)); } unless ($trial_run) { print "[+] Decrypting: $dir/$filename\n" unless $quiet; &decrypt_file($filename, $decrypt_filename, $NO_DEL_SOURCE_FILE); if (-e $decrypt_filename && -s $decrypt_filename != 0) { ### set the atime and mtime to be the same as the ### original file. unless ($no_fs_times) { if (defined $mtime and $mtime and defined $atime and $atime) { utime $atime, $mtime, $decrypt_filename; } } if ($wipe_mode and not $quiet) { print " Securely deleting file: $file\n"; } ### only delete the original encrypted ### file if the decrypted one exists &delete_file($filename); $total_decrypted++; } else { print "[-] Could not decrypt file: $file\n" unless $quiet; next FILE; } } } } print "\n" unless $quiet; chdir $initial_dir or die "[*] Could not chdir: $initial_dir\n"; return; } sub get_files() { my $dir = shift; print "[+] Building file list...\n" unless $quiet; if ($norecurse) { opendir D, $dir or die "[*] Could not open $dir: $!"; my @files = readdir D; closedir D; for my $file (@files) { next if $file eq '.'; next if $file eq '..'; &check_file_criteria("$dir/$file"); } } else { ### get all files in all subdirectories find(\&find_files, $dir); } return; } sub exclude_file() { my $file = shift; for my $pat (@exclude_patterns) { if ($file =~ m|$pat|) { print "[+] Skipping $file (matches exclude pattern: $pat)\n" if $verbose and not $quiet; return 1; } } return 0; } sub include_file() { my $file = shift; for my $pat (@include_patterns) { if ($file =~ m|$pat|) { print "[+] Including $file (matches include pattern: $pat)\n" if $verbose and not $quiet; return 1; } } return 0; } sub obfuscated_mapping_files() { my $dirs_href; if ($encrypt_mode) { $dirs_href = \%obfuscate_ctrs; } else { $dirs_href = \%obfuscated_dirs; } DIR: for my $dir (keys %$dirs_href) { unless (chdir($dir)) { print "[-] Could not chdir $dir, skipping.\n" unless $quiet; next DIR; } if ($encrypt_mode) { next DIR unless -e $obfuscate_map_filename; ### encrypt the map file now that we have encrypted ### the directory print "[+] Encrypting mapping file: ", "$dir/$obfuscate_map_filename\n" unless $quiet; unless ($trial_run) { &encrypt_file($obfuscate_map_filename, "$obfuscate_map_filename.gpg", $NO_DEL_SOURCE_FILE); unlink $obfuscate_map_filename; } } else { next DIR unless -e "$obfuscate_map_filename.gpg"; ### delete the map file since we have decrypted ### the directory print "[+] Decrypting mapping file: ", "$dir/$obfuscate_map_filename.gpg\n" unless $quiet; unless ($trial_run) { &decrypt_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); unlink "$obfuscate_map_filename.gpg"; } } } return; } sub handle_old_obfuscated_map_file() { return unless -e "$obfuscate_map_filename.gpg"; &decrypt_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); unlink "$obfuscate_map_filename.gpg"; my @existing_obfuscated_files = (); open F, '<', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; while () { if (/^\s*.*\s+(gpgdir_\d+_\d+.gpg)/) { if (-e $1) { push @existing_obfuscated_files, $_; } } } close F; if (@existing_obfuscated_files) { ### there are some obfuscated files from a previous gpgdir ### execution open G, '>', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; print G for @existing_obfuscated_files; close G; } return; } sub append_obfuscated_mapping() { my ($filename, $encrypt_filename) = @_; open G, '>>', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; print G "$filename $encrypt_filename\n"; close G; return; } sub import_obfuscated_file_map() { my $dir = shift; $obfuscated_dirs{$dir} = {}; return unless -e "$obfuscate_map_filename.gpg"; &decrypt_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); open G, '<', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; while () { if (/^\s*(.*)\s+(gpgdir_\d+_\d+.gpg)/) { $obfuscated_dirs{$dir}{$2} = $1; } } close G; return; } sub get_homedir() { my $uid = $<; my $homedir = ''; if (-e '/etc/passwd') { open P, '<', '/etc/passwd' or die "[*] Could not open /etc/passwd. Exiting.\n"; my @lines =

; close P; for my $line (@lines) { ### mbr:x:222:222:Michael Rash:/home/mbr:/bin/bash chomp $line; if ($line =~ /^(?:.*:){2}$uid:(?:.*:){2}(\S+):/) { $homedir = $1; last; } } } else { $homedir = $ENV{'HOME'} if defined $ENV{'HOME'}; } die "[*] Could not determine home directory. Use the -u option." unless $homedir; return $homedir; } sub get_key() { if (-e "${homedir}/.gpgdirrc") { open F, '<', "$homedir/.gpgdirrc" or die "[*] Could not open ", "${homedir}/.gpgdirrc. Exiting.\n"; my @lines = ; close F; my $key = ''; for my $line (@lines) { chomp $line; if ($line =~ /^\s*default_key/) { ### prefer to use the default GnuPG key $use_default_key = 1; return ''; } elsif ($line =~ /^\s*use_key\s+(.*)$/) { ### GnuPG accepts strings to match the key, so we don't ### have to strictly require a key ID... just a string ### that matches the key return $1; } } die "[*] Please edit ${homedir}/.gpgdirrc to include your gpg key identifier\n", " (e.g. \"D4696445\"; see the output of \"gpg --list-keys\"), or use the\n", " default GnuPG key defined in ~/.gnupg/options"; } print "[+] Creating gpgdir rc file: $homedir/.gpgdirrc\n"; open F, '>', "$homedir/.gpgdirrc" or die "[*] Could not open " . "${homedir}/.gpgdirrc. Exiting.\n"; print F <<_CONFIGRC_; # Config file for gpgdir. # # Set the key to use to encrypt files with "use_key ", e.g. # "use_key D4696445". See "gpg --list-keys" for a list of keys on your # GnuPG key ring. Alternatively, if you want gpgdir to always use the # default key that is defined by the "default-key" variable in # ~/.gnupg/options, then uncomment the "default_key" line below. # Uncomment to use the GnuPG default key defined in ~/.gnupg/options: #default_key # If you want to use a specific GnuPG key, Uncomment the next line and # replace "KEYID" with your real key id: #use_key KEYID _CONFIGRC_ close F; print "[*] Please edit $homedir/.gpgdirrc to include your gpg key identifier,\n", " or use the default GnuPG key defined in ~/.gnupg/options. Exiting.\n"; exit 0; } sub find_files() { my $file = $File::Find::name; &check_file_criteria($file); return; } sub check_file_criteria() { my $file = shift; ### skip all links, zero size files, all hidden ### files (includes .gnupg files), etc. return if -d $file; if (-e $file and not -l $file and -s $file != 0 and $file !~ m|/\.|) { if ($encrypt_mode) { if ($file =~ m|\.gpg| or $file =~ m|\.asc|) { print "[-] Skipping encrypted file: $file\n" unless $quiet; return; } } else { unless ($file =~ m|\.gpg| or $file =~ m|\.asc|) { print "[-] Skipping unencrypted file: $file\n" unless $quiet; return; } } my ($atime, $mtime) = (stat($file))[8,9]; $files{$file}{'atime'} = $atime; $files{$file}{'mtime'} = $mtime; } else { print "[-] Skipping file: $file\n" if $verbose and not $quiet; } return; } sub get_password() { ### this is only useful if the gpg key literally has no password ### (usually this is not the case, but gpgdir will support it if ### so). return if $cmdline_no_password; ### if we are using gpg-agent for passwords, then return return if $use_gpg_agent; if ($pw_file) { open PW, '<', $pw_file or die "[*] Could not open $pw_file: $!"; $pw = ; close PW; chomp $pw; } else { print "[+] Executing: gpgdir @args_cp\n" unless $quiet; if ($symmetric_mode) { print " [Symmetric mode]\n" unless $quiet; } else { if ($use_default_key) { print " Using default GnuPG key.\n" unless $quiet; } else { print " Using GnuPG key: $encrypt_user\n" unless $quiet; } } if ($test_and_exit) { print " *** test_mode() ***\n" unless $quiet; } if ($encrypt_mode) { print ' Enter password (for initial ' . "encrypt/decrypt test)\n" unless $quiet; } my $msg = 'Password: '; ### get the password without echoing the chars back to the screen ReadMode 'noecho'; while (! $pw) { print $msg; $pw = ReadLine 0; chomp $pw; } ReadMode 'normal'; if ($quiet) { print "\n"; } else { print "\n\n"; } } return; } sub test_mode() { chdir $dir or die "[*] Could not chdir($dir): $!"; my $test_file = "gpgdir_test.$$"; print "[+] test_mode(): Encrypt/Decrypt test of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); if (-e $test_file) { &delete_file($test_file) or die "[*] test_mode(): Could not remove $test_file: $!"; } if (-e "$test_file.gpg") { &delete_file("$test_file.gpg") or die "[*] test_mode(): Could not remove $test_file.gpg: $!"; } open G, '>', $test_file or die "[*] test_mode(): Could not create $test_file: $!"; print G "gpgdir test\n"; close G; if (-e $test_file) { print "[+] test_mode(): Created $test_file\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Could not create $test_file\n"; } &encrypt_file($test_file, "${test_file}.gpg", $DEL_SOURCE_FILE); if (-e "$test_file.gpg" and (-s $test_file != 0)) { print "[+] test_mode(): Successful encrypt of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); &delete_file($test_file) if -e $test_file; } else { die "[*] test_mode(): not encrypt $test_file (try adding -v).\n"; } &decrypt_file("${test_file}.gpg", $test_file, $DEL_SOURCE_FILE); if (-e $test_file and (-s $test_file != 0)) { print "[+] test_mode(): Successful decrypt of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Could not decrypt $test_file.gpg ", "(try adding -v).\n"; } open F, '<', $test_file or die "[*] test_mode(): Could not open $test_file: $!"; my $line = ; close F; if (defined $line and $line =~ /\S/) { chomp $line; if ($line eq 'gpgdir test') { print "[+] test_mode(): Decrypted content matches original.\n", "[+] test_mode(): Success!\n\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Decrypted content does not match ", "original (try adding -v)."; } } else { die "[*] test_mode(): Fail (try adding -v).\n"; } &delete_file($test_file) if -e $test_file; &delete_file("$test_file.gpg") if -e "$test_file.gpg"; chdir $initial_dir or die "[*] Could not chdir($initial_dir)"; return 1; } sub query_yes_no() { my ($msg, $style) = @_; my $ans = ''; while ($ans ne 'y' and $ans ne 'n') { print $msg; $ans = lc(); if ($style == $ACCEPT_YES_DEFAULT) { return 1 if $ans eq "\n"; } elsif ($style == $ACCEPT_NO_DEFAULT) { return 0 if $ans eq "\n"; } chomp $ans; } return 1 if $ans eq 'y'; return 0; } sub usage_and_exit() { print <<_HELP_; gpgdir; Recursive direction encryption and decryption with GnuPG [+] Version: $version (file revision: $rev_num) By Michael Rash (mbr\@cipherdyne.org) URL: http://www.cipherdyne.org/gpgdir/ Usage: gpgdir -e|-d [options] Options: -e, --encrypt - Encrypt and all of its subdirectories. -d, --decrypt - Decrypt and all of its subdirectories. -a, --agent - Acquire password information from a running instance of gpg-agent. -A, --Agent-info - Specify the value for the GPG_AGENT_INFO environment variable as returned by 'gpg-agent --daemon'. -g, --gnupg-dir

- Specify a path to a .gnupg directory for gpg keys (the default is ~/.gnupg if this option is not used). -p, --pw-file - Read password in from . -s, --skip-test - Skip encrypt -> decrypt test. -t, --test-mode - Run encrypt -> decrypt test and exit. -T, --Trial-run - Show what filesystem actions would take place without actually doing them. -P, --Plain-ascii - Ascii armor mode (creates non-binary encrypted files). --Interactive - Query the user before encrypting, decrypting, or deleting any files. --Exclude - Skip all filenames that match . --Exclude-from - Skip all filenames that match any pattern contained within . --Include - Include only those filenames that match . --Include-from - Include only those filenames that match a pattern contained within . -K, --Key-id - Specify GnuPG key ID, or key-matching string. This overrides the use_key value in ~/.gpgdirrc -D, --Default-key - Use the key that GnuPG defines as the default (i.e. the key that is specified by the default-key option in ~/.gnupg/options). -O, --Obfuscate-filenames - Substitute all real filenames in a directory with manufactured ones (the original filenames are preserved in a mapping file and restored when the directory is decrypted). --obfuscate-map_file - Specify path to obfuscated mapping file (in -O mode). -F, --Force - Continue to run even if files cannot be deleted (because of permissions problems for example). --overwrite-encrypted - Overwrite encrypted files even if a previous .gpg file already exists. --overwrite-decrypted - Overwrite decrypted files even if the previous unencrypted file already exists. -q, --quiet - Print as little to the screen as possible -W, --Wipe - Use the 'wipe' command to securely delete unencrypted copies of files after they have been encrypted. --wipe-path - Specify path to the wipe command. --wipe-interactive - Force interactive mode with the wipe command. --wipe-cmdline - Manually specify command line arguments to the wipe command. --no-recurse - Don't recursively encrypt/decrypt subdirectories. --no-delete - Don't delete original unencrypted files. --no-preserve-times - Don't preserve original mtime and atime values on encrypted/decrypted files. --no-password - Assume the gpg key has no password at all (this is not common). -u, --user-homedir - Path to home directory. -v, --verbose - Run in verbose mode. -V, --Version - print version. -h, --help - print help. _HELP_ exit 0; } ./gpgdir/test/data-dir/dir3/dir4/.hidden0000644000175000017500000000000013233575632017737 0ustar guilhemguilhem./gpgdir/test/data-dir/dir3/dir4/random-binary-data0000644000175000017500000000012713233575632022111 0ustar guilhemguilhem·âU…пŸÐ1'uçUfInsq,ËASIçóJò fn:f1Íãnê5¼ÎõC¨NotÖqWbydñiI”ŸzŠPéhaC)!”ßÕ³å¬Ý5ò…Š./gpgdir/test/data-dir/dir3/dir4/random-binary-data.bin0000644000175000017500000000012713233575632022660 0ustar guilhemguilhem·âU…пŸÐ1'uçUfInsq,ËASIçóJò fn:f1Íãnê5¼ÎõC¨NotÖqWbydñiI”ŸzŠPéhaC)!”ßÕ³å¬Ý5ò…Š./gpgdir/test/data-dir/dir3/.hidden0000644000175000017500000000000013233575632017075 0ustar guilhemguilhem./gpgdir/test/data-dir/dir3/file10000644000175000017500000000001313233575632016570 0ustar guilhemguilhemsome lines ./gpgdir/test/data-dir/dir3/file20000644000175000017500000000001313233575632016571 0ustar guilhemguilhemmore lines ./gpgdir/test/data-dir/.hidden0000644000175000017500000000000013233575632016234 0ustar guilhemguilhem./gpgdir/test/data-dir/random-binary-data0000644000175000017500000000012713233575632020406 0ustar guilhemguilhem·âU…пŸÐ1'uçUfInsq,ËASIçóJò fn:f1Íãnê5¼ÎõC¨NotÖqWbydñiI”ŸzŠPéhaC)!”ßÕ³å¬Ý5ò…Š./gpgdir/test/data-dir/.hidden-dir/0000755000175000017500000000000013233575632017077 5ustar guilhemguilhem./gpgdir/test/data-dir/.hidden-dir/git-svn_doesnt_like_empty_dirs0000644000175000017500000000000013233575632025216 0ustar guilhemguilhem./gpgdir/test/data-dir/random-binary-data.bin0000644000175000017500000000012713233575632021155 0ustar guilhemguilhem·âU…пŸÐ1'uçUfInsq,ËASIçóJò fn:f1Íãnê5¼ÎõC¨NotÖqWbydñiI”ŸzŠPéhaC)!”ßÕ³å¬Ý5ò…Š./gpgdir/test/data-dir/multi-line-ascii0000644000175000017500000000032113233575632020076 0ustar guilhemguilhemThis is a file that contains multiple lines of ascii text, but there is no file extension on this one. gppdir should encrypt this file under the test suite. This file is in the top-level data-dir directory. ./gpgdir/packaging/0000755000175000017500000000000013233575632014272 5ustar guilhemguilhem./gpgdir/packaging/gpgdir-nobuildreqs.spec0000644000175000017500000001552013233575632020752 0ustar guilhemguilhem%define name gpgdir %define version 1.9.5 %define release 1 %define gpgdirlibdir %_libdir/%name ### get the first @INC directory that includes the string "linux". ### This may be 'i386-linux', or 'i686-linux-thread-multi', etc. %define gpgdirmoddir `perl -e '$path='i386-linux'; for (@INC) { if($_ =~ m|.*/(.*linux.*)|) {$path = $1; last; }} print $path'` Summary: Gpgdir recursively encrypts/decrypts directories with GnuPG. Name: %name Version: %version Release: %release License: GPL Group: Applications/Cryptography Url: http://www.cipherdyne.org/gpgdir/ Source: %name-%version.tar.gz BuildRoot: %_tmppath/%{name}-buildroot #Prereq: rpm-helper %description gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to encrypt and decrypt directories using a gpg key specified in ~/.gpgdirrc. gpgdir recursively descends through a directory in order to make sure it encrypts or decrypts every file in a directory and all of its subdirectories. By default the mtime and atime values of all files will be preserved upon encryption and decryption (this can be disabled with the --no-preserve-times option). Note that in --encrypt mode, gpgdir will delete the original files that it successfully encrypts (unless the --no-delete option is given). However, upon startup gpgdir first asks for a the decryption pass- word to be sure that a dummy file can successfully be encrypted and decrypted. The initial test can be disabled with the --skip-test option so that a directory can eas- ily be encrypted without having to also specify a password (this is consistent with gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and direc- tories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file to be encrypted. %prep [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %setup -q for i in $(grep -r "use lib" . | cut -d: -f1); do awk '/use lib/ { sub("/usr/lib/gpgdir", "%_libdir/%name") } { print }' $i > $i.tmp mv $i.tmp $i done cd deps cd Class-MethodMaker && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir cd .. cd GnuPG-Interface && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir cd .. cd TermReadKey && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir cd ../.. %build ### build perl modules used by gpgdir cd deps make OPTS="$RPM_OPT_FLAGS" -C Class-MethodMaker make OPTS="$RPM_OPT_FLAGS" -C GnuPG-Interface make OPTS="$RPM_OPT_FLAGS" -C TermReadKey cd .. %install ### gpgdir module dirs cd deps mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/array mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/Engine mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/hash mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/scalar mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Term mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/auto/GnuPG/Interface mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/GnuPG mkdir -p $RPM_BUILD_ROOT%_bindir mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 mkdir -p $RPM_BUILD_ROOT%_sbindir cd .. install -m 755 gpgdir $RPM_BUILD_ROOT%_bindir/ install -m 644 gpgdir.1 $RPM_BUILD_ROOT%{_mandir}/man1/ ### install perl modules used by gpgdir cd deps install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/array/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/array/ install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/scalar/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/scalar/ install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/hash/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/hash/ install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/Engine/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/Engine/ install -m 444 Class-MethodMaker/blib/arch/auto/Class/MethodMaker/MethodMaker.bs $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/MethodMaker.bs install -m 444 Class-MethodMaker/blib/arch/auto/Class/MethodMaker/MethodMaker.so $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/MethodMaker.so install -m 444 Class-MethodMaker/blib/lib/Class/MethodMaker.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker.pm install -m 444 Class-MethodMaker/blib/lib/Class/MethodMaker/*.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker install -m 444 GnuPG-Interface/blib/lib/auto/GnuPG/Interface/*.* $RPM_BUILD_ROOT%gpgdirlibdir/auto/GnuPG/Interface/ install -m 444 GnuPG-Interface/blib/lib/GnuPG/*.pm $RPM_BUILD_ROOT%gpgdirlibdir/GnuPG/ install -m 444 TermReadKey/blib/lib/Term/ReadKey.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Term/ReadKey.pm install -m 444 TermReadKey/blib/lib/auto/Term/ReadKey/autosplit.ix $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/autosplit.ix install -m 444 TermReadKey/blib/arch/auto/Term/ReadKey/ReadKey.bs $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/ReadKey.bs install -m 444 TermReadKey/blib/arch/auto/Term/ReadKey/ReadKey.so $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/ReadKey.so cd .. %clean [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %pre %post %preun %files %defattr(-,root,root) %_bindir/* %{_mandir}/man1/* %_libdir/%name %changelog * Sat Sep 05 2009 Michael Rash - gpgdir-1.9.5 release * Thu Feb 12 2009 Michael Rash - gpgdir-1.9.4 release * Wed Nov 11 2008 Michael Rash - gpgdir-1.9.3 release * Sun Aug 31 2008 Michael Rash - Updated to use the deps/ directory for all perl module sources. - gpgdir-1.9.2 release * Sat Jun 07 2008 Michael Rash - gpgdir-1.9.1 release * Sat May 31 2008 Michael Rash - gpgdir-1.9 release * Mon Feb 18 2008 Michael Rash - gpgdir-1.8 release * Mon Feb 18 2008 Michael Rash - gpgdir-1.7 release * Sun Feb 17 2008 Michael Rash - gpgdir-1.6 release * Fri Aug 31 2007 Michael Rash - gpgdir-1.5 release * Sat Jul 20 2007 Michael Rash - gpgdir-1.4 release * Sat Jun 09 2007 Michael Rash - gpgdir-1.3 release * Mon May 28 2007 Michael Rash - gpgdir-1.2 release * Mon May 21 2007 Michael Rash - gpgdir-1.1 release * Sun Sep 17 2006 Michael Rash - gpgdir-1.0.3 release (1.0.2 was skipped accidentally). * Sat Sep 16 2006 Michael Rash - Added x86_64 RPM. - Removed iptables as a prerequisite. - gpgdir-1.0.1 release * Wed Sep 13 2006 Michael Rash - gpgdir-1.0 release * Thu Sep 09 2006 Michael Rash - Initial RPM release of gpgdir-0.9.9 ./gpgdir/packaging/gpgdir.SlackBuild0000755000175000017500000000254213233575632017513 0ustar guilhemguilhem#!/bin/bash ################################################################################ # gpgdir.SlackBuild -- pyllyukko@maimed.org -- 26.1.2007 (originally for psad) # ################################################################################ declare -r RPM_BUILDER="http://www.cipherdyne.org/scripts/cd_rpmbuilder.tar.gz" declare -r RPM_ROOT_DIR="/usr/src/rpm" declare -r ARCH="i386" declare -ri BUILD=1 GPGDIR_VERSION=`wget --no-verbose --output-document=- http://www.cipherdyne.org/gpgdir/gpgdir-latest` || { echo "error!" 1>&2 exit 1 } ################################################################################ wget --no-verbose --output-document=- "${RPM_BUILDER}" | tar xz --to-stdout | perl -- - -p gpgdir -r "${RPM_ROOT_DIR}" [ $[ ${PIPESTATUS[0]} | ${PIPESTATUS[1]} | ${PIPESTATUS[2]} ] -ne 0 ] && { echo "error!" 1>&2 exit 1 } [ ! -f "${RPM_ROOT_DIR}/RPMS/${ARCH}/gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.rpm" ] && { echo "error: file \`gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.rpm' doesn't exist!" 1>&2 exit 1 } pushd "${RPM_ROOT_DIR}/RPMS/${ARCH}" || exit 1 rpm2tgz "gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.rpm" || exit 1 mv -v "gpgdir-${GPGDIR_VERSION}-${BUILD}.${ARCH}.tgz" "gpgdir-${GPGDIR_VERSION}-${ARCH}-${BUILD}.tgz" || exit 1 ls -l "${RPM_ROOT_DIR}/RPMS/${ARCH}/gpgdir-${GPGDIR_VERSION}-${ARCH}-${BUILD}.tgz" exit ${?} ./gpgdir/packaging/gpgdir.spec0000644000175000017500000001556713233575632016440 0ustar guilhemguilhem%define name gpgdir %define version 1.9.5 %define release 1 %define gpgdirlibdir %_libdir/%name ### get the first @INC directory that includes the string "linux". ### This may be 'i386-linux', or 'i686-linux-thread-multi', etc. %define gpgdirmoddir `perl -e '$path='i386-linux'; for (@INC) { if($_ =~ m|.*/(.*linux.*)|) {$path = $1; last; }} print $path'` Summary: Gpgdir recursively encrypts/decrypts directories with GnuPG. Name: %name Version: %version Release: %release License: GPL Group: Applications/Cryptography Url: http://www.cipherdyne.org/gpgdir/ Source: %name-%version.tar.gz BuildRoot: %_tmppath/%{name}-buildroot BuildRequires: perl-ExtUtils-MakeMaker #Prereq: rpm-helper %description gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to encrypt and decrypt directories using a gpg key specified in ~/.gpgdirrc. gpgdir recursively descends through a directory in order to make sure it encrypts or decrypts every file in a directory and all of its subdirectories. By default the mtime and atime values of all files will be preserved upon encryption and decryption (this can be disabled with the --no-preserve-times option). Note that in --encrypt mode, gpgdir will delete the original files that it successfully encrypts (unless the --no-delete option is given). However, upon startup gpgdir first asks for a the decryption pass- word to be sure that a dummy file can successfully be encrypted and decrypted. The initial test can be disabled with the --skip-test option so that a directory can eas- ily be encrypted without having to also specify a password (this is consistent with gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and direc- tories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file to be encrypted. %prep [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %setup -q for i in $(grep -r "use lib" . | cut -d: -f1); do awk '/use lib/ { sub("/usr/lib/gpgdir", "%_libdir/%name") } { print }' $i > $i.tmp mv $i.tmp $i done cd deps cd Class-MethodMaker && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir cd .. cd GnuPG-Interface && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir cd .. cd TermReadKey && perl Makefile.PL PREFIX=%gpgdirlibdir LIB=%gpgdirlibdir cd ../.. %build ### build perl modules used by gpgdir cd deps make OPTS="$RPM_OPT_FLAGS" -C Class-MethodMaker make OPTS="$RPM_OPT_FLAGS" -C GnuPG-Interface make OPTS="$RPM_OPT_FLAGS" -C TermReadKey cd .. %install ### gpgdir module dirs cd deps mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/array mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/Engine mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/hash mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/scalar mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Term mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/auto/GnuPG/Interface mkdir -p $RPM_BUILD_ROOT%gpgdirlibdir/GnuPG mkdir -p $RPM_BUILD_ROOT%_bindir mkdir -p $RPM_BUILD_ROOT%{_mandir}/man1 mkdir -p $RPM_BUILD_ROOT%_sbindir cd .. install -m 755 gpgdir $RPM_BUILD_ROOT%_bindir/ install -m 644 gpgdir.1 $RPM_BUILD_ROOT%{_mandir}/man1/ ### install perl modules used by gpgdir cd deps install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/array/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/array/ install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/scalar/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/scalar/ install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/hash/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/hash/ install -m 444 Class-MethodMaker/blib/lib/auto/Class/MethodMaker/Engine/*.* $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/Engine/ install -m 444 Class-MethodMaker/blib/arch/auto/Class/MethodMaker/MethodMaker.bs $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/MethodMaker.bs install -m 444 Class-MethodMaker/blib/arch/auto/Class/MethodMaker/MethodMaker.so $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Class/MethodMaker/MethodMaker.so install -m 444 Class-MethodMaker/blib/lib/Class/MethodMaker.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker.pm install -m 444 Class-MethodMaker/blib/lib/Class/MethodMaker/*.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Class/MethodMaker install -m 444 GnuPG-Interface/blib/lib/auto/GnuPG/Interface/*.* $RPM_BUILD_ROOT%gpgdirlibdir/auto/GnuPG/Interface/ install -m 444 GnuPG-Interface/blib/lib/GnuPG/*.pm $RPM_BUILD_ROOT%gpgdirlibdir/GnuPG/ install -m 444 TermReadKey/blib/lib/Term/ReadKey.pm $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/Term/ReadKey.pm install -m 444 TermReadKey/blib/lib/auto/Term/ReadKey/autosplit.ix $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/autosplit.ix install -m 444 TermReadKey/blib/arch/auto/Term/ReadKey/ReadKey.bs $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/ReadKey.bs install -m 444 TermReadKey/blib/arch/auto/Term/ReadKey/ReadKey.so $RPM_BUILD_ROOT%gpgdirlibdir/%gpgdirmoddir/auto/Term/ReadKey/ReadKey.so cd .. %clean [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %pre %post %preun %files %defattr(-,root,root) %_bindir/* %{_mandir}/man1/* %_libdir/%name %changelog * Sat Sep 05 2009 Michael Rash - gpgdir-1.9.5 release * Thu Feb 12 2009 Michael Rash - gpgdir-1.9.4 release * Wed Nov 11 2008 Michael Rash - gpgdir-1.9.3 release * Sun Aug 31 2008 Michael Rash - Updated to use the deps/ directory for all perl module sources. - gpgdir-1.9.2 release * Sat Jun 07 2008 Michael Rash - gpgdir-1.9.1 release * Sat May 31 2008 Michael Rash - gpgdir-1.9 release * Mon Feb 18 2008 Michael Rash - gpgdir-1.8 release * Mon Feb 18 2008 Michael Rash - gpgdir-1.7 release * Sun Feb 17 2008 Michael Rash - gpgdir-1.6 release * Fri Aug 31 2007 Michael Rash - gpgdir-1.5 release * Sat Jul 20 2007 Michael Rash - gpgdir-1.4 release * Sat Jun 09 2007 Michael Rash - gpgdir-1.3 release * Mon May 28 2007 Michael Rash - gpgdir-1.2 release * Mon May 21 2007 Michael Rash - gpgdir-1.1 release * Sun Sep 17 2006 Michael Rash - gpgdir-1.0.3 release (1.0.2 was skipped accidentally). * Sat Sep 16 2006 Michael Rash - Added x86_64 RPM. - Removed iptables as a prerequisite. - gpgdir-1.0.1 release * Wed Sep 13 2006 Michael Rash - gpgdir-1.0 release * Thu Sep 09 2006 Michael Rash - Initial RPM release of gpgdir-0.9.9 ./gpgdir/packaging/gpgdir-nodeps.spec0000644000175000017500000000650013233575632017711 0ustar guilhemguilhem%define name gpgdir %define version 1.9.5 %define release 1 %define gpgdirlibdir %_libdir/%name Summary: Gpgdir recursively encrypts/decrypts directories with GnuPG. Name: %name Version: %version Release: %release License: GPL Group: Applications/Cryptography Url: http://www.cipherdyne.org/gpgdir/ Source: %name-%version.tar.gz BuildRoot: %_tmppath/%{name}-buildroot #Prereq: rpm-helper %description gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to encrypt and decrypt directories using a gpg key specified in ~/.gpgdirrc. gpgdir recursively descends through a directory in order to make sure it encrypts or decrypts every file in a directory and all of its subdirectories. By default the mtime and atime values of all files will be preserved upon encryption and decryption (this can be disabled with the --no-preserve-times option). Note that in --encrypt mode, gpgdir will delete the original files that it successfully encrypts (unless the --no-delete option is given). However, upon startup gpgdir first asks for a the decryption pass- word to be sure that a dummy file can successfully be encrypted and decrypted. The initial test can be disabled with the --skip-test option so that a directory can eas- ily be encrypted without having to also specify a password (this is consistent with gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and direc- tories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file to be encrypted. %prep [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %setup -q %build %install install -m 755 gpgdir $RPM_BUILD_ROOT%_bindir/ install -m 644 gpgdir.1 $RPM_BUILD_ROOT%{_mandir}/man1/ %clean [ "$RPM_BUILD_ROOT" != "/" ] && rm -rf $RPM_BUILD_ROOT %pre %post %preun %files %defattr(-,root,root) %_bindir/* %{_mandir}/man1/* %_libdir/%name %changelog * Sat Sep 05 2009 Michael Rash - gpgdir-1.9.5 release * Thu Feb 12 2009 Michael Rash - gpgdir-1.9.4 release * Wed Nov 11 2008 Michael Rash - gpgdir-1.9.3 release * Sun Aug 31 2008 Michael Rash - This spec file omits installing any perl module dependencies. - gpgdir-1.9.2 release * Sat Jun 07 2008 Michael Rash - gpgdir-1.9.1 release * Sat May 31 2008 Michael Rash - gpgdir-1.9 release * Mon Feb 18 2008 Michael Rash - gpgdir-1.8 release * Mon Feb 18 2008 Michael Rash - gpgdir-1.7 release * Sun Feb 17 2008 Michael Rash - gpgdir-1.6 release * Fri Aug 31 2007 Michael Rash - gpgdir-1.5 release * Sat Jul 20 2007 Michael Rash - gpgdir-1.4 release * Sat Jun 09 2007 Michael Rash - gpgdir-1.3 release * Mon May 28 2007 Michael Rash - gpgdir-1.2 release * Mon May 21 2007 Michael Rash - gpgdir-1.1 release * Sun Sep 17 2006 Michael Rash - gpgdir-1.0.3 release (1.0.2 was skipped accidentally). * Sat Sep 16 2006 Michael Rash - Added x86_64 RPM. - Removed iptables as a prerequisite. - gpgdir-1.0.1 release * Wed Sep 13 2006 Michael Rash - gpgdir-1.0 release * Thu Sep 09 2006 Michael Rash - Initial RPM release of gpgdir-0.9.9 ./gpgdir/packaging/cd_rpmbuilder0000755000175000017500000001607513233575632017044 0ustar guilhemguilhem#!/usr/bin/perl -w # ############################################################################# # # File: cd_rpmbuilder "CipherDyne Rpm Builder" # # Purpose: Provides a consistent way to build RPMs of CipherDyne open source # projects (psad, fwsnort, fwsknop, and gpgdir). # # Author: Michael Rash # # Copyright (C) 2006-2008 Michael Rash (mbr@cipherdyne.org) # # License (GNU Public License - GPLv2): # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ############################################################################# # # $Id: cd_rpmbuilder 1864 2008-08-22 03:16:19Z mbr $ # use File::Find; use File::Copy; use Getopt::Long 'GetOptions'; use strict; #============================ config ============================= my $rpm_root_dir = '/usr/src/redhat'; my $build_url_base = 'http://www.cipherdyne.org'; ### commands my $rpmbuildCmd = '/usr/bin/rpmbuild'; my $wgetCmd = '/usr/bin/wget'; #========================== end config =========================== my $version = '0.9'; my $project = ''; my $build_version = ''; my $print_version = 0; my $nodeps = 0; my $verbose = 0; my $help = 0; my @rpm_paths = (); my $RM = 1; my $PRINT = 2; my %projects = ( 'psad' => '', 'fwknop' => '', 'fwsnort' => '', 'gpgdir' => '' ); Getopt::Long::Configure('no_ignore_case'); &usage() unless (GetOptions( 'project=s' => \$project, 'build-version=s' => \$build_version, 'rpm-build-dir=s' => \$rpm_root_dir, 'no-deps' => \$nodeps, 'verbose' => \$verbose, 'Version' => \$print_version, 'help' => \$help )); &usage() if $help; if ($print_version) { print "[+] cd_rpmbuilder by Michael Rash \n"; exit 0; } if ($project) { unless (defined $projects{$project}) { print "[*] Unrecognized project: $project; must be one of:\n"; print $_, "\n" for keys %projects; exit 1; } } else { die "[*] Must specify a project with -p \n"; } die "[*] $wgetCmd is not a valid path to wget, update the config section." unless -x $wgetCmd; die "[*] $rpmbuildCmd is not a valid path to rpmbuild, update the config " . "section." unless -x $rpmbuildCmd; chdir "$rpm_root_dir/SPECS" or die "[*] Could not chdir $rpm_root_dir/SPECS"; unless ($build_version) { ### we need to get the latest version from cipherdyne.org &get_latest_version(); } my $spec_file = "$project-$build_version.spec"; my $tar_file = "$project-$build_version.tar.gz"; if ($nodeps) { $spec_file = "$project-nodeps-$build_version.spec"; $tar_file = "$project-nodeps-$build_version.tar.gz"; } ### remove old RPMS &find_rpms($RM); ### get the remote spec file &download_file($spec_file); &md5_check($spec_file); ### get the remote source tarball and md5 sum file &download_file($tar_file); &md5_check($tar_file); if ($nodeps) { move $tar_file, "../SOURCES/$project-$build_version.tar.gz" or die $!; } else { move $tar_file, '../SOURCES' or die $!; } ### build the rpm &build_rpm(); ### print the paths to the new RPMS &find_rpms($PRINT); exit 0; #======================= end main ======================== sub find_rpms() { my $action = shift; @rpm_paths = (); find(\&get_rpms, "$rpm_root_dir/SRPMS"); find(\&get_rpms, "$rpm_root_dir/RPMS"); if ($action == $PRINT) { if (@rpm_paths) { print "[+] The following RPMS were successfully built:\n\n"; } else { print "[-] No RPMS were successfully built; try running ", "with --verbose\n"; } } for my $rpm_file (@rpm_paths) { if ($action == $RM) { unlink $rpm_file or die "[*] Could not unlink $rpm_file: $!"; } elsif ($action == $PRINT) { if ($rpm_file =~ /\.src\.rpm/) { print " $rpm_file (source RPM)\n"; } else { print " $rpm_file\n"; } } } print "\n" if $action == $PRINT; return; } sub get_rpms() { my $file = $File::Find::name; if ($file =~ /$project-$build_version-.*\.rpm$/) { push @rpm_paths, $file; } return; } sub download_file() { my $file = shift; unlink $file if -e $file; print "[+] Downloading file:\n", " $build_url_base/$project/download/$file\n"; my $cmd = "$wgetCmd $build_url_base/$project/download/$file"; unless ($verbose) { $cmd .= ' > /dev/null 2>&1'; } system $cmd; die "[*] Could not download $file, try running with -v" unless -e $file; return; } sub md5_check() { my $file = shift; &download_file("$file.md5"); ### check MD5 sum open MD5, '-|', qw/md5sum -c/, "$file.md5" or die $!; my $sum_line = ; close MD5; unless ($sum_line =~ m/$file:\s+OK/) { die "[*] MD5 sum check failed for $file, ", "exiting."; } print "[+] Valid md5 sum check for $file\n"; unlink "$file.md5"; return; } sub build_rpm() { print "[+] Building RPM, this may take a little while (try -v if you want\n", " to see all of the steps)...\n\n"; my $cmd = "$rpmbuildCmd -ba $spec_file"; unless ($verbose) { $cmd .= ' > /dev/null 2>&1'; } system $cmd; return; } sub get_latest_version() { unlink "$project-latest" if -e "$project-latest"; print "[+] Getting latest version file:\n", " $build_url_base/$project/$project-latest\n"; my $cmd = "$wgetCmd $build_url_base/$project/$project-latest"; unless ($verbose) { $cmd .= ' > /dev/null 2>&1'; } system $cmd; open F, '<', "$project-latest" or die "[*] Could not open $project-latest: $!"; my $line = ; close F; chomp $line; $build_version = $line; die "[*] Could not get build version" unless $build_version; unlink "$project-latest" if -e "$project-latest"; return; } sub usage() { print <<_HELP_; cd_rpmbuilder; the CipherDyne RPM builder [+] Version: $version [+] By Michael Rash (mbr\@cipherdyne.org, http://www.cipherdyne.org) Usage: cd_rpmbuilder -p [options] Options: -p, --project - This can be one of "psad", "fwknop", "gpgdir", or "fwsnort". -b, --build-version - Build a specific project version. -r, --rpm-build-dir - Change the RPM build directory from the default of $rpm_root_dir. -n, --no-deps - Build the specified project without any dependencies (such as perl modules). -v, --verbose - Run in verbose mode. -V, --Version - Print version and exit. -h, --help - Display usage information. _HELP_ exit 0; } ./gpgdir/ChangeLog0000644000175000017500000003306713233575632014131 0ustar guilhemguilhemgpgdir-1.9.5 (09/05/2009): - Added support for the decryption of PGP encrypted files (to round out the support of GnuPG). gpgdir-1.9.4 (02/12/2009): - Fixed a bug in missing the proper handling of files with spaces when using the --Wipe secure deletion mode (reported by Lars Wilke). - The --Force option now supports the ability to encrypt/decrypt hidden files (suggested by Lars Wilke). gpgdir-1.9.3 (11/05/2008): - Bugfix for using -f instead of -I for non-interactive file erasure (Franck Joncourt). - Simplified test suite code by creating a set of default arguments for the gpgdir command line as each test is executed. gpgdir-1.9.2 (08/31/2008): - Added new modes '--sign ' and '--verify ' to allow all files in the specified directory to be signed or verified instead of encrypted or decrypted. All GnuPG signatures are created as ".asc", and the original file is not removed in --sign mode. In --verify mode, if any file does not match the expected .asc signature, then a warning like the following will be generated: [+] Verifying: /home/mbr/src/gpgdir/test/data-dir/multi-line-ascii.asc [GNUPG:] BADSIG 9EDEEEEBA742EEEF Some User - Bugfix to not die() when files that are encrypted with a different GnuPG key are encountered in a directory that is being decrypted. A warning message (see below) is now generated and the file is skipped: [+] Decrypting: /home/mbr/tmp/gpgdir/a.gpg [GNUPG:] BAD_PASSPHRASE CF16F0FCFFF3FF4F [-] Skipping file encrypted with different GnuPG key: a.gpg - Updated to use the status output from GnuPG::Interface to detect a bad passphrase and whether a file is encrypted with the expected GnuPG key. - Moved the GnuPG::Interface, Class::MethodMaker, and Term::ReadKey modules to the deps/ directory, and updated the installer and RPM spec file to account for the path change. This change was suggested by Franck Joncourt for the other cipherdyne.org projects. - Updated the test suite to generate files in the output/ directory according to test number and append the result of each test within each file. This makes it easy to tell which tests have failed with a simple 'grep fail output/*test'. - Added the gpgdir-nodeps.spec file to allow an RPM to be built that does not contain any perl modules dependencies. - Updated gpgdir to import perl modules via 'require' statements instead of 'use' statements so that the path to the modules directory can be changed via the --Lib-dir command line argument. Also updated to use the 'auto' heuristic (first implemented in the fwknop project) to detect perl module directories that should be used in the --Lib-dir directory to import perl modules from. gpgdir-1.9.1 (06/07/2008): - Updated to Class::MethodMaker 2.11 from CPAN. This helps with systems running perl-5.10.0 and greater (such as Fedora 9). - Updated to always set the LC_ALL environmental variable to the "C" locale. This can be set to other locales with a new argument --locale, or the default locale can be used by using --no-locale argument. gpgdir-1.9 (05/31/2008): - Changed --Obfuscate-filenames format to not include the gpgdir PID. This allows directories to be encrypted/decrypted under -O multiple times without creating new filenames (which would pollute encrypted directories under rsync to other systems). The new -O encrypted filename format is just "gpgdir_.gpg". - Added PID locking against directories so that multiple gpgdir processes cannot operate against the same top-level directory simultaneously. This is useful for users that typically operate with multiple shells and might launch gpgdir from any of them. gpgdir-1.8 (04/04/2008): - Updated the test suite to validate the gpgdir --Obfuscate-filenames mode to ensure that files are encrypted as "gpgdir__.gpg". - Minor bug fix to remove the .gpgdir_map_file in --Obfuscate-filenames mode after a successful decryption cycle. - Updated to version 0.36 of CPAN GnuPG::Interface module. gpgdir-1.7 (02/18/2008): - Bugfix to ensure that encrypted directories can actually be decrypted. This bug was reported by Per Ronny Westin. - Updated to use the ".asc" extension for encrypted files in --Plain-ascii mode. - Added gpgdir test suite. All future gpgdir releases (and including this 1.7 release) require that all gpgdir tests pass on the systems where gpgdir is developed. gpgdir-1.6 (02/17/2008): - Bugfix to not include previously encrypted files (i.e. those with a .gpg extension) in the encryption/decryption file list. This bug was introduced in gpgdir-1.5 when a change was made to ignore ascii-armored files. - Added added LC_ALL=C locale setting for the install.pl script (this should help to ensure gpgdir is properly installed on most systems). Two new command line arguments --LC_ALL and --no-LC_ALL also allow the locale setting to be changed or not used at all. - Added --Exclude-mod-regex option to the install.pl script so that it is possible to force the exclusion of perl modules that gpgdir would normally install. This is useful for ensuring that gpgdir references perl modules that are already installed in the system perl library tree instead of using those that are installed in /usr/lib/gpgdir. - Updated to display command line usage warnings without automatically displaying the entire usage() page (which is quite long). gpgdir-1.5 (08/31/2007): - Added the --Symmetric option so that files can be encrypted/decrypted via a symmetric encryption algorithm (GnuPG commonly uses CAST5 for this). - Added the --Plain-ascii option so that GnuPG is invoked with the -a option so that encrypted files are ascii armored instead of encrypted in binary form. - Bugfix to ensure not to delete zero-size files if a bad password is given (gpgdir now just throws a warning and exits in this case). - Minor code enhancements to provide a consistent hash_init() invocation with the same options hash. - Updated to exclude .asc files from the encryption/decryption process. gpgdir-1.4 (07/20/2007): - (Anthony Chivetta) Submitted patch to implement the --overwrite-encrypted command line argument to allow previously encrypted files to be overwritten. This is useful for updating an encrypted directory with new versions of the previously encrypted files. Also added the --overwrite-decrypted command line argument to perform the same function for previously decrypted files. - (Anthony Chivetta) Submitted patch to fix a bug where a filename of "0.gpg" could not be decrypted because "0" does not evaluate to a true value. gpgdir-1.3 (06/09/2007): - Added --Obfuscate mode so that the files within a directory can be altered into unrecognizable names (which are stored within the file .gpgdir_map_file within each sub-directory, and this file is itself encrypted). The obfuscated file names are reversed when a directory is decrypted. - Added the --Agent-info command line argument so that the value of the GPG_AGENT_INFO environment variable can be specified on the gpgdir command line. gpgdir-1.2 (05/28/2007): - Added support for installing gpgdir on Windows under Cygwin (via the install.pl script). Installing gpgdir on FreeBSD systems also works. - Added support for installing gpgdir within a user home directory without the need for root access (this requires installing gpgdir with the install.pl script). - Added --agent to have gpgdir acquire gpg key password from a running gpg-agent instance. - Added --no-password so gpgdir can use a gpg key with no associated password (this is not common). The user is not prompted for a password in this case. gpgdir-1.1 (05/21/2007): - Added the ability to securely delete the original versions of files with the 'wipe' program (after they have been successfully encrypted). Also added --wipe-path to specify a path to the wipe binary (the default is /usr/bin/wipe), --wipe-interactive to force the wipe program to prompt the user before a file is deleted, and --wipe-cmdline to allow the user to build a set of command line arguments that are passed to the wipe program. - Added --Force to have gpgdir skip over the error condition where a file cannot be deleted (because of a permissions issue for example). - Added --Trial-run to allow the user to see what actions gpgdir would take to encrypt or decrypt files, but no files are actually modified. - Added --Interactive to have gpgdir prompt the user before every file is encrypted, decrypted. - Added the gpgdir.SlackBuild script (contributed by pyllyukko originally for the psad project) for building gpgdir on Slackware systems. gpgdir-1.0.3 (09/17/2006): - Minor bugfix to correct 1.0.1 version number (which should have been set to 1.0.2) in the gpgdir RPM spec file. gpgdir-1.0.2 (09/17/2006): - Minor bugfix to correct 1.0 version number (which should have been set to 1.0.1). The result is the 1.0.2 release. gpgdir-1.0.1 (09/16/2006): - Added --quiet option to have gpgdir print as little as possible to the screen when encrypting or decrypting a directory. - Added x86_64 RPM (original patch from Mate Wierdl adapted for gpgdir). gpgdir-1.0 (09/13/2006): - Added --Key-id command line argument so that use_key can be overridden from the command line - Made the argument to use_key not have to strictly be a keyID since GnuPG allows a unique string match on keys in the key ring - Added --Default-key to allow the user to have gpgdir use the default key that is defined by GnuPG within the ~/.gnupg/options file. - Updated the .gpgdirrc file to include the line "default_key" to allow the user to have gpgdir prefer to use the GnuPG default key. - Added the ChangeLog.svn file to show exactly which files have been changed from release to release, and what the corresponding Subversion log messages are. - Minor documentation updates. gpgdir-0.9.9 (09/07/2006): - Added RPM .spec file to build gpgdir as an RPM. - Added the --Skip-mod-install command line argument to install.pl to allow all perl module installs to be skipped. - Added the --force-mod-regex command line argument to install.pl to allow a regex match on perl module names to force matching modules to be installed. - Updated to TermReadKey-2.30 from 2.21. gpgdir-0.9.8 (07/03/2006): - Updated to use GnuPG::Interface instead of GnuPG module. This should fix the incompatibility issues seen between the GnuPG module and some GnuPG installations. - Added perl module installation code from fwknop (see http://www.cipherdyne.org/fwknop/). This allows gpgdir to preferentially use any perl modules that are already be installed on the system. gpgdir-0.9.4 (10/12/2005): - Updated test mode to encrypt and decrypt a testing file within the directory to be encrypted or decrypted. This file is located at /gpgdir_test, and is removed after the test is completed. - Bugfix for "protocol error: expected SHM_GET_XXX got GOOD_PASSPHRASE" error in GnuPG module. gpgdir-0.9.3 (02/20/2005): - Added --Include and --Include-from options to allow inclusion regular expressions to be specified. - Bugfix for not decrypting filesnames that contain spaces. gpgdir-0.9.2 (01/05/2005): - Added preservation of file mtime and atime values (may be disabled with the --no-preserve-times option). - Added testing encryption and decryption of dummy file (may be disabled with --skip-test) by default for both encrypt and decrypt modes. - Added --test-mode to run encrypt -> decrypt test and exit. - Removed unnecessary compression options. - Updated get_homedir() to reference HOME environmental variable if the /etc/passwd file does not exist (OS X being a good example). - Added --verbose mode. - Updated output to generate errors on a per-file basis instead of dumping them at the end of an encrypt/decrypt operation. gpgdir-0.9.1 (11/11/2004): - Updated GnuPG.pm perl module to handle return code of PLAINTEXT which seems to be returned by GunPG now (as of version 1.2.6) instead of DECRYPTION_OKAY upon a successful decryption. gpgdir-0.9 (09/12/2004): - Added --gnupg-dir option to allow a user to specify a different user's .gnupg directory for encryption keys. - Switched to "[+]" (and related) message prefixes. gpgdir-0.8 (05/29/2004): - Added --Exclude and --Exclude-from options to allow files to be excluded based on regex matches. - Reworked error messages so they contain the filename associated with each error. gpgdir-0.4 (04/23/2004): - Added --pw-file option so that a decryption password can be read out of a file. - Better directory validation (filesystem -e and -d checks). - Added INSTALL file. - Updated man page and README file. gpgdir-0.3 (09/27/2003): - Bundled perl modules GnuPG and TermReadKey with gpgdir. - Modified install.pl and gpgdir to install and use GnuPG and TermReadKey modules from the /usr/lib/gpgdir directory. - Added check_commands() subroutine from psad. ./gpgdir/gpgdir.10000644000175000017500000002423413233575632013711 0ustar guilhemguilhem.\" Process this file with .\" groff -man -Tascii foo.1 .\" .TH GPGDIR 1 "May, 2007" Linux .SH NAME .B gpgdir \- recursive directory encryption with GnuPG .SH SYNOPSIS .B gpgdir \-e|\-d [options] .SH DESCRIPTION .B gpgdir is a perl script that uses the CPAN GnuPG::Interface perl module to recursively encrypt and decrypt directories using gpg. .B gpgdir recursively descends through a directory in order to make sure it encrypts or decrypts every file in a directory and all of its subdirectories. By default the mtime and atime values of all files will be preserved upon encryption and decryption (this can be disabled with the .B \-\-no-preserve-times option). Note that in .B \-\-encrypt mode, gpgdir will delete the original files that it successfully encrypts (unless the .B \-\-no-delete option is given). However, upon startup gpgdir first asks for a the decryption password to be sure that a dummy file can successfully be encrypted and decrypted. The initial test can be disabled with the .B \-\-skip-test option so that a directory can easily be encrypted without having to also specify a password (this is consistent with .B gpg behavior). Also, note that gpgdir is careful not encrypt hidden files and directories. After all, you probably don't want your ~/.gnupg directory or ~/.bashrc file to be encrypted. The key .B gpgdir uses to encrypt/decrypt a directory is specified in ~/.gpgdirrc. Finally, .B gpgdir can use the .B wipe program with the .B \-\-Wipe command line option to securely delete the original unencrypted files after they have been successfully encrypted. This elevates the security stance of gpgdir since it is more difficult to recover the unencrypted data associated with files from the filesystem after they are encrypted (unlink() does not erase data blocks even though a file is removed). .SH OPTIONS .TP .BR \-e ", " \-\^\-encrypt\ \ Recursively encrypt all files in the directory specified on the command line. All original files will be deleted (a password check is performed first to make sure that the correct password to unlock the private GnuPG key is known to the user). .TP .BR \-d ", " \-\^\-decrypt\ \ Recursively decrypt all files in the directory specified on the command line. The encrypted .gpg version of each file will be deleted. .TP .BR \-\^\-sign\ \ Recursively sign all files in the directory specified on the command line. For each file, a detached .asc signature will be created. .TP .BR \-\^\-verify\ \ Recursively verify all .asc signatures for files in the directory specified on the command line. .TP .BR \-g ", " \-\^\-gnupg-dir\ \ Specify which .gnupg directory will be used to find GnuPG keys. The default is ~/.gnupg if this option is not used. This option allows gpgdir to be run as one user but use the keys of another user (assuming permissions are setup correctly, etc.). .TP .BR \-p ", " \-\^\-pw-file\ \ Read decryption password from .B pw-file instead of typing it on the command line. .TP .BR \-t ", " \-\^\-test-mode Run an encryption and decryption test against a dummy file and exit. This test is always run by default in both .B \-\-encrypt and .B \-\-decrypt mode. .TP .BR \-S ", " \-\^\-Symmetric Instruct .B gpgdir to encrypt to decrypt files using a symmetric cipher supported by GnuPG (CAST5 is commonly used). This results in a significant speed up for the encryption/decryption process. .TP .BR \-T ", " \-\^\-Trial-run Show what encrypt/decrypt actions would take place without actually doing them. The filesystem is not changed in any way in this mode. .TP .BR \-I ", " \-\^\-Interactive Prompt the user before actually encrypting or decrypting each file. This is useful to have fine-grained control over .B gpgdir operations as it recurses through a directory structure. .TP .BR \-F ", " \-\^\-Force Tell .B gpgdir to ignore non-fatal error conditions, such as the inability to encrypt or decrypt individual files because of permissions errors. .TP .BR \-\^\-Exclude\ \ Instruct gpgdir to skip all files that match .B pattern as a regex match against each filename. This is similar to the .B \-\-exclude option in the standard GNU tar command. .TP .BR \-\^\-Exclude-from\ \ Instruct gpgdir to exclude all files matched by patterns listed in .B file. This is similar to the .B \-\-exclude-from the GNU tar command. .TP .BR \-\^\-Include\ \ Instruct gpgdir to only include files that match .B pattern as a regex match against each filename. .TP .BR \-\^\-Include-from\ \ Instruct gpgdir to only include files matched by patterns listed in .B file. .TP .BR \-W ", " \-\^\-Wipe Use the .B wipe program to securely delete files after they have been successfully encrypted. .TP .BR \-O ", " \-\^\-Obfuscate-filename Tell .B gpgdir to obfuscate the file names of files that it encrypts (in \-e mode). The names of each file are stored within the file .gpgdir_map_file for every sub-directory, and this file is itself encrypted. In decryption mode (\-d), the \-O argument reverses the process so that the original files are restored. .TP .BR \-\^\-overwrite-encrypted Overwrite encrypted files even if a previous .gpg file already exists. .TP .BR \-\^\-overwrite-decrypted Overwrite decrypted files even if the previous unencrypted file already exists. .TP .BR \-K ", " \-\^\-Key-id\ \ Manually specify a GnuPG key ID from the command line. Because GnuPG supports matching keys with a string, .B id does not strictly have to be a key ID; it can be a string that uniquely matches a key in the GnuPG key ring. .TP .BR \-D ", " \-\^\-Default-key Use the key that GnuPG defines as the default, i.e. the key that is specified by the .B default-key variable in ~/.gnupg/options. If the default-key variable is not defined within ~/.gnupg/options, then GnuPG tries to use the first suitable key on its key ring (the initial encrypt/decrypt test makes sure that the user knows the corresponding password for the key). .TP .BR \-a ", " " \-\^\-agent Instruct .B gpgdir to acquire gpg key password from a running .B gpg-agent instance. .TP .BR \-A ", " \-\^\-Agent-info\ \ Specify the value of the GPG_AGENT_INFO environment variable as returned by the .B gpg-agent \-\-daemon command. If the .B gpgdir \-\-agent command line argument is used instead of .B \-\-Agent-info, then gpgdir assumes that the GPG_AGENT_INFO environment variable has already been set in the current shell. .TP .BR \-s ", " " \-\^\-skip-test Skip encryption and decryption test. This will allow .B gpgdir to be used to encrypt a directory without specifying a password (which normally gets used in encryption mode to test to make sure decryption against a dummy file works properly). .TP .BR \-q ", " \-\^\-quiet Print as little as possible to the screen when encrypting or decrypting a directory. .TP .BR \-\^\-no-recurse Instruct gpgdir to not recurse through any subdirectories of the directory that is being encrypted or decrypted. .TP .BR \-\^\-no-password Instruct gpgdir to not ask the user for a password. This is only useful when a gpg key literally has no associated password (this is not common). .TP .BR \-\^\-no-delete Instruct gpgdir to not delete original files at encrypt time. .TP .BR \-\^\-no-preserve times Instruct gpgdir to not preserve original file mtime and atime values upon encryption or decryption. .TP .BR \-l ", " " \-\^\-locale\ \ Provide a locale setting other than the default "C" locale. .TP .BR \-\^\-no-locale Do not set the locale at all so that the default system locale will apply. .TP .BR \-v ", " \-\^\-verbose Run in verbose mode. .TP .BR \-V ", " \-\^\-Version Print version number and exit. .TP .BR \-h ", " \-\^\-help Print usage information and exit. .SH FILES .B ~/.gpgdirrc .RS Contains the key id of the user gpg key that will be used to encrypt or decrypt the files within a directory. .RE .PP .SH ENVIRONMENT .TP 13 .I HOME Set the default home directory. .TP 13 .I GNUPGBIN Set the gpg binary. Default: "gpg". .TP 13 .I GNUPGHOME Set the default working directory for gpg. Default: "~/.gnupg". .SH EXAMPLES The following examples illustrate the command line arguments that could be supplied to gpgdir in a few situations: .PP To encrypt a directory: .PP .B $ gpgdir \-e /some/dir .PP To encrypt a directory, and use the wipe command to securely delete the original unencrypted files: .PP .B $ gpgdir \-W \-e /some/dir .PP To encrypt a directory with the default GnuPG key defined in ~/.gnupg/options: .PP .B $ gpgdir \-e /some/dir \-\-Default-key .PP To decrypt a directory with a key specified in ~/.gpgdirrc: .PP .B $ gpgdir \-d /some/dir .PP To encrypt a directory but skip all filenames that contain the string "host": .PP .B $ gpgdir \-e /some/dir \-\-Exclude host .PP To encrypt a directory but only encrypt those files that contain the string "passwd": .PP .B $ gpgdir \-e /some/dir \-\-Include passwd .PP To acquire the GnuPG key password from a running gpg-agent daemon in order to decrypt a directory (this requires that gpg-agent has the password): .PP .B $ gpgdir \-A /tmp/gpg-H4DBhc/S.gpg-agent:7046:1 \-d /some/dir .PP To encrypt a directory but skip the encryption/decryption test (so you will not be prompted for a decryption password): .PP .B $ gpgdir \-e /some/dir \-s .PP To encrypt a directory and no subdirectories: .PP .B $ gpgdir \-e /some/dir \-\-no-recurse .PP To encrypt root's home directory, but use the GnuPG keys associated with the user "bob": .PP .B # gpgdir \-e /root \-g /home/bob/.gnupg .PP .SH DEPENDENCIES .B gpgdir requires that gpg, the Gnu Privacy Guard (http://www.gnupg.org) is installed. .B gpgdir also requires the GnuPG::Interface perl module from CPAN, but it is bundled with .B gpgdir and is installed in /usr/lib/gpgdir at install-time so it does not pollute the system perl library tree. .SH "SEE ALSO" .BR gpg (1) .SH AUTHOR Michael Rash .SH CONTRIBUTORS Many people who are active in the open source community have contributed to gpgdir; see the .B CREDITS file in the gpgdir sources. .SH BUGS Send bug reports to mbr@cipherdyne.org. Suggestions and/or comments are always welcome as well. .SH DISTRIBUTION .B gpgdir is distributed under the GNU General Public License (GPL), and the latest version may be downloaded from .B http://www.cipherdyne.org ./gpgdir/bump_version.pl0000755000175000017500000000267213233575632015425 0ustar guilhemguilhem#!/usr/bin/perl -w # ############################################################################# # # File: bump_version.pl # # Purpose: Minor script to enforce consistency in gpgdir version tags. # ############################################################################# # # $Id: bump_version.pl 1055 2008-05-21 02:57:17Z mbr $ # use strict; my @files = qw( gpgdir test/gpgdir_test.pl ); my $new_version = $ARGV[0] or die "[*] $0 "; open F, '< VERSION' or die "[*] Could not open VERSION file: $!"; my $old_version = ; close F; chomp $old_version; print "[+] Updating software versions...\n"; for my $file (@files) { if ($file =~ /\.c/) { ###* Version: 1.8.4-pre2 my $search_re = qr/^\*\s+Version:\s+$old_version/; my $replace_str = '* Version: ' . $new_version; system qq{perl -p -i -e 's|$search_re|} . qq{$replace_str|' $file}; } else { ### Version: 1.8.4 my $search_re = qr/#\s+Version:\s+$old_version/; my $replace_str = '# Version: ' . $new_version; system qq{perl -p -i -e 's|$search_re|$replace_str|' $file}; ### my $version = '1.8.4'; $search_re = qr/^my\s+\x24version\s+=\s+'$old_version';/; $replace_str = q|my \x24version = '| . $new_version . q|';|; system qq{perl -p -i -e "s|$search_re|$replace_str|" $file}; } } system qq{perl -p -i -e 's|$old_version|$new_version|' VERSION}; exit 0; ./gpgdir/CREDITS0000644000175000017500000000421113233575632013364 0ustar guilhemguilhemPer Ronny Westin - Found PLAINTEXT vs. DECRYPTION_OKAY return code bug for GnuPG 1.2.6. - Reported directory decryption bug in gpgdir-1.6. The result was the addition of the gpgdir test suite. Kai Raven - Bugfix in man page for file compression/decompression wording. Craig Needs - Suggested --gnupg-dir option, testing help. Chris P - Found bug where gpgdir would not decrypt files that contained spaces. Ian Scott - Reported "protocol error: expected SHM_GET_XXX got GOOD_PASSPHRASE" bug in GnuPG module. Mate Wierdl - Contributed patch (originally for the psad project) for building the RPM on x86_64 platforms. pyllyukko - Added the gpgdir.SlackBuild script (adapted from the psad project). Anthony Chivetta - Submitted patch to fix a bug where files named "0.gpg" could not be decrypted. - Submitted patch to implement the --overwrite-encrypted command line argument to allow previously encrypted files to be overwritten. This is useful for updating an encrypted directory with new versions of the previously encrypted files. Fermin Manzanedo - Suggested the --Symmetric option so that files can be encrypted/ decrypted via a symmetric cipher (GnuPG supports CAST5 by default). Franck Joncourt - Performed analysis of locale settings for fwknop installer and suggested using the LC_ALL environmental variable instead of the LANG variable (which is superseded by LC_* vars). - Suggested moving perl modules to the deps/ directory. This is to support the integration of the Cipherdyne projects with Debian. - Added Short description to the gpgdir man page. This fixes the following lintian warning: http://lintian.debian.org/tags/manpage-has-bad-whatis-entry.html - Suggested the appropriate bugfix to interface non-interactively with the wipe program (-f instead of -I in later versions). Lars Wilke - Reported a bug in missing the proper handling of files with spaces when using the --Wipe secure deletion mode. - Suggested the ability to encrypt/decrypt hidden files, and --Force now supports this. ./gpgdir/LICENSE0000644000175000017500000004307713233575632013366 0ustar guilhemguilhem GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ./gpgdir/gpgdir0000755000175000017500000014301413233575632013553 0ustar guilhemguilhem#!/usr/bin/perl -w # ########################################################################### # # File: gpgdir # # URL: http://www.cipherdyne.org/gpgdir/ # # Purpose: To encrypt/decrypt whole directories # # Author: Michael Rash (mbr@cipherdyne.com) # # Version: 1.9.5 # # Copyright (C) 2002-2009 Michael Rash (mbr@cipherdyne.org) # # License: GNU General Public License version 2 (GPLv2) # # 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, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA # ########################################################################### # # $Id: gpgdir 341 2009-08-26 02:43:51Z mbr $ # use File::Find; use File::Copy; use IO::File; use IO::Handle; use Getopt::Long; use Cwd; use strict; ### set the current gpgdir version and file revision numbers my $version = '1.9.5'; my $revision_svn = '$Revision: 341 $'; my $rev_num = '1'; ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|; ### establish some defaults my $encrypt_user = ''; my $gpg_homedir = ''; my $dir = ''; my $pw = ''; my $encrypt_dir = ''; my $decrypt_dir = ''; my $sign_dir = ''; my $verify_dir = ''; my $homedir = ''; my $exclude_pat = ''; my $exclude_file = ''; my $include_pat = ''; my $include_file = ''; my $lib_dir = '/usr/lib/gpgdir'; my $pid_file = ''; my $total_encrypted = 0; my $total_decrypted = 0; my $norecurse = 0; my $printver = 0; my $no_delete = 0; my $no_fs_times = 0; my $test_and_exit = 0; my $trial_run = 0; my $skip_test_mode = 0; my $verbose = 0; my $quiet = 0; my $use_gpg_agent = 0; ### use gpg-agent for passwords my $gpg_agent_info = ''; my $force_mode = 0; my $help = 0; my $wipe_mode = 0; my $encrypt_mode = 0; my $signing_mode = 0; my $verify_mode = 0; my $use_default_key = 0; my $pw_file = ''; my $wipe_cmd = '/usr/bin/wipe'; my $wipe_cmdline = ''; my $wipe_interactive = 0; my $interactive_mode = 0; my $ascii_armor_mode = 0; my @exclude_patterns = (); my @include_patterns = (); my %files = (); my %options = (); my %obfuscate_ctrs = (); my %obfuscated_dirs = (); my $total_mapped_files = 0; my $have_obfuscated_file = 0; my $cmdline_no_password = 0; my $obfuscate_mode = 0; my $obfuscate_map_filename = '.gpgdir_map_file'; my $overwrite_encrypted = 0; my $overwrite_decrypted = 0; my $symmetric_mode = 0; my $DEL_SOURCE_FILE = 1; my $NO_DEL_SOURCE_FILE = 0; my $locale = 'C'; ### default LC_ALL env variable my $no_locale = 0; ### for user answers my $ACCEPT_YES_DEFAULT = 1; my $ACCEPT_NO_DEFAULT = 2; ### turn off buffering $| = 1; unless ($< == $>) { die "[*] Real and effective uid must be the same. Make sure\n", " gpgdir has not been installed as a SUID binary.\n", "Exiting."; } my @args_cp = @ARGV; ### make Getopts case sensitive Getopt::Long::Configure('no_ignore_case'); die "[*] Use --help for usage information.\n" unless(GetOptions ( 'encrypt-dir=s' => \$encrypt_dir, # Encrypt files in this directory. 'decrypt-dir=s' => \$decrypt_dir, # Decrypt files in this directory. 'sign-dir=s' => \$sign_dir, # Sign files in this directory. 'verify-dir=s' => \$verify_dir, # Verify files in this directory. 'gnupg-dir=s' => \$gpg_homedir, # Path to /path/to/.gnupg directory. 'pw-file=s' => \$pw_file, # Read password out of this file. 'agent' => \$use_gpg_agent, # Use gpg-agent for passwords. 'Agent-info=s' => \$gpg_agent_info, # Specify GnuPG agent connection # information. 'Wipe' => \$wipe_mode, # Securely delete unencrypted files. 'wipe-path=s' => \$wipe_cmd, # Path to wipe command. 'wipe-interactive' => \$wipe_interactive, # Disable "wipe -I" 'wipe-cmdline=s' => \$wipe_cmdline, # Specify wipe command line. 'Obfuscate-filenames' => \$obfuscate_mode, # substitute real filenames # with manufactured ones. 'obfuscate-map-file=s' => \$obfuscate_map_filename, # path to mapping file. 'Force' => \$force_mode, # Continue if files can't be deleted. 'overwrite-encrypted' => \$overwrite_encrypted, # Overwrite encrypted files # even if they exist. 'overwrite-decrypted' => \$overwrite_decrypted, # Overwrite decrypted files # even if they exist. 'Exclude=s' => \$exclude_pat, # Exclude a pattern from encrypt/decrypt # cycle. 'Exclude-from=s' => \$exclude_file, # Exclude patterns in from # encrypt decrypt cycle. 'Include=s' => \$include_pat, # Specify a pattern used to restrict # encrypt/decrypt operation to. 'Include-from=s' => \$include_file, # Specify a file of include patterns to # restrict all encrypt/decrypt # operations to. 'test-mode' => \$test_and_exit, # Run encrypt -> decrypt test only and # exit. 'Trial-run' => \$trial_run, # Don't modify any files; just show what # would have happened. 'quiet' => \$quiet, # Print as little as possible to # stdout. 'Interactive' => \$interactive_mode, # Query the user before encrypting/ # decrypting/deleting any files. 'Key-id=s' => \$encrypt_user, # Specify encrypt/decrypt key 'Default-key' => \$use_default_key, # Assume that default-key is set within # ~/.gnupg/options. 'Symmetric' => \$symmetric_mode, # encrypt using symmetric cipher. # (this option is not required to # also decrypt, GnuPG handles # that automatically). 'Plain-ascii' => \$ascii_armor_mode, # Ascii armor mode (creates non-binary # encrypted files). 'skip-test' => \$skip_test_mode, # Skip encrypt -> decrypt test. 'no-recurse' => \$norecurse, # Don't encrypt/decrypt files in # subdirectories. 'no-delete' => \$no_delete, # Don't delete files once they have # been encrypted. 'no-password' => \$cmdline_no_password, # Do not query for a password (only # useful for when the gpg literally # has no password). 'user-homedir=s' => \$homedir, # Path to home directory. 'no-preserve-times' => \$no_fs_times, # Don't preserve mtimes or atimes. 'LC_ALL=s' => \$locale, 'locale=s' => \$locale, # synonym 'no-LC_ALL' => \$no_locale, 'no-locale' => \$no_locale, # synonym 'Lib-dir=s' => \$lib_dir, # Path to perl module path 'verbose' => \$verbose, # Verbose mode. 'Version' => \$printver, # Print version 'help' => \$help # Print help )); &usage_and_exit() if $help; ### set LC_ALL env variable $ENV{'LC_ALL'} = $locale unless $no_locale; print "[+] gpgdir v$version (file revision: $rev_num)\n", " by Michael Rash \n" and exit 0 if $printver; if ($symmetric_mode and ($use_gpg_agent or $gpg_agent_info)) { die "[*] gpg-agent incompatible with --Symmetric mode"; } die "[*] Cannot --sign-dir and --verify-dir" if $sign_dir and $verify_dir; if ($sign_dir) { $encrypt_dir = $sign_dir; $signing_mode = 1; } elsif ($verify_dir) { $decrypt_dir = $verify_dir; $verify_mode = 1; } if ($encrypt_dir and $overwrite_decrypted) { die "[*] The -e and --overwrite-decrypted options are incompatible."; } if ($decrypt_dir and $overwrite_encrypted) { die "[*] The -d and --overwrite-encrypted options are incompatible."; } ### import perl modules (GnuPG::Interface, etc.) &import_perl_modules(); if ($wipe_mode) { unless (-e $wipe_cmd) { die "[*] Can't find wipe command at: $wipe_cmd,\n", " use --wipe-path to specify path."; } unless (-e $wipe_cmd) { die "[*] Can't execute $wipe_cmd"; } } my $initial_dir = cwd or die "[*] Could not get CWD: $!"; if ($gpg_homedir) { ### it was specified on the comamnd line if ($gpg_homedir !~ m|^/|) { $gpg_homedir = $initial_dir . '/' . $gpg_homedir; } } ### build up GnuPG options hash if ($verbose) { %options = ('homedir' => $gpg_homedir); } else { %options = ( 'batch' => 1, 'homedir' => $gpg_homedir ); } $options{'armor'} = 1 if $ascii_armor_mode or $signing_mode; ### get the path to the user's home directory $homedir = &get_homedir() unless $homedir; unless ($symmetric_mode) { unless ($gpg_homedir) { $gpg_homedir = "${homedir}/.gnupg" if -d "${homedir}/.gnupg"; } unless (-d $gpg_homedir) { die "[*] GnuPG directory: $gpg_homedir does not exist. Please\n", " create it by executing: \"gpg --gen-key\". Exiting.\n"; } ### get the key identifier from ~/.gnupg $encrypt_user = &get_key() unless $encrypt_user or $use_default_key; } if ($decrypt_dir and $encrypt_dir) { die "[*] Cannot encrypt and decrypt the same directory, see --help\n"; } unless ($decrypt_dir or $encrypt_dir or $test_and_exit) { die "[*] Please specify -e , -d , or --test-mode, see --help\n"; } if ($obfuscate_mode) { if ($sign_dir) { die "[*] -O mode incompatible with --sign-dir"; } elsif ($verify_dir) { die "[*] -O mode incompatible with --verify-dir"; } } ### exclude file pattern push @exclude_patterns, $exclude_pat if $exclude_pat; if ($exclude_file) { open P, '<', $exclude_file or die "[*] Could not open file: $exclude_file"; my @lines =

; close P; for my $line (@lines) { next unless $line =~ /\S/; chomp $line; push @exclude_patterns, qr{$line}; } } ### include file pattern push @include_patterns, $include_pat if $include_pat; if ($include_file) { open P, '<', $include_file or die "[*] Could not open file: $include_file"; my @lines =

; close P; for my $line (@lines) { next unless $line =~ /\S/; chomp $line; push @include_patterns, qr{$line}; } } if ($encrypt_dir) { $dir = $encrypt_dir; $encrypt_mode = 1; } elsif ($decrypt_dir) { $dir = $decrypt_dir; $encrypt_mode = 0; } if ($dir) { die "[*] Directory does not exist: $dir" unless -e $dir; die "[*] Not a directory: $dir" unless -d $dir; } ### don't need to test encrypt/decrypt ability if we are running ### in --Trial-run mode. $skip_test_mode = 1 if $trial_run or $signing_mode or $verify_mode; if ($dir eq '.') { $dir = $initial_dir; } elsif ($dir !~ m|^/|) { $dir = $initial_dir . '/' . $dir; } $dir =~ s|/$||; ### remove any trailing slash ### make sure another gpgdir process is not trying to operate ### on the same directory $pid_file = "$dir/.gpgdir.pid"; &unique_pid(); &write_pid(); if ($symmetric_mode or $signing_mode) { &get_password(); } else { &get_password() unless (($encrypt_mode and $skip_test_mode) or $verify_mode); } ### run a test to make sure gpgdir and encrypt and decrypt a file unless ($skip_test_mode) { my $rv = &test_mode(); exit $rv if $test_and_exit; } if ($signing_mode) { print "[+] Signing files in directory: $dir\n" unless $quiet; } elsif ($encrypt_mode) { print "[+] Encrypting files in directory: $dir\n" unless $quiet; } elsif ($verify_mode) { print "[+] Verifying signatures in directory: $dir\n" unless $quiet; } else { print "[+] Decrypting files in directory: $dir\n" unless $quiet; } ### build a hash of file paths to work against &get_files($dir); ### perform the gpg operation (encrypt/decrypt) &gpg_operation(); &obfuscated_mapping_files() if $obfuscate_mode; unless ($obfuscate_mode) { if ($have_obfuscated_file) { print "[-] Obfuscated filenames detected, try decrypting with -O\n" unless $quiet; } } if ($signing_mode) { print "[+] Total number of files signed: " . "$total_encrypted\n" unless $quiet; } elsif ($encrypt_mode) { print "[+] Total number of files encrypted: " . "$total_encrypted\n" unless $quiet; } elsif ($verify_mode) { print "[+] Total number of files verified: " . "$total_decrypted\n" unless $quiet; } else { print "[+] Total number of files decrypted: " . "$total_decrypted\n" unless $quiet; } if (-e $pid_file) { unlink $pid_file or die "[*] Could not remove pid file $pid_file: $!"; } exit 0; #==================== end main ===================== sub encrypt_or_sign_file() { my ($in_file, $out_file, $del_flag) = @_; my $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->hash_init(%options); die "[*] Could not create new gpg object with ", "homedir: $gpg_homedir" unless $gpg; unless ($symmetric_mode or $use_default_key) { $gpg->options->default_key($encrypt_user); $gpg->options->push_recipients($encrypt_user); } my ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) = (IO::File->new($in_file), IO::File->new("> $out_file"), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); my $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, passphrase => $pw_fh, status => $status_fh ); $handles->options('stdin')->{'direct'} = 1; $handles->options('stdout')->{'direct'} = 1; my $pid; if ($use_gpg_agent or $gpg_agent_info) { ### set environment explicitly if --Agent was specified if ($gpg_agent_info) { $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info; } $pid = $gpg->encrypt('handles' => $handles, 'command_args' => [ qw( --use-agent ) ]); } else { if ($symmetric_mode) { $pid = $gpg->encrypt_symmetrically('handles' => $handles); } elsif ($signing_mode) { $pid = $gpg->detach_sign('handles' => $handles); } else { $pid = $gpg->encrypt('handles' => $handles); } } print $pw_fh $pw; close $pw_fh; my @errors = <$error_fh>; close $error_fh; my @status = <$status_fh>; close $status_fh; close $input_fh; close $output_fh; waitpid $pid, 0; if ($verbose) { print for @errors; } else { for (@errors) { print if /bad\s+pass/; } } if (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($use_gpg_agent) { die "[*] Created zero-size file: $out_file\n", " Maybe gpg-agent does not yet have the password for that key?\n", " Try with --verbose"; } else { die "[*] Created zero-size file: $out_file\n", " Bad password? Try with --verbose"; } } return 1; } sub decrypt_or_verify_file() { my ($in_file, $out_file, $del_flag) = @_; my $pid; my $bad_passphrase = 0; my $bad_signature = 0; my $file_encrypted_with_expected_key = 0; my $input_fh = ''; my $output_fh = ''; my $error_fh = ''; my $pw_fh = ''; my $status_fh = ''; my $handles = ''; my $gpg = GnuPG::Interface->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $gpg->options->hash_init(%options); die "[*] Could not create new gpg object with ", "homedir: $gpg_homedir" unless $gpg; unless ($verify_mode or $symmetric_mode or $use_default_key) { $gpg->options->default_key($encrypt_user); $gpg->options->push_recipients($encrypt_user); } if ($verify_mode) { ($input_fh, $output_fh, $error_fh, $status_fh) = (IO::Handle->new(), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, status => $status_fh ); } else { ($input_fh, $output_fh, $error_fh, $pw_fh, $status_fh) = (IO::File->new($in_file), IO::File->new("> $out_file"), IO::Handle->new(), IO::Handle->new(), IO::Handle->new()); $handles = GnuPG::Handles->new( stdin => $input_fh, stdout => $output_fh, stderr => $error_fh, passphrase => $pw_fh, status => $status_fh ); $handles->options('stdin')->{'direct'} = 1; $handles->options('stdout')->{'direct'} = 1; } if ($use_gpg_agent) { $pid = $gpg->decrypt('handles' => $handles, 'command_args' => [ qw( --use-agent ) ]); } else { if ($verify_mode) { $pid = $gpg->wrap_call( 'commands' => [ qw( --verify ) ], 'command_args' => [ ( $in_file ) ], 'handles' => $handles ); } else { $pid = $gpg->decrypt('handles' => $handles); } } unless ($verify_mode) { print $pw_fh $pw; close $pw_fh; } my @errors = <$error_fh>; close $error_fh; my @status = <$status_fh>; close $status_fh; close $input_fh; close $output_fh; waitpid $pid, 0; for (@status) { if ($verify_mode) { ### [GNUPG:] BADSIG 9EEEEE6BEE428EEE Some User $bad_signature = 1 if /BADSIG/; } else { ### [GNUPG:] BAD_PASSPHRASE C326F95CE133EA4E $bad_passphrase = 1 if /BAD_?PASS/; if (/NEED_PASSPHRASE\s\S+\s+\S+$encrypt_user\s/) { ### [GNUPG:] NEED_PASSPHRASE CDE4D7DDFD66DCB9 95D85DDDDD42D39D 16 0 $file_encrypted_with_expected_key = 1; } elsif ((length($encrypt_user) == 8) and /USERID_HINT\s+.*$encrypt_user/) { $file_encrypted_with_expected_key = 1; } } } if ($verbose) { print " GnuPG errors:\n"; print for @errors; print " GnuPG status:\n"; print for @status; } else { for (@status) { if (/BAD_?PASS/) { print unless $quiet; } elsif (/BADSIG/) { print unless $quiet; } } } if ($bad_passphrase) { if (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($file_encrypted_with_expected_key) { die "[*] Bad passphrase, try gpgdir with -v"; } else { print "[-] Skipping file encrypted with different ", "GnuPG key: $in_file\n" unless $quiet; } } else { die "[*] Bad passphrase, but created non-zero sized output file, should not\n", " happen. Try with --verbose"; } } elsif (-s $out_file == 0) { &delete_file($out_file); &delete_file($in_file) if $del_flag == $DEL_SOURCE_FILE; if ($use_gpg_agent) { die "[*] Created zero-size file: $out_file\n", " Maybe gpg-agent does not yet have the password for that key?\n", " Try with --verbose"; } else { die "[*] Created zero-size file: $out_file\n", " Bad password? Try with --verbose"; } } if ($bad_signature) { return 0; } return 1; } sub delete_file() { my $file = shift; return if $no_delete; return unless -e $file; if ($wipe_mode) { my @cmd = ($wipe_cmd); if ($wipe_cmdline) { push @cmd, $wipe_cmdline; } else { push @cmd, $wipe_interactive ? '-i' : qw/-f -s/; } push @cmd, $file; if ($verbose) { print " Executing: ", join(' ', @cmd), "\n"; } ### wipe the file system @cmd; } else { unlink $file; } if (-e $file) { my $msg = "[-] Could not delete file: $file\n"; if ($force_mode) { print $msg unless $quiet; } else { die $msg unless $quiet; } } return; } sub gpg_operation() { ### sort by oldest to youngest mtime FILE: for my $file (sort {$files{$a}{'mtime'} <=> $files{$b}{'mtime'}} keys %files) { ### see if we have an exclusion pattern that implies ### we should skip this file if (@exclude_patterns and &exclude_file($file)) { print "[+] Skipping excluded file: $file\n" if $verbose and not $quiet; next FILE; } ### see if we have an inclusion pattern that implies ### we should process this file if (@include_patterns and not &include_file($file)) { print "[+] Skipping non-included file: $file\n" if $verbose and not $quiet; next FILE; } ### dir is always a full path my ($dir, $filename) = ($file =~ m|(.*)/(.*)|); unless (chdir($dir)) { print "[-] Could not chdir $dir, skipping.\n" unless $quiet; next FILE; } my $mtime = $files{$file}{'mtime'}; my $atime = $files{$file}{'atime'}; if ($encrypt_mode) { my $encrypt_filename = "$filename.gpg"; if ($obfuscate_mode) { unless (defined $obfuscate_ctrs{$dir}) { ### create a new gpgdir mapping file for obfuscated file ### names, but preserve any previously encrypted file ### name mappings &handle_old_obfuscated_map_file(); ### make obfuscated file names start at 1 for each ### directory $obfuscate_ctrs{$dir} = 1; } $encrypt_filename = 'gpgdir_' . $obfuscate_ctrs{$dir} . '.gpg'; } if ($ascii_armor_mode or $signing_mode) { $encrypt_filename = "$filename.asc"; } if (-e $encrypt_filename and not $overwrite_encrypted) { my $str = 'Encrypted'; $str = 'Signed' if $signing_mode; print "[-] $str file $dir/$encrypt_filename already ", "exists, skipping.\n" unless $quiet; next FILE; } if ($interactive_mode) { my $str = 'Encrypt'; $str = 'Sign' if $signing_mode; next FILE unless (&query_yes_no( " $str: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT)); } my $str = 'Encrypting'; $str = 'Signing' if $signing_mode; print "[+] $str: $file\n" unless $quiet; unless ($trial_run) { my $rv = &encrypt_or_sign_file($filename, $encrypt_filename, $NO_DEL_SOURCE_FILE); if (-e $encrypt_filename and -s $encrypt_filename != 0) { ### set the atime and mtime to be the same as the ### original file. unless ($no_fs_times) { if (defined $mtime and $mtime and defined $atime and $atime) { utime $atime, $mtime, $encrypt_filename; } } unless ($signing_mode) { ### only delete the original file if ### the encrypted one exists if ($wipe_mode and not $quiet) { print " Securely deleting file: $file\n"; } &delete_file($filename); if ($obfuscate_mode) { ### record the original file name mapping &append_obfuscated_mapping($filename, $encrypt_filename); $obfuscate_ctrs{$dir}++; } } $total_encrypted++; } else { my $str = 'encrypt'; $str = 'sign' if $signing_mode; print "[-] Could not $str file: $file\n" unless $quiet; next FILE; } } } else { ### allow filenames with spaces my $decrypt_filename = ''; if ($filename =~ /^(.+)\.gpg$/) { $decrypt_filename = $1; } elsif ($filename =~ /^(.+)\.asc$/) { $decrypt_filename = $1; } elsif ($filename =~ /^(.+)\.pgp$/) { $decrypt_filename = $1; } if ($obfuscate_mode) { &import_obfuscated_file_map($dir) unless defined $obfuscated_dirs{$dir}; if (defined $obfuscated_dirs{$dir}{$filename}) { $decrypt_filename = $obfuscated_dirs{$dir}{$filename}; } else { ### print "[-] Obfuscated file map does not exist for ", "$filename in\n $obfuscate_map_filename, ", "skipping.\n" unless $quiet; next FILE; } } else { if (not $force_mode and ($file =~ /gpgdir_\d+_\d+\.gpg/ or $file =~ /gpgdir_\d+\.gpg/)) { ### be careful not to decrypt obfuscated file unless we ### are running in -O mode. This ensures that the ### original file names will be acquired from the ### /some/dir/.gpgdir_map_file $have_obfuscated_file = 1; next FILE; } } ### length() allows files named "0" next FILE unless length($decrypt_filename) > 0; if ($verify_mode) { unless (-e $decrypt_filename) { print "[-] Original file $decrypt_filename ", "does not exist, skipping.\n"; next FILE; } } else { ### don't decrypt a file on top of a normal file of ### the same name if (-e $decrypt_filename and not $overwrite_decrypted) { print "[-] Decrypted file $dir/$decrypt_filename ", "already exists. Skipping.\n" unless $quiet; next FILE; } } if ($interactive_mode) { my $str = 'Decrypt'; $str = 'Verify' if $verify_mode; next FILE unless (&query_yes_no( " $str: $file ([y]/n)? ", $ACCEPT_YES_DEFAULT)); } unless ($trial_run) { my $str = 'Decrypting'; $str = 'Verifying' if $verify_mode; print "[+] $str: $dir/$filename\n" unless $quiet; my $rv = &decrypt_or_verify_file($filename, $decrypt_filename, $NO_DEL_SOURCE_FILE); if ($verify_mode) { $total_decrypted++ if $rv; } else { if (-e $decrypt_filename and -s $decrypt_filename != 0) { ### set the atime and mtime to be the same as the ### original file. unless ($no_fs_times) { if (defined $mtime and $mtime and defined $atime and $atime) { utime $atime, $mtime, $decrypt_filename; } } if ($wipe_mode and not $quiet) { print " Securely deleting file: $file\n"; } ### only delete the original encrypted ### file if the decrypted one exists &delete_file($filename); $total_decrypted++; } else { print "[-] Could not decrypt file: $file\n" unless $quiet; next FILE; } } } } } print "\n" unless $quiet; chdir $initial_dir or die "[*] Could not chdir: $initial_dir\n"; return; } sub get_files() { my $dir = shift; print "[+] Building file list...\n" unless $quiet; if ($norecurse) { opendir D, $dir or die "[*] Could not open $dir: $!"; my @files = readdir D; closedir D; for my $file (@files) { next if $file eq '.'; next if $file eq '..'; &check_file_criteria("$dir/$file"); } } else { ### get all files in all subdirectories find(\&find_files, $dir); } return; } sub exclude_file() { my $file = shift; for my $pat (@exclude_patterns) { if ($file =~ m|$pat|) { print "[+] Skipping $file (matches exclude pattern: $pat)\n" if $verbose and not $quiet; return 1; } } return 0; } sub include_file() { my $file = shift; for my $pat (@include_patterns) { if ($file =~ m|$pat|) { print "[+] Including $file (matches include pattern: $pat)\n" if $verbose and not $quiet; return 1; } } return 0; } sub obfuscated_mapping_files() { my $dirs_href = {}; if ($encrypt_mode) { $dirs_href = \%obfuscate_ctrs; } else { $dirs_href = \%obfuscated_dirs; } DIR: for my $dir (keys %$dirs_href) { unless (chdir($dir)) { print "[-] Could not chdir $dir, skipping.\n" unless $quiet; next DIR; } if ($encrypt_mode) { next DIR unless -e $obfuscate_map_filename; ### encrypt the map file now that we have encrypted ### the directory print "[+] Encrypting mapping file: ", "$dir/$obfuscate_map_filename\n" unless $quiet; unless ($trial_run) { &encrypt_or_sign_file($obfuscate_map_filename, "$obfuscate_map_filename.gpg", $NO_DEL_SOURCE_FILE); unlink $obfuscate_map_filename; } } else { next DIR unless -e "$obfuscate_map_filename.gpg"; ### delete the map file since we have decrypted ### the directory print "[+] Decrypting mapping file: ", "$dir/$obfuscate_map_filename.gpg\n" unless $quiet; unless ($trial_run) { &decrypt_or_verify_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); unlink "$obfuscate_map_filename.gpg"; if ($total_mapped_files == $total_decrypted) { ### we are confident that we decrypted all of them, ### so delete the mapping file. unlink $obfuscate_map_filename; } } } } return; } sub handle_old_obfuscated_map_file() { return unless -e "$obfuscate_map_filename.gpg"; &decrypt_or_verify_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); unlink "$obfuscate_map_filename.gpg"; my @existing_obfuscated_files = (); open F, '<', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; while () { if (/^\s*.*\s+(gpgdir_\d+_\d+\.gpg)/) { if (-e $1) { push @existing_obfuscated_files, $_; } } elsif (/^\s*.*\s+(gpgdir_\d+\.gpg)/) { if (-e $1) { push @existing_obfuscated_files, $_; } } } close F; if (@existing_obfuscated_files) { ### there are some obfuscated files from a previous gpgdir ### execution open G, '>', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; print G for @existing_obfuscated_files; close G; } return; } sub append_obfuscated_mapping() { my ($filename, $encrypt_filename) = @_; open G, '>>', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; print G "$filename $encrypt_filename\n"; close G; return; } sub import_obfuscated_file_map() { my $dir = shift; $obfuscated_dirs{$dir} = {}; return unless -e "$obfuscate_map_filename.gpg"; &decrypt_or_verify_file("$obfuscate_map_filename.gpg", $obfuscate_map_filename, $NO_DEL_SOURCE_FILE); open G, '<', $obfuscate_map_filename or die "[*] Could not open ", "$obfuscate_map_filename: $!"; while () { if (/^\s*(.*)\s+(gpgdir_\d+_\d+\.gpg)/) { $obfuscated_dirs{$dir}{$2} = $1; $total_mapped_files++; } elsif (/^\s*(.*)\s+(gpgdir_\d+\.gpg)/) { $obfuscated_dirs{$dir}{$2} = $1; $total_mapped_files++; } } close G; return; } sub get_homedir() { my $uid = $<; my $homedir = ''; if (-e '/etc/passwd') { open P, '<', '/etc/passwd' or die "[*] Could not open /etc/passwd. Exiting.\n"; my @lines =

; close P; for my $line (@lines) { ### mbr:x:222:222:Michael Rash:/home/mbr:/bin/bash chomp $line; if ($line =~ /^(?:.*:){2}$uid:(?:.*:){2}(\S+):/) { $homedir = $1; last; } } } else { $homedir = $ENV{'HOME'} if defined $ENV{'HOME'}; } die "[*] Could not determine home directory. Use the -u option." unless $homedir; return $homedir; } sub get_key() { if (-e "${homedir}/.gpgdirrc") { open F, '<', "$homedir/.gpgdirrc" or die "[*] Could not open ", "${homedir}/.gpgdirrc. Exiting.\n"; my @lines = ; close F; my $key = ''; for my $line (@lines) { chomp $line; if ($line =~ /^\s*default_key/) { ### prefer to use the default GnuPG key $use_default_key = 1; return ''; } elsif ($line =~ /^\s*use_key\s+(.*)$/) { ### GnuPG accepts strings to match the key, so we don't ### have to strictly require a key ID... just a string ### that matches the key return $1; } } die "[*] Please edit ${homedir}/.gpgdirrc to include your gpg key identifier\n", " (e.g. \"D4696445\"; see the output of \"gpg --list-keys\"), or use the\n", " default GnuPG key defined in ~/.gnupg/options"; } print "[+] Creating gpgdir rc file: $homedir/.gpgdirrc\n"; open F, '>', "$homedir/.gpgdirrc" or die "[*] Could not open " . "${homedir}/.gpgdirrc. Exiting.\n"; print F <<_CONFIGRC_; # Config file for gpgdir. # # Set the key to use to encrypt files with "use_key ", e.g. # "use_key D4696445". See "gpg --list-keys" for a list of keys on your # GnuPG key ring. Alternatively, if you want gpgdir to always use the # default key that is defined by the "default-key" variable in # ~/.gnupg/options, then uncomment the "default_key" line below. # Uncomment to use the GnuPG default key defined in ~/.gnupg/options: #default_key # If you want to use a specific GnuPG key, Uncomment the next line and # replace "KEYID" with your real key id: #use_key KEYID _CONFIGRC_ close F; die "[*] Please edit $homedir/.gpgdirrc to include your gpg key identifier,\n", " or use the default GnuPG key defined in ~/.gnupg/options. Exiting.\n"; } sub find_files() { my $file = $File::Find::name; &check_file_criteria($file); return; } sub check_file_criteria() { my $file = shift; ### skip all links, zero size files, all hidden ### files (includes the .gnupg directory), etc. return if -d $file; unless ($force_mode) { if ($file =~ m|/\.|) { print "[-] Skipping file: $file\n" if $verbose and not $quiet; return; } } if (-e $file and not -l $file and -s $file != 0 and $file !~ m|\.gpgdir\.pid| and $file !~ m|\.gnupg|) { if ($encrypt_mode or $signing_mode) { if ($file =~ m|\.gpg| or $file =~ m|\.asc| or $file =~ m|\.pgp|) { print "[-] Skipping encrypted/signed file: $file\n" unless $quiet; return; } } elsif ($verify_mode) { unless ($file =~ m|\.asc|) { ### only pick up the signature files return; } } else { unless ($file =~ m|\.gpg| or $file =~ m|\.asc| or $file =~ m|\.pgp|) { print "[-] Skipping unencrypted file: $file\n" unless $quiet; return; } } my ($atime, $mtime) = (stat($file))[8,9]; $files{$file}{'atime'} = $atime; $files{$file}{'mtime'} = $mtime; } else { print "[-] Skipping file: $file\n" if $verbose and not $quiet; } return; } sub get_password() { ### this is only useful if the gpg key literally has no password ### (usually this is not the case, but gpgdir will support it if ### so). return if $cmdline_no_password; ### if we are using gpg-agent for passwords, then return return if $use_gpg_agent; if ($pw_file) { open PW, '<', $pw_file or die "[*] Could not open $pw_file: $!"; $pw = ; close PW; chomp $pw; } else { print "[+] Executing: gpgdir @args_cp\n" unless $quiet; if ($symmetric_mode) { print " [Symmetric mode]\n" unless $quiet; } else { if ($use_default_key) { print " Using default GnuPG key.\n" unless $quiet; } else { print " Using GnuPG key: $encrypt_user\n" unless $quiet; } } if ($test_and_exit) { print " *** test_mode() ***\n" unless $quiet; } if ($signing_mode) { print " Enter signing password.\n" unless $quiet; } elsif ($encrypt_mode) { print ' Enter password (for initial ' . "encrypt/decrypt test)\n" unless $quiet; } my $msg = 'Password: '; ### get the password without echoing the chars back to the screen ReadMode('noecho'); while (not $pw) { print $msg; $pw = ReadLine(0); chomp $pw; } ReadMode('normal'); if ($quiet) { print "\n"; } else { print "\n\n"; } } return; } sub test_mode() { chdir $dir or die "[*] Could not chdir($dir): $!"; my $test_file = "gpgdir_test.$$"; print "[+] test_mode(): Encrypt/Decrypt test of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); if (-e $test_file) { &delete_file($test_file) or die "[*] test_mode(): Could not remove $test_file: $!"; } if (-e "$test_file.gpg") { &delete_file("$test_file.gpg") or die "[*] test_mode(): Could not remove $test_file.gpg: $!"; } open G, '>', $test_file or die "[*] test_mode(): Could not create $test_file: $!"; print G "gpgdir test\n"; close G; if (-e $test_file) { print "[+] test_mode(): Created $test_file\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Could not create $test_file\n"; } &encrypt_or_sign_file($test_file, "${test_file}.gpg", $DEL_SOURCE_FILE); if (-e "$test_file.gpg" and (-s $test_file != 0)) { print "[+] test_mode(): Successful encrypt of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); &delete_file($test_file) if -e $test_file; } else { die "[*] test_mode(): not encrypt $test_file (try adding -v).\n"; } &decrypt_or_verify_file("${test_file}.gpg", $test_file, $DEL_SOURCE_FILE); if (-e $test_file and (-s $test_file != 0)) { print "[+] test_mode(): Successful decrypt of $test_file\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Could not decrypt $test_file.gpg ", "(try adding -v).\n"; } open F, '<', $test_file or die "[*] test_mode(): Could not open $test_file: $!"; my $line = ; close F; if (defined $line and $line =~ /\S/) { chomp $line; if ($line eq 'gpgdir test') { print "[+] test_mode(): Decrypted content matches original.\n", "[+] test_mode(): Success!\n\n" if (($test_and_exit or $verbose) and not $quiet); } else { die "[*] test_mode(): Decrypted content does not match ", "original (try adding -v)."; } } else { die "[*] test_mode(): Fail (try adding -v).\n"; } &delete_file($test_file) if -e $test_file; &delete_file("$test_file.gpg") if -e "$test_file.gpg"; chdir $initial_dir or die "[*] Could not chdir($initial_dir)"; return 0; ### exit status } sub query_yes_no() { my ($msg, $style) = @_; my $ans = ''; while ($ans ne 'y' and $ans ne 'n') { print $msg; $ans = lc(); if ($style == $ACCEPT_YES_DEFAULT) { return 1 if $ans eq "\n"; } elsif ($style == $ACCEPT_NO_DEFAULT) { return 0 if $ans eq "\n"; } chomp $ans; } return 1 if $ans eq 'y'; return 0; } sub unique_pid() { return unless -e $pid_file; open P, '<', $pid_file or die "[*] Could not open $pid_file: $!"; my $pid =

; chomp $pid; close P; if (kill 0, $pid) { die "[*] Another gpgdir process (pid: $pid) is already ", "running against\n $dir"; } return; } sub write_pid() { open P, '>', $pid_file or die "[*] Could not open $pid_file: $!"; print P $$, "\n"; close P; return; } sub import_perl_modules() { my $mod_paths_ar = &get_mod_paths(); if ($#$mod_paths_ar > -1) { ### /usr/lib/gpgdir/ exists push @$mod_paths_ar, @INC; splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar; } if ($verbose) { print "[+] import_perl_modules(): The \@INC array:\n"; print "$_\n" for @INC; } require GnuPG::Interface; require Term::ReadKey; Term::ReadKey->import(qw/ReadMode ReadLine/); return; } sub get_mod_paths() { my @paths = (); unless (-d $lib_dir) { my $dir_tmp = $lib_dir; $dir_tmp =~ s|lib/|lib64/|; if (-d $dir_tmp) { $lib_dir = $dir_tmp; } else { return []; } } opendir D, $lib_dir or die "[*] Could not open $lib_dir: $!"; my @dirs = readdir D; closedir D; push @paths, $lib_dir; for my $dir (@dirs) { ### get directories like "/usr/lib/gpgdir/x86_64-linux" next unless -d "$lib_dir/$dir"; push @paths, "$lib_dir/$dir" if $dir =~ m|linux| or $dir =~ m|thread| or (-d "$lib_dir/$dir/auto"); } return \@paths; } sub usage_and_exit() { print <<_HELP_; gpgdir; Recursive direction encryption and decryption with GnuPG [+] Version: $version (file revision: $rev_num) By Michael Rash (mbr\@cipherdyne.org) URL: http://www.cipherdyne.org/gpgdir/ Usage: gpgdir -e|-d [options] Options: -e, --encrypt - Recursively encrypt all files in and all subdirectories. -d, --decrypt - Recursively decrypt all files in and all subdirectories. --sign - Recursively sign all files in and all subdirectories. --verify - Recursively verify all GnuPG signatures in . -K, --Key-id - Specify GnuPG key ID, or key-matching string. This overrides the use_key value in ~/.gpgdirrc -D, --Default-key - Use the key that GnuPG defines as the default (i.e. the key that is specified by the default-key option in ~/.gnupg/options). -a, --agent - Acquire password information from a running instance of gpg-agent. -A, --Agent-info - Specify the value for the GPG_AGENT_INFO environment variable as returned by 'gpg-agent --daemon'. -g, --gnupg-dir

- Specify a path to a .gnupg directory for gpg keys (the default is ~/.gnupg if this option is not used). -S, --Symmetric - Use symmetric encryption instead of the default asymmetric encryption. -p, --pw-file - Read password in from . --skip-test - Skip encrypt -> decrypt test. -t, --test-mode - Run encrypt -> decrypt test and exit. -T, --Trial-run - Show what filesystem actions would take place without actually doing them. -P, --Plain-ascii - Ascii armor mode (creates non-binary encrypted files). --Interactive - Query the user before encrypting, decrypting, or deleting any files. --Exclude - Skip all filenames that match . --Exclude-from - Skip all filenames that match any pattern contained within . --Include - Include only those filenames that match . --Include-from - Include only those filenames that match a pattern contained within . -O, --Obfuscate-filenames - Substitute all real filenames in a directory with manufactured ones (the original filenames are preserved in a mapping file and restored when the directory is decrypted). --obfuscate-map_file - Specify path to obfuscated mapping file (in -O mode). -F, --Force - Continue to run even if files cannot be deleted (because of permissions problems for example). --overwrite-encrypted - Overwrite encrypted files even if a previous .gpg file already exists. --overwrite-decrypted - Overwrite decrypted files even if the previous unencrypted file already exists. -q, --quiet - Print as little to the screen as possible -W, --Wipe - Use the 'wipe' command to securely delete unencrypted copies of files after they have been encrypted. --wipe-path - Specify path to the wipe command. --wipe-interactive - Force interactive mode with the wipe command. --wipe-cmdline - Manually specify command line arguments to the wipe command. --no-recurse - Don't recursively encrypt/decrypt subdirectories. --no-delete - Don't delete original unencrypted files. --no-preserve-times - Don't preserve original mtime and atime values on encrypted/decrypted files. --no-password - Assume the gpg key has no password at all (this is not common). -u, --user-homedir - Path to home directory. -l, --locale - Manually define a locale setting. --Lib-dir - Path to the perl modules directory (not usually necessary). --no-locale - Don't set the locale to anything (the default is the "C" locale). --verbose - Run in verbose mode. -V, --Version - print version. -h, --help - print help. _HELP_ exit 0; } ./gpgdir/ChangeLog.svn0000644000175000017500000000457713233575632014742 0ustar guilhemguilhem------------------------------------------------------------------------ r349 | mbr | 2009-09-05 14:52:04 -0400 (Sat, 05 Sep 2009) | 1 line Changed paths: A /gpgdir/branches/gpgdir-1.9.5 (from /gpgdir/trunk:348) recreated gpgdir-1.9.5 branch ------------------------------------------------------------------------ r347 | mbr | 2009-09-05 14:50:48 -0400 (Sat, 05 Sep 2009) | 10 lines Changed paths: A /gpgdir/trunk/packaging/gpgdir-nobuildreqs.spec M /gpgdir/trunk/packaging/gpgdir.spec For all RPM's built on the local system (Ubuntu for now), updated to reference the "-nobuildreqs.spec" file so that the "BuildRequires: perl-ExtUtils-MakeMaker" directive is not used. Using this results in the following error on an Ubuntu system where no software is installed/upgrade with RPM: rpm: To install rpm packages on Debian systems, use alien. See README.Debian. error: cannot open Packages index using db3 - No such file or directory (2) error: cannot open Packages database in /var/lib/rpm ------------------------------------------------------------------------ r344 | mbr | 2009-09-05 14:28:24 -0400 (Sat, 05 Sep 2009) | 1 line Changed paths: M /gpgdir/trunk/packaging/gpgdir-nodeps.spec M /gpgdir/trunk/packaging/gpgdir.spec updated to include 1.9.5 release ------------------------------------------------------------------------ r342 | mbr | 2009-09-05 14:24:38 -0400 (Sat, 05 Sep 2009) | 1 line Changed paths: M /gpgdir/trunk/ChangeLog updated 1.9.5 release date ------------------------------------------------------------------------ r341 | mbr | 2009-08-25 22:43:51 -0400 (Tue, 25 Aug 2009) | 1 line Changed paths: M /gpgdir/trunk/gpgdir M /gpgdir/trunk/test/gpgdir_test.pl minor copyright update ------------------------------------------------------------------------ r340 | mbr | 2009-08-23 14:32:29 -0400 (Sun, 23 Aug 2009) | 1 line Changed paths: M /gpgdir/trunk/VERSION M /gpgdir/trunk/gpgdir M /gpgdir/trunk/test/gpgdir_test.pl bumped version to 1.9.5 ------------------------------------------------------------------------ r339 | mbr | 2009-08-23 14:31:48 -0400 (Sun, 23 Aug 2009) | 3 lines Changed paths: M /gpgdir/trunk/ChangeLog M /gpgdir/trunk/gpgdir M /gpgdir/trunk/test/gpgdir_test.pl - Added support for the decryption of PGP encrypted files (to round out the support of GnuPG). ------------------------------------------------------------------------ ./gpgdir/INSTALL0000644000175000017500000000111613233575632013376 0ustar guilhemguilhemInstallation notes: QUICK AND EASY INSTALLATION INSTRUCTIONS: Just run the gpgdir installation script "install.pl" from the gpgdir sources directory: # ./install.pl If you are not installing as root, the install.pl script will install gpgdir along with required perl modules within your home directory. If you are installing as root, required perl modules will be installed in /usr/lib/gpgdir so as to not pollute the system perl library tree. The required perl modules are GnuPG::Interface, Class::MethodMaker, and Term::ReadKey, and these modules are placed in the deps/ directory. ./gpgsigs/0000755000175000017500000000000013233575735012541 5ustar guilhemguilhem./gpgsigs/gpgsigs-lt2k5.txt0000644000175000017500000000740313233575632015704 0ustar guilhemguilhemSaturday, June 25th, 2005; 14:00 Room R 2.05 Peter Palfrader ######## ######## ### ######## ######## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ######## ## ## ###### ## ## ## ## ## ######### ## ## ## ## ## ## ## ## ## ## ######## ## ## ## ## ## ## L I N U X T A G K E Y S I G N I N G P A R T Y List of Participants (v 0.0) Here's what you have to do with this file: (1) Print this file to paper. (2) Compute this file's MD5 checksum and optionally also its SHA1 checksum. gpg --print-md md5 ksp-lt2k5.txt (or use md5sum) gpg --print-md sha1 ksp-lt2k5.txt (or use sha1sum) (3) fill in the hash values on the printout. (4) Bring the printout, a pen, and proof of identity to the keysigningparty (and be on time!). MD5 Checksum: __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ __ [ ] SHA1 Checksum: ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ [ ] 001 [ ] Fingerprint OK [ ] ID OK pub 1024D/CD15A883 2002-09-28 Key fingerprint = 02DF 08F5 FD35 6BF2 7F5F 7B83 8921 B5DC CD15 A883 uid Alexander Schmehl (privat) uid Alexander Schmehl (private) uid Alexander Schmehl (knOEpix) uid Alexander Schmehl (Skolelinux) uid Alexander Schmehl (university) uid Alexander Schmehl (university) uid Alexander Schmehl (unused, but read) 002 [ ] Fingerprint OK [ ] ID OK pub 1024D/00D8CD16 2002-09-28 Key fingerprint = 46CD D292 0692 D5A2 8F81 2E48 0717 74E0 00D8 CD16 uid Alexander Schmehl (university) uid Alexander Schmehl (privat) uid Alexander Schmehl (university) 003 [ ] Fingerprint OK [ ] ID OK pub 1024R/6D8ABE71 1998-07-25 Key fingerprint = 09 9D 09 8F 89 52 24 12 FE C2 31 9D FE F8 5C 03 uid Christoph Berg uid Christoph Berg 004 [ ] Fingerprint OK [ ] ID OK pub 1024D/58510B5A 2004-04-17 Key fingerprint = D224 C8B0 7E63 A694 6DA3 2E07 C5AF 774A 5851 0B5A uid Christoph Berg 005 [ ] Fingerprint OK [ ] ID OK pub 1024D/514B3E7C 2003-07-19 Key fingerprint = 34F8 7997 8BC1 03F0 9C43 F3D7 B375 3E4D 514B 3E7C uid Florian Ernst uid Florian Ernst uid Florian Ernst uid Florian Ernst 006 [ ] Fingerprint OK [ ] ID OK pub 1024D/7E7B8AC9 2002-05-11 Key fingerprint = DF7D EB2F DB28 FD2B A9FB FA6D 715E D6A0 7E7B 8AC9 uid Joerg Jaspert uid Joerg Jaspert uid Joerg Jaspert uid Joerg Jaspert 007 [ ] Fingerprint OK [ ] ID OK pub 1024D/94C09C7F 1999-11-10 Key fingerprint = 5B00 C96D 5D54 AEE1 206B AF84 DE7A AF6E 94C0 9C7F uid Peter Palfrader uid Weasel uid Peter Palfrader uid Peter Palfrader uid Peter Palfrader 008 [ ] Fingerprint OK [ ] ID OK pub 4096R/C82E0039 2003-03-24 Key fingerprint = 25FC 1614 B8F8 7B52 FF2F 99B9 62AF 4031 C82E 0039 uid Peter Palfrader uid Peter Palfrader ./gpgsigs/gpgsigs0000755000175000017500000004463713233575735014150 0ustar guilhemguilhem#!/usr/bin/perl # $Id: gpgsigs 900 2018-01-29 10:33:26Z guilhem $ # See the pod documentation at the end of this file for author, # copyright, and licence information. # # 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: 900 $; use strict; use warnings; use Encode (); use I18N::Langinfo 'langinfo'; use English '-no_match_vars'; use IPC::Open3; use Getopt::Long; use File::Temp; use IO::Handle; use IO::Select; use GnuPG::Interface; sub version($) { my ($fd) = @_; print $fd < (c) 2004, 2005 Peter Palfrader (c) 2004, 2005, 2006, 2007 Christoph Berg (c) 2014, 2015 Guilhem Moulin EOF } sub usage($$) { my ($fd, $error) = @_; version($fd); print $fd <] [] keyid is a long or short keyid (e.g. DE7AAF6E94C09C7F or 94C09C7F) or a key fingerprint 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; } 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 //= langinfo(I18N::Langinfo::CODESET()); $charset //= langinfo(I18N::Langinfo::CODESET()); my $locale = Encode::find_encoding($charset); # parse options my @mykeys = split /,/, uc(shift @ARGV); my $keytxt = (shift @ARGV) || usage(*STDERR, 1); my $outfile = (shift @ARGV) || '-'; map { y/ //d if /^(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}$/; # remove spaces in fprs /^[0-9A-F]{40}$/ ? s/.{24}// : s/^0x//i; } @mykeys; if (!@mykeys || scalar @ARGV) { usage(*STDERR, 1); } foreach my $falsekey (grep { $_ !~ /^([0-9A-F]{16}|[0-9A-F]{8})$/ } @mykeys) { print STDERR "Invalid keyid $falsekey given\n"; usage(*STDERR, 1); } -r $keytxt or die ("$keytxt does not exist\n"); # get list of keys in file (from fingerprints if available) my (@keys, @shortkeys); open (TXT, '<', $keytxt) or die ("Cannot open $keytxt\n"); while () { if ( m/^pub {2,}[^ \/]+\/(?:0x)?([0-9A-F]{8}|[0-9A-F]{16}) [0-9]{4}-[0-9]{2}-[0-9]{2}/ ) { push @shortkeys, $1; } elsif ( m/^ {5,}(?:Key fingerprint = )?((?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}|[0-9A-F]{40})$/ ) { push @keys, substr ($1 =~ y/ //dr, -16); } } close TXT; @keys = @shortkeys unless @keys; # get all known signatures if ($recv_keys) { print STDERR "Requesting keys from keyserver\n"; system $ENV{GNUPGBIN} // 'gpg', '--recv-keys', @keys; } my $now = time; print STDERR "Running --list-sigs, this may take a while "; my $photos = $latex ? File::Temp::->new(TMPDIR => 1) : '/dev/null'; my $gpg = GnuPG::Interface::->new(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; # 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/ --list-options show-sig-subpackets --no-auto-check-trustdb --fixed-list-mode --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_sigs( handles => $handles, command_args => [ @mykeys, @keys ] ); $_->blocking(0) for ($stdout, $status); my $output = IO::Select::->new(); $output->add($stdout, $status); my (%keys, %uids, @photos, %sigs, %revs); my ($key, $uid, $sig); # 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; undef $sig unless /^spk:/; if (/^pub:([^:]+):(?:[^:]*:){2}([0-9A-F]{16}):(?:[^:]*:){6}([^:]+)/) { undef $uid; $key = $2; if ($1 =~ /[ir]/ or $3 =~ /D/ ) { warn "Ignoring unusable key $key.\n"; undef $key; last; } $keys{$key} = []; print STDERR '.'; @mykeys = map { substr($key,-8) eq $_ ? $key : $_ } @mykeys; next; } next unless $key; # nothing to do on revoked keys if (/^(uid|uat):([^:]+):(?:[^:]*:){5}([0-9A-F]{40}):[^:]*:([^:]+)/) { undef $uid; next if $2 =~ /[er]/; $uid = $3; # use the hash to have proper distinction between UATs if ($1 eq 'uid') { my $text = $4; $text =~ s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge; # --with-colons always outputs UTF-8 $uids{$key}->{$uid} = { type => 'uid', text => $locale->encode(Encode::decode_utf8($text)) }; } else { # we can't rely on $4 for the size: get it from # the status fd instead $uids{$key}->{$uid} = { type => 'uat' }; } push @{$keys{$key}}, $uid; # preserve order next; } next unless defined $uid; # nothing to do on revoked uids if (/^sig:(?:[^:]*:){3}([0-9A-F]{16}):(\d+):(\d*):(?:[^:]*:){3}(1[0-3][lx])(?::.*)?$/) { if (!grep { $1 =~ /$_$/ or $key =~ /$_$/ } @mykeys) { $sig = []; # $key is not ours, and the signer isn't us: don't waste resources } else { $sigs{$key}->{$uid}->{$1} //= []; $sig = $sigs{$key}->{$uid}->{$1}; push @$sig, { created => $2, expiring => $3, class => $4, revocable => 1 }; } next; } if (/^spk:7:1:1:%0([01])$/ and @$sig) { # mark the last sig as revocable (1) or not (0) $sig->[$#$sig]->{revocable} = $1; next; } if (/^rev:(?:[^:]*:){3}([0-9A-F]{16}):(\d+):(?:[^:]*:){4}30x(?::.*)?$/) { $revs{$key}->{$uid}->{$1} = $2 # keep only the most recent revocation cert unless defined $revs{$key}->{$uid}->{$1} and $revs{$key}->{$uid}->{$1} > $2; next; } if (/^sub:/) { undef $uid; next; } if (!/^(?:rvk|tru|fpr|spk):/) { # revoke/revoker/trust/fpr warn "Unknown value: '$_', key: ".($key // 'none')."\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/gnupg/DETAILS.gz if (/^\[GNUPG:\] ATTRIBUTE [0-9A-F]{24}([0-9A-F]{16}) (\d+) (\d+) (\d+) (\d+) \d+ \d+ (\d+)$/) { my $ignore = ($3 == 1 and $4 == 1 and $5 == 1) ? 0 : 1; push @photos, {key => $1, size => $2, revoked => $6 & 0x02, ignore => $ignore}; next; } if (!/^\[GNUPG:\] (?:KEYEXPIRED \d+|SIGEXPIRED(?: deprecated-use-keyexpired-instead)?|KEY_CONSIDERED [0-9A-F]{40} \d+)$/) { warn "Unknown value: '$_'"; } } } } } warn "Parsing gpg's output went wrong.\n" if $oldstdout or $oldstatus; waitpid $pid, 0; die if $?; close $_ for ($stdout, $status); my $photosfd; if ($latex) { open $photosfd, '<:raw', $photos or die "Couldn't open: $!"; } # get photo sizes and split $photos if $latex foreach my $photo (@photos) { my $chunk; if ($latex) { my $got = read $photosfd, $chunk, $photo->{size} or die "Couldn't read: $!"; warn "Read $photo->{size} bytes but got $got bytes.\n" if $got != $photo->{size}; } next if $photo->{revoked} or $photo->{ignore}; # ignore revoked attributes my $key = $photo->{key}; my @uats = grep { $uids{$key}->{$_}->{type} eq 'uat' } @{$keys{$key}}; my @found = grep { !defined $uids{$key}->{$_}->{text} } @uats; my $size = $photo->{size} - 16; # remove the header part unless (@found) { warn "No more UAT was found on $key, but there is an image of size $size that belongs to that key.\n"; next; } my $uat = $uids{$key}->{shift @found}; $uat->{text} = "[jpeg image of size $size]"; if ($latex) { $uat->{file} = "${key}_".($#uats - $#found).".jpg"; my $pid = open my $pic, '|-', 'convert', 'jpg:-', '-density', 90, $uat->{file} or die "Can't run convert(1)\n"; print $pic (substr $chunk, 16); close $pic; waitpid $pid, 0; warn "convert(1) exited with value ".($? >> 8)."\n" if $?; } } close $photosfd if $latex; # collapse sigs following RFC 4880 while (my ($key, $uids) = each %sigs) { while (my ($uid, $signers) = each %$uids) { while (my ($signer, $sigs) = each %$signers) { my $lastrev = $revs{$key}->{$uid}->{$signer}; my $class; my @sigs; # remove expired signatures @sigs = grep {!$_->{expiring} or $now < $_->{expiring}} @$sigs; $class = 'X' if !$class and !@sigs; # eXpired # remove revoked signatures (but keep signatures issued after the last revocation cert) @sigs = grep {!$_->{revocable} or $lastrev < $_->{created}} @sigs if $lastrev; $class = 'R' if !$class and !@sigs; # Revoked unless ($class) { # only non-expired, non-revoked sigs are left in @sigs. @sigs = grep { $_->{class} =~ /x$/ } @sigs; # grep for exportable sigs if (@sigs) { # we take the one with the best level @sigs = sort { $a->{class} cmp $b->{class} } @sigs; my $s = pop @sigs; if ($s->{expiring}) { $class = 'x'; } else { $class = $s->{class} =~ s/1([0-3])./$1/r; $class = 'S' if $class eq '0'; } } else { $class = 'L'; } } undef $sigs; $signers->{$signer} = $class; } } } sub getChecksum ($$) { my ($algo, $infile) = @_; my $pid = open MD, '-|', $ENV{GNUPGBIN} // 'gpg', qw/--with-colons --print-md/, $algo, $infile or warn "Can't get gpg $algo digest\n"; my $digest = ; waitpid $pid, 0; warn "gpg(1) exited with value ".($? >> 8)."\n" if $?; close MD; return $1 if $digest and $digest =~ /:([0-9A-F]+):[^:]*$/; } # write out result sub print_tag { my ($key, $uid) = @_; my $r = '('; $r .= $sigs{$key}->{$uid}->{$_} // ' ' for @mykeys; $r .= ')'; return $r; } $key = undef; $uid = undef; my $line = 0; my $keys = 0; print STDERR "\nAnnotating $keytxt, writing into $outfile\n"; open (TXT, '<', $keytxt) or die ("Cannot open $keytxt\n"); $outfile eq '-' ? *WRITE = *STDOUT : 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{ifluatex,ifxetex} \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 \usepackage[utf8x]{inputenc} \else \usepackage[log-declarations=false]{xparse} \usepackage{fontspec} \setmonofont{Noto Mono} \ifxetex \usepackage[quiet]{xeCJK} \CJKfontspec{Noto Sans Mono CJK TC} \CJKfontspec{Noto Sans Mono CJK SC} \CJKfontspec{Noto Sans Mono CJK JP} \CJKfontspec{Noto Sans Mono CJK KR} \fi \fi \begin{document} \begin{alltt} EOF } while () { $line++; if (/^(\S+) Checksum:/) { my $algo = uc $1; my $md = getChecksum($algo, $keytxt); if ($md) { my $r = $_; while ( /^(?:.*_)?$/ ) { $line++; $_ = ; $r .= $_; } my $n = $r =~ y/_//; my $k = $n / length($md); if (int($k) * length($md) != $n) { warn "Skipping $algo digest value (invalid fill-in length)\n"; } else { my $w = '_' x $k; $r =~ s/\Q$w\E/%c/g; $r = sprintf($r, unpack ("C*", $md)); } print WRITE $r; next; } } 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; next; } if ( m/^pub {2,}[^ \/]+\/(?:0x)?([0-9A-F]{8}|[0-9A-F]{16}) [0-9]{4}-[0-9]{2}-[0-9]{2}/ ) { $key = $1; print WRITE; next; } if ( m/^ {5,}(?:Key fingerprint = )?((?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}|[0-9A-F]{40})$/ ) { # derive the keyid from the fingerprint if available $key = substr $1 =~ y/ //dr, -16; print WRITE; my $inc = ""; foreach my $mykey (@mykeys) { foreach my $myuid (@{$keys{$mykey}}) { $inc .= $sigs{$mykey}->{$myuid}->{$key} // ' '; } } print WRITE "[$inc] incoming signatures\n" if $inc =~ /\S/; if ($refresh or $latex) { foreach my $uid (@{$keys{$key}}) { next unless defined $uids{$key}->{$uid}->{text}; my $tag = print_tag($key, $uid); if (!$latex) { print WRITE $tag .' '. $uids{$key}->{$uid}->{text},"\n"; } else { for (my $i = 0; $i < length $uids{$key}->{$uid}->{text}; $i+=78) { print WRITE ($i ? ' ' x length $tag : $tag ), ' ', substr ($uids{$key}->{$uid}->{text}, $i, 78), "\n"; } } if ($latex and $uids{$key}->{$uid}->{type} eq 'uat') { print WRITE "\\begin{flushright}\n"; print WRITE "\\includegraphics[height=3cm]{$uids{$key}->{$uid}->{file}}\n"; print WRITE "\\end{flushright}\n"; } } } next; } if ( m/^uid +(.*)$/ ) { next if $refresh or $latex; my $uid = $locale->encode( Encode::decode($fromcharset, $1) ); unless (defined $key) { warn "key is undefined - input text is possibly malformed near line $line\n"; next; }; my @h = grep { $uids{$key}->{$_}->{text} eq $uid } @{$keys{$key}}; if (@h) { # if there are multiple matches we can't distinguish them print WRITE print_tag($key, pop @h)." $uid\n"; } else { warn "uid '$uid' not found on key $key\n"; print WRITE "(" . (' ' x @mykeys) . ") $uid\n"; } next; } if ( /^(?:-+|_+)$/ and $latex ) { $_ = "\n\\hrule\n"; } print WRITE; } close TXT; if ($latex and $keys > 0) { print WRITE "\\end{samepage}\n"; } print WRITE "Legend:\n"; my $num_myuids = 0; foreach my $i (0 .. $#mykeys) { print WRITE ' (' . ' 'x$i . 'S' . ' 'x(@mykeys-$i-1) . ") signed with $mykeys[$i] $uids{$mykeys[$i]}->{$keys{$mykeys[$i]}->[0]}->{text}\n"; $num_myuids += @{$keys{$mykeys[$i]}}; } my $i = 0; foreach my $mykey (@mykeys) { foreach my $myuid (@{$keys{$mykey}}) { print WRITE " [" . ' 'x$i . 'S' . ' 'x($num_myuids-$i-1) . "] has signed $mykey $uids{$mykey}->{$myuid}->{text}\n"; $i++; } } print WRITE <<'EOF'; Signature types: R Revoked signature X Expired signature L Local (non-exportable) signature x Exportable signature with an expiration date in the future S Non-expiring, exportable signature with certification level 0 1 Non-expiring, exportable signature with certification level 1 2 Non-expiring, exportable signature with certification level 2 3 Non-expiring, exportable signature with certification level 3 EOF if ($latex) { print WRITE <<'EOF'; \end{alltt} \end{document} EOF } 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 jpg files to the current directory. =item I Use this keyid (8 or 16 bytes, or full fingerprint) 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 ENVIRONMENT =over =item I The default home directory. =item I The gpg binary. Default: C<"gpg">. =item I The default working directory for gpg. Default: C<$HOME/.gnupg>. =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.4.18 and gpg 2.0.26. YMMV. =head1 SEE ALSO gpg(1), caff(1). https://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 (c) 2014, 2015 Guilhem Moulin =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. ./gpgsigs/Makefile0000644000175000017500000000045013233575632014174 0ustar guilhemguilhemall: gpgsigs.1 gpgsigs.1: gpgsigs pod2man $< > $@ install: install -D gpgsigs $(DESTDIR)/usr/bin/gpgsigs install -d $(DESTDIR)/usr/share/doc/signing-party/examples/gpgsigs install -m644 gpgsigs-lt2k5*.txt \ $(DESTDIR)/usr/share/doc/signing-party/examples/gpgsigs clean: rm -f gpgsigs.1 ./gpgsigs/gpgsigs-lt2k5-annotated.txt0000644000175000017500000000770713233575632017666 0ustar guilhemguilhemSaturday, June 25th, 2005; 14:00 Room R 2.05 Peter Palfrader ######## ######## ### ######## ######## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ######## ## ## ###### ## ## ## ## ## ######### ## ## ## ## ## ## ## ## ## ## ######## ## ## ## ## ## ## L I N U X T A G K E Y S I G N I N G P A R T Y List of Participants (v 0.0) Here's what you have to do with this file: (1) Print this file to paper. (2) Compute this file's MD5 checksum and optionally also its SHA1 checksum. gpg --print-md md5 ksp-lt2k5.txt (or use md5sum) gpg --print-md sha1 ksp-lt2k5.txt (or use sha1sum) (3) fill in the hash values on the printout. (4) Bring the printout, a pen, and proof of identity to the keysigningparty (and be on time!). MD5 Checksum: 37 90 98 40 22 7D 68 90 1E B1 1C 1B FF 7C 0A 49 [ ] SHA1 Checksum: 4A01 4EC9 1043 8C39 7F5F 4CA8 FC51 AC99 16F8 2FE9 [ ] 001 [ ] Fingerprint OK [ ] ID OK pub 1024D/CD15A883 2002-09-28 Key fingerprint = 02DF 08F5 FD35 6BF2 7F5F 7B83 8921 B5DC CD15 A883 (S ) uid Alexander Schmehl (privat) (S ) uid Alexander Schmehl (private) ( ) uid Alexander Schmehl (knOEpix) ( ) uid Alexander Schmehl (Skolelinux) (S ) uid Alexander Schmehl (university) (S ) uid Alexander Schmehl (university) (S ) uid Alexander Schmehl (unused, but read) 002 [ ] Fingerprint OK [ ] ID OK pub 1024D/00D8CD16 2002-09-28 Key fingerprint = 46CD D292 0692 D5A2 8F81 2E48 0717 74E0 00D8 CD16 (SS) uid Alexander Schmehl (university) (SS) uid Alexander Schmehl (privat) (SS) uid Alexander Schmehl (university) 003 [ ] Fingerprint OK [ ] ID OK pub 1024R/6D8ABE71 1998-07-25 Key fingerprint = 09 9D 09 8F 89 52 24 12 FE C2 31 9D FE F8 5C 03 (SS) uid Christoph Berg (SS) uid Christoph Berg 004 [ ] Fingerprint OK [ ] ID OK pub 1024D/58510B5A 2004-04-17 Key fingerprint = D224 C8B0 7E63 A694 6DA3 2E07 C5AF 774A 5851 0B5A (SS) uid Christoph Berg 005 [ ] Fingerprint OK [ ] ID OK pub 1024D/514B3E7C 2003-07-19 Key fingerprint = 34F8 7997 8BC1 03F0 9C43 F3D7 B375 3E4D 514B 3E7C (SS) uid Florian Ernst ( ) uid Florian Ernst (SS) uid Florian Ernst (SS) uid Florian Ernst 006 [ ] Fingerprint OK [ ] ID OK pub 1024D/7E7B8AC9 2002-05-11 Key fingerprint = DF7D EB2F DB28 FD2B A9FB FA6D 715E D6A0 7E7B 8AC9 (SS) uid Joerg Jaspert (SS) uid Joerg Jaspert (SS) uid Joerg Jaspert (SS) uid Joerg Jaspert 007 [ ] Fingerprint OK [ ] ID OK pub 1024D/94C09C7F 1999-11-10 Key fingerprint = 5B00 C96D 5D54 AEE1 206B AF84 DE7A AF6E 94C0 9C7F ( ) uid Peter Palfrader (SS) uid Weasel (SS) uid Peter Palfrader (SS) uid Peter Palfrader (SS) uid Peter Palfrader 008 [ ] Fingerprint OK [ ] ID OK pub 4096R/C82E0039 2003-03-24 Key fingerprint = 25FC 1614 B8F8 7B52 FF2F 99B9 62AF 4031 C82E 0039 ( ) uid Peter Palfrader ( ) uid Peter Palfrader Legend: (S ) signed with 6D8ABE71 ( S) signed with 58510B5A ./keylookup/0000755000175000017500000000000013233575735013120 5ustar guilhemguilhem./keylookup/NEWS0000644000175000017500000000127613233575735013625 0ustar guilhemguilhem$Id: NEWS 185 2005-08-16 14:45:08Z weasel $ Release notes for keylookup 3.0 (2005-08-16) * Make keylookup use gpg --search instead of querying the keyserver directly. * Sort keys by creation date. Release notes for keylookup 2.2 (2002-09-24) * Some keyservers have started putting the key type (DSA or RSA) in their replies to searches. Keylookup now handles that. * Untaint keyIDs before passing them to GnuPG as passing tainted argumentes in exec() is now deprecated. (closes: DebianBug #161392) Release notes for keylookup 2.1 (2002-06-17) * Support newstyle gnupg config file (honor-proxy); (closes: SavannahBug #523) * Rewrote TODO in English; (closes: DebianBug #120861). ./keylookup/COPYING0000644000175000017500000004311113233575632014147 0ustar guilhemguilhem GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ./keylookup/keylookup0000755000175000017500000002333113233575735015072 0ustar guilhemguilhem#!/usr/bin/perl -w # Copyright (c) 2000, 2002 Christian Kurz , # Copyright (c) 2000, 2002, 2005 Peter Palfrader # # $Id: keylookup 839 2016-02-18 22:33:34Z Guilhem Moulin $ # # 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 2, 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, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # # Keylookup is part of pgp-tools: # https://pgp-tools.alioth.debian.org/ # svn://anonscm.debian.org/pgp-tools/trunk # https://anonscm.debian.org/viewvc/pgp-tools/trunk/ delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; $|=1; # Always flush buffers use strict; use IO::Socket; use IPC::Open3; use Getopt::Long; my $version = '3.0 ($Id: keylookup 839 2016-02-18 22:33:34Z Guilhem Moulin $)'; # Strings to use in the dialog|whiptail frontend my $TITLE = 'Import Keys'; my $BACKTITLE = 'KeyLookup $Revision: 839 $'; my $INSTRUCTION = 'Select keys to import:'; # my @TPUTCOL=('tput', 'cols'); my @TPUTROW=('tput', 'lines'); my $DEFAULTCOLS = 80; my $DEFAULTROWS = 25; # Size of the dialog boxes, will be set in calcDialogSize; my $MAX_UID_FIELD_LEN; my @DIALOGSIZE; my @WHIPTAILSIZE; # Was the keyserver overriden|given on the command line? # This is used to find out wheter we need to instruct the user # to give the keyserver option to GnuPG. my $keyserverWasSetOnCmdLine = 0; # Maps algorithm numbers to algorithm types as defined in RFC 2400. my %ALGOS = ( 1 => 'R', # RSA 2 => 'r', # RSA encrypt only (deprecated) 3 => 's', # RSA sign only (deprecated) 16 => 'g', # ElGamal encrypt only 20 => 'g', # ElGamal sign and encrypt (all OpenPGP implementations cryptographically broken, do not use. no longer part of OpenPGP) 17 => 'D' # DSA ); # getHits receives all options as a parameter, calls fetchIT to # query a keyserver, processes the output from the keyserver and # stores it in a datastructure for later use. sub getHits($) { my $options = shift; my $pid = open(KID, '-|'); defined ($pid) or die ("Cannot fork: $!\n"); unless ($pid) { close (STDIN); open (STDIN, "/dev/null") || die ("Cannot open /dev/null as stdin: $!\n"); # child my @ops = ($ENV{GNUPGBIN} // 'gpg'); if ($options->{'keyserver'}) { push @ops, '--keyserver='.$options->{'keyserver'}; }; push @ops, '--command-fd=0'; push @ops, '--batch'; push @ops, '--no-tty'; push @ops, '--with-colons'; push @ops, '--fixed-list-mode'; push @ops, '--search'; push @ops, @{$options->{'search'}}; exec(@ops); die ("Cannot exec GnuPG: $!\n"); }; my %keys; my $currentKey; while () { chomp; if ( $_ eq "" ) { next; } my ($type, undef) = split /:/; if ($type eq 'pub') { my ($type, $keyid, $algo, $bits, $created, undef, $revoked) = split /:/; $currentKey = { 'bits' => $bits, 'type' => (defined $ALGOS{$algo} ? $ALGOS{$algo} : '#'.$algo), 'keyid' => $keyid, 'created' => $created, 'revoked' => $revoked, 'uid' => [] }; $keys{ $keyid } = $currentKey; } elsif (defined $currentKey && $type eq 'uid') { my ($type, $name) = split /:/; if ($currentKey->{'revoked'} eq 'r') { $name .= ' [REVOKED]'; }; push @{ $currentKey->{'uid'} }, $name; }; }; close KID; waitpid $pid, 0; return \%keys; }; # returns the number of columns of the terminal sub getCols { my $pid; return $DEFAULTCOLS unless (defined ($pid = open(KID, "-|"))); unless ($pid) { exec (@TPUTCOL); }; my $cols = ; close KID; wait; return (defined $cols) ? $cols : $DEFAULTCOLS; }; # returns the number of lines of the terminal sub getRows { my $pid; return $DEFAULTROWS unless (defined ($pid = open(KID, "-|"))); unless ($pid) { exec (@TPUTROW); }; my $rows = ; close KID; wait; return (defined $rows) ? $rows : $DEFAULTROWS; }; # sets MAX_UID_FIELD_LEN, DIALOGSIZE, and WHIPTAILSIZE sub calcDialogSize { my $COLS = &getCols(); my $ROWS = &getRows(); $MAX_UID_FIELD_LEN = $COLS - 27; @DIALOGSIZE = ($ROWS-7, $COLS-7, $ROWS-14); @WHIPTAILSIZE = ($ROWS-7, $COLS-7, $ROWS-14); } sub prepareForDialog { my $keys = shift; my @keyargs = (); for my $keyid (sort {- ($keys->{$a}->{'created'} <=> $keys->{$b}->{'created'})} keys %$keys) { for (@{ $keys->{$keyid}->{'uid'} }) { push @keyargs, $keys->{$keyid}->{'keyid'}, length() <= $MAX_UID_FIELD_LEN ? $_ : substr($_, 0, $MAX_UID_FIELD_LEN-2) . '..', 'off'; }; my (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime ($keys->{$keyid}->{'created'}); push @keyargs, $keys->{$keyid}->{'keyid'}, sprintf( "[created: %s-%s-%s]", $year+1900, $mon+1, $mday ), 'off'; push @keyargs, '-'x8, '-'x40, 'off'; }; pop @keyargs; pop @keyargs; pop @keyargs; return \@keyargs; }; sub prepareForTXT { my $keys = shift; my @lines = (); for my $keyid (sort {- ($keys->{$a}->{'created'} <=> $keys->{$b}->{'created'})} keys %$keys) { my (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime ($keys->{$keyid}->{'created'}); push @lines, sprintf( "%s%s/%s %s-%s-%s\n", $keys->{$keyid}->{'bits'}, $keys->{$keyid}->{'type'}, $keys->{$keyid}->{'keyid'}, $year+1900, $mon+1, $mday ); push @lines, map { ' 'x26 . $_ . "\n" } @{ $keys->{$keyid}->{'uid'} }; push @lines, "\n"; }; return \@lines; }; sub callDialog { my $args = shift; # open(SAVEOUT, ">&STDOUT") || die ("Cannot save STDOUT: $!\n"); # open(SAVEIN , "<&STDIN" ) || die ("Cannot save STDIN: $!\n"); my $pid = open3( '<&STDIN', '>&STDOUT', \*ERRFH, @$args); my %unique; my @keys = grep { !$unique{$_}++ } # get the keyID; can be 8, 16 or 40 nibbles grep /^((([a-zA-Z0-9]{24})?[a-zA-Z0-9]{8})?[a-zA-Z0-9]{8})$/, map { s/\s//g; $_ } ; wait; # open(STDOUT, ">&SAVEOUT") || die "Cannot restore STDOUT: $!\n"; # open(STDIN , "<&SAVEIN") || die "Cannot restore STDIN: $!\n"; return \@keys; }; sub selectKeys { my $keys = shift; my $options = shift; my $frontend = $options->{'frontend'}; $frontend = 'dialog' unless (defined $frontend); if ($frontend eq 'dialog') { unless (`which dialog` && $? == 0) { warn("Dialog not executeable/installed. Falling back to Whiptail\n"); $frontend = 'whiptail'; } }; if ($frontend eq 'whiptail') { unless (`which whiptail` && $? == 0 ) { warn("Whiptail not executeable/installed. Falling back to plain\n"); $frontend = 'plain'; } }; if ( $frontend eq 'dialog' ) { calcDialogSize; my @ARGS = ( 'dialog', '--backtitle', $BACKTITLE, '--separate-output', '--title', $TITLE, '--checklist', $INSTRUCTION, @DIALOGSIZE); push @ARGS, @{&prepareForDialog($keys)}; return &callDialog( \@ARGS ); } elsif ( $frontend eq 'whiptail' ) { calcDialogSize; my @ARGS = ( 'whiptail', '--backtitle', $BACKTITLE, '--separate-output', '--title', $TITLE, '--checklist', $INSTRUCTION, @WHIPTAILSIZE, '--'); push @ARGS, @{&prepareForDialog($keys)}; return &callDialog( \@ARGS ); } else { print for (@{ &prepareForTXT( $keys ) }); if ($keyserverWasSetOnCmdLine) { printf ("Now run gpg --keyserver %s --recv-keys \n", $options->{'keyserver'}); } else { print ("Now run gpg --recv-keys \n"); }; ## If no frontend was selected, or selected frontend was plain, ## exit successfully, otherwise with an exitcode != 0 exit (defined $options->{'frontend'} && $options->{'frontend'} ne "" && $options->{'frontend'} ne "plain"); }; }; sub importKeys { my $keyids = shift; my $options = shift; my @args = ($ENV{GNUPGBIN} // 'gpg'); if ($options->{'keyserver'}) { push @args, '--keyserver='.$options->{'keyserver'}; }; push @args, '--recv-keys'; for my $keyid (@$keyids) { # untaint keyids my ($cleanid) = $keyid =~ /^((([a-zA-Z0-9]{24})?[a-zA-Z0-9]{8})?[a-zA-Z0-9]{8})$/; warn ("keyid '$keyid' has unexpected format - skipping\n"), next unless defined $cleanid; push @args, $cleanid; } print "Calling GnuPG...\n"; exec (@args) || die "can't exec gnupg: $!\n"; # won't return }; sub usage { my $errorcode = shift; print << 'EOF' Syntax: keylookup [options] Options: --keyserver= Select keyserver --frontend= One of whiptail, dialog or plain --importall Import all matched keys --help print this message EOF ; exit($errorcode); }; sub version { print "keylookup $version\nWritten by Christian Kurz and Peter Palfrader.\n"; exit(0); }; my %options; GetOptions( \%options, 'keyserver=s', 'frontend=s', 'importall', 'version', 'help') or &usage(1); &version(0) if ($options{'version'}); &usage(0) if ($options{'help'} || ( scalar(@ARGV) == 0)); ## Take all additional arguments to the program as a search target, ## escape the string for use in URLs. $options{'search'} = \@ARGV; my $keys = getHits( \%options ); my $keyids; if (scalar keys %$keys == 0) { print "GnuPG did not find any keys matching your search string.\n"; exit 0; }; if ($options{'importall'}) { my @allkeys = keys %$keys; $keyids = \@allkeys; } else { $keyids = selectKeys($keys, \%options); # won't return if no interactive frontend }; &importKeys($keyids, \%options) if (scalar @$keyids); # won't return ./keylookup/keylookup.10000644000175000017500000000631313233575735015227 0ustar guilhemguilhem.TH keylookup 1 "" Jun-2002 "" .\" manual page (c) 2000, 2001, 2002 Christian Kurz, Peter Palfrader .\" $Id: keylookup.1 839 2016-02-18 22:33:34Z Guilhem Moulin $ .SH NAME .LP keylookup - Fetch and Import GnuPG keys from keyservers. .SH SYNOPSIS \fBkeylookup\fP [\fIoptions\fP] \fIsearch-string\fP .SH DESCRIPTION .LP \fBkeylookup\fR is a wrapper around gpg \-\-search, allowing you to search for keys on a keyserver. It presents the list of matching keys to the user and allows her to select the keys for importing into the GnuPG keyring. For the search and actual import of keys GnuPG itself is called. .SH OPTIONS .IP "\fB\-\-keyserver\fP=\fIkeyserver\fP" 8 Specify the keyserver to use. If no keyserver is specified, it will parse the GnuPG options file for a default keyserver to use. If no keyserver can be found, \fBkeylookup\fP will abort. .IP "\fB\-\-port\fP=\fIport\fP" 8 Use a port other than 11371. .IP "\fB\-\-frontend\fP=\fIfrontend\fP" 8 \fBkeylookup\fP supports displaying the search results with 3 different frondends. Both \fBwhiptail\fP and \fBdialog\fP are interactive and allow the user to select the keys to import. The third frontend \fBplain\fP is non\-interactive and just prints the keys to STDOUT. The user must then call GnuPG him/herself. If available, \fB/usr/bin/dialog\fP is the default. If it is not available but \fB/usr/bin/whiptail\fP is installed, then this is used instead. If nothing else works, we'll fall back to \fBplain\fP. .IP "\fB\-\-importall\fP" 8 Don't ask the user which keys to import, but instead import all keys matching the \fIsearch-string\fP. If this is given no frontend is needed. .IP "\fB\-\-honor\-http\-proxy\fP" 8 Similar to GnuP \fBkeylookup\fP will only honor the \fBhttp_proxy\fP environment variable if this option is given. If it is not given but your GnuPG options file includes it, then \fBkeylookup\fP will use it. .IP "\fB\-\-help\fP" 8 Print a brief help message and exit successfully. .SH ENVIRONMENT .IP "HOME" 10 Used to locate the default home directory. .IP "GNUPGHOME" 10 If set directory used instead of "~/.gnupg". .IP "GNUPGBIN" 10 If set used as gpg binary instead of "gpg". .IP "http_proxy" 10 Only honored when the option \-\-honor\-http\-proxy is set or honor\-http\-proxy is set in GnuPG's config file. .SH EXAMPLES .IP "keylookup Christian Kurz" will query your default keyserver for Christian's keys and offer you to import them into your keyring with the dialog frontend (if available). .IP "keylookup \-\-honor\-http\-proxy \-\-frontend plain wk@gnupg" will query the default keyserver again, now using the http_proxy if the environment variable is defined and list wk@gnupg's (Werner Koch)'s key on STDOUT. .IP "keylookup \-\-keyserver pgp.mit.edu Peter Palfrader" will now ask the keyserver pgp.mit.edu for my (Peter's) keys and display them for import in dialog. .SH FILES .IP "~/.gnupg/options" 10 GnuPG's options file where \fBkeylookup\fP will take the keyserver and honor\-http\-proxy values from if it exists. .SH "SEE ALSO" \fBgpg\fP(1) .SH BUGS .LP Please report bugs using the Debian bug tracking system at https://bugs.debian.org/. .SH AUTHORS .LP Christian Kurz .br Peter Palfrader ./keylookup/Makefile0000644000175000017500000000034313233575632014554 0ustar guilhemguilhemall: install: install -D keylookup $(DESTDIR)/usr/bin/keylookup install -D -m644 keylookup.1 $(DESTDIR)/usr/share/man/man1/keylookup.1 install -D -m644 NEWS \ $(DESTDIR)/usr/share/doc/signing-party/NEWS.keylookup clean: ./Makefile0000644000175000017500000000301713233575632012533 0ustar guilhemguilhem-include /usr/share/dpkg/pkg-info.mk DIRS=caff gpg-key2ps gpg-mailkeys gpgsigs gpglist gpgparticipants keyanalyze keylookup \ sig2dot springgraph gpgwrap gpgdir keyart gpg-key2latex TGZ=../signing-party_$(DEB_VERSION_UPSREAM).orig.tar.gz TGZ_DIR=signing-party-$(DEB_VERSION_UPSREAM) all: for dir in $(DIRS) ; do if [ -f $$dir/Makefile ] ; then $(MAKE) -C $$dir || exit 1 ; fi ; done install: for dir in $(DIRS) ; do if [ -f $$dir/Makefile ] ; then $(MAKE) -C $$dir install || exit 1 ; fi ; done clean: for dir in $(DIRS) ; do if [ -f $$dir/Makefile ] ; then $(MAKE) -C $$dir clean || exit 1 ; fi ; done dist: [ -d debian ] && fakeroot debian/rules clean [ ! -f $(TGZ) ] mkdir $(TGZ_DIR) for dir in $(DIRS) ; do cp -a $$dir $(TGZ_DIR); done cp -a README Makefile $(TGZ_DIR) GZIP=--best tar cvz -f $(TGZ) --exclude .svn $(TGZ_DIR) rm -rf $(TGZ_DIR) tag-release: if svn ls svn+ssh://svn.debian.org/svn/pgp-tools/tags/release-$(DEB_VERSION_UPSREAM) >/dev/null 2>&1; then \ echo "Already exists." >&2; exit 1; \ fi svn cp -m 'tagging release $(DEB_VERSION_UPSREAM)' svn+ssh://svn.debian.org/svn/pgp-tools/trunk svn+ssh://svn.debian.org/svn/pgp-tools/tags/release-$(DEB_VERSION_UPSREAM) tag-debian-version: if svn ls svn+ssh://svn.debian.org/svn/pgp-tools/tags/debian-version-$(DEB_VERSION) >/dev/null 2>&1; then \ echo "Already exists." >&2; exit 1; \ fi svn cp -m 'tagging debian version $(DEB_VERSION)' svn+ssh://svn.debian.org/svn/pgp-tools/trunk svn+ssh://svn.debian.org/svn/pgp-tools/tags/debian-version-$(DEB_VERSION) ./keyanalyze/0000755000175000017500000000000013233575632013246 5ustar guilhemguilhem./keyanalyze/scripts/0000755000175000017500000000000013233575632014735 5ustar guilhemguilhem./keyanalyze/scripts/report_top.php0000644000175000017500000000553013233575632017646 0ustar guilhemguilhem

[Back to Keyring Analysis Page]

Key Analysis 10 Aug 2001

The following stats are being pulled from a keyring that was exported from pgp.dtype.org on August 9, 2001. Before reading this, please be sure to view the explanation of this analysis and read the FAQ before asking me any questions about it.

The strong set MSD raw analysis is available here. Please read the FAQ to explain how to read this file. This file includes all keys reachable from the strong set. Look up reports for individual keys in the raw output directory. Here you can also see what keys are signed by each key (otherwise very difficult to find).

New This Month

General statistics
Size of binary keyring (bytes): 1,863,975,684(+0.56%)
Number of keys: 1,583,621(+0.37%)
Non-revoked keys with at least one non-self sig: 148,845(+0.77%)
Total non-self sigs on those keys: 306,035 302,662(+1.10%)

The "strong set"
Size of largest strongly connected set: 10,153(+6.18%)
Keys that have signed this set: 14,811(+6.04%)
Keys that this set has signed (target of MSD calculations): 40,249(+5.27%)

Best connected keys (shortest distance to)

Please read about the mean shortest distance (MSD) calculated here in the analysis explanation. Here are the top 50 keys. Look for your own key in this month's raw analysis (see above). Note that the only keys analyzed were those reachable from the strong set. I've included some of my own comments on people I recognize. I'm sorry if you're listed here without a comment. If you email me a quick phrase to describe what you do that would be of interest to readers, I'll put it in.

The average MSD is 6.6224, in the set of 10,153. The median value is 6.1993.

Go to this keyserver's web interface to look up these keys.

./keyanalyze/scripts/top50.pl0000755000175000017500000000474213233575632016253 0ustar guilhemguilhem#!/usr/bin/perl -w # this short script is for making the HTML for the top50 report monthly # Copyright (c)2001 M. Drew Streib # This code is released under the GPL version 2 or later. # 2004-09-14: modifications by Christoph Berg : # * use perl to read top50comments.txt # * use gpg --list-key instead of wget # * use strict & warnings # 2008-07-18: modifications by Christoph Berg : # * directly read msd.txt instead of a -sorted variant use strict; use Getopt::Std; #my $keyserver = "http://pks.gpg.cz:11371/pks/lookup?op=vindex&fingerprint=on&search=0x"; #my $keyserver = "http://keyserver.noreply.org/pks/lookup?op=index&fingerprint=on&search=0x"; my $keyserver = "http://pool.sks-keyservers.net:11371/pks/lookup?op=index&fingerprint=on&search=0x"; my %options; getopts('c:k:n:', \%options); my $comments = $options{c} || "top50comments.txt"; my $keyring = $options{k} ? "--no-default-keyring --keyring=$options{k}" : ""; my $top = $options{n} || 50; my %comment; if (open F, $comments) { while() { die "$comments.$.: syntax error" unless /([\dA-F]+)\b ?(.*)/; $comment{$1} = $2; } close F; } my %msd; while (my $line = <>) { $line =~ /^\w+\s+(\w+)\s+([\d\.]+)/ or die "cannot parse line $.: $line"; $msd{$1} = $2; } print "
RankHex ID (last 32b) Key Name (Identifier)Comments MSD
\n"; print "\n"; my $oldmsd = 0; my $i = 1; foreach my $key (sort { $msd{$a} <=> $msd{$b} } keys %msd) { my $rank = ""; if($oldmsd != $msd{$key}) { $rank = $i++; } last if $rank and $rank > $top; $oldmsd = $msd{$key}; my $name = ""; open G, '-|', qw/gpg --list-key --fixed-list-mode --with-colon --trust-model always/, $keyring, $key or die "gpg: $!"; while() { #uid:u::::1082202576::1DC0BEA2AC64671CC902D50B8121F6E4E6336E15::Christoph Berg : next unless /^uid:[-qmfue]::::\d*::[\dA-F]*::(.+):$/; $name = $1; $name =~ s//>/g; $name =~ s/\@/@/g; last; } close G; my $comment = $comment{$key} || ""; $key =~ /^([\dA-F]{2})/; #my $prefix = $1; #print "\n"; print "\n"; } print "
#IdNameMSD
$rank$key keyserver$name$comment$msd
$rank$keykeyserver$name $comment$msd{$key}
\n"; ./keyanalyze/scripts/1000_bottom.php0000644000175000017500000000004313233575632017407 0ustar guilhemguilhem ./keyanalyze/scripts/htmlify_report0000755000175000017500000000220013233575632017724 0ustar guilhemguilhem#!/usr/bin/perl -w use strict; use Getopt::Std; my %options; getopts('k:', \%options); my $keyring = $options{k} ? "--no-default-keyring --keyring=$options{k}" : ""; my %UID; sub get_uid { my $key = shift; return $UID{$key} if $UID{$key}; open G, '-|', qw/gpg --list-key --fixed-list-mode --with-colon/, $keyring, $key or die "gpg: $!"; while() { next unless /^uid:[-qmfue]::::\d*::[\dA-F]*::(.+):$/; my $name = $1; $name =~ s//>/g; $name =~ s/\@/@/g; close G; return $UID{$key} = $name; } close G; } sub uid_link { my $key = shift; #$key =~ /^([\dA-F]{2})/; #return "$key"; return "$key"; } for my $file (@ARGV) { #print STDERR "$file...\n"; open F, "$file" or die "$file: $!"; open H, ">$file.html" or die "$file.html: $!"; print H < $file

EOF
	while() {
		next if /^(This individual|report at)/;
		s/([\dA-F]{8})$/uid_link($1)." ".get_uid($1);/e;
		print H;
	}
	print H "
\n\n"; } ./keyanalyze/scripts/top50comments.txt0000644000175000017500000000321113233575632020210 0ustar guilhemguilhem09590CFD GA Tech College of computing 8B4608A1 GA Tech College of computing 6916C873 GA Tech College of computing 4F570BA3 PGP security maverick DC4ED62D PGP security maverick 466B4289 ext2fstools, Kerberos, LSB, IETF, other 80B07A4F ext2fstools, Kerberos, LSB, IETF, other 103D4013 ext2fstools, Kerberos, LSB, IETF, other 93674C40 ext2fstools, Kerberos, LSB, IETF, other F95C2F6D Debian maintainer & uni-mainz keyserver admin 09AC0A6A cypherpunks 66FBC18C keyanalyze report, free software law, freedb, other 0A2F87E5 OpenBSD, OpenSSH, IPSEC C2009841 OpenBSD, OpenSSH, IPSEC 0679ED91 manages SURFnet servers, scanned PGP source code 66A74B31 manages SURFnet servers, scanned PGP source code C7A966DD inventor of PGP FAEBD5FC inventor of PGP 0DBF906D MIT security/network manager 5B0358A2 author of GNU Privacy Guard (GPG) 1CF27FD5 author of pks PGP keyserver software DD934139 hosts Swiss PGP keyserver, www.ch.pgp.net 52D1CAB1 security researcher at ETH Zurich 00292B81 security researcher at ETH Zurich 46F3212D Debian developer, postfix junkie 2B48F6F5 ISAAC, crypto guru 2DE30EC1 CERT, you know 09D3E64D USENIX, PGPMoose 7362BE39 chief cryptographer, Intel C3FC4C69 security expert, AT&T Labs ED9547ED Debian Project Leader emeritus, dpkg 2FA3BC2D Debian Project Leader emeritus, dpkg 39F37F5D privacy advocate & security expert 961F4A35 inventor of SSH DB41B387 Free Software Foundation VP 7DFF8533 USENIX, Linux NFSv4, uucp 13D9873D co-author, Linux Kernel Internals 1FE961A1 xfree86 driver coder 94C09C7F debian trust analysis 603F2D01 PKI page, pki-page.org A9FA17FF Debian maintainer BDBFE838 KMail developer BB1D9F6D German computer magazine c't; crypto campaign ./keyanalyze/scripts/1000_top.php0000644000175000017500000000055613233575632016716 0ustar guilhemguilhem

[Back to March report]

Top 1000 keys (lowest MSD) for March 2002

./keyanalyze/scripts/report_bottom.php0000644000175000017500000000062413233575632020347 0ustar guilhemguilhem
RankHex ID (last 32b) Key Name (Identifier)Comments MSD

For next month

Discussion about this analysis continues on the keyanalyze-discuss mailing list.

If you have any suggestions, please send them my way, especially if you have the algorithms as well. If you're so inclined, please have a look at the code as well. ./keyanalyze/README0000644000175000017500000000124613233575632014131 0ustar guilhemguilhemOpenPGP key analysis keyanalyze takes a PGP/GnuPG public key ring and analyses the relationships between the keys in it. It produces output suitable for placing on a web site showing which keys have signed which other keys. Its aim is to stimulate awareness of keysigning and help people notice gaps in the Web of Trust that could easily be filled. More documentation later on I hope. For now: make ./analyze.sh path/to/pubring.pgp Output is stored in ./output/ - Be prepared. There is a lot of it. I'd suggest having at least 1GB free if you're processing from the the 'full' keyring. (1 million keys or more) Modify the analyze.sh file to meet your needs if necessary. ./keyanalyze/process_keys.c0000644000175000017500000000772113233575632016132 0ustar guilhemguilhem/* * Does preprocessing of keyrings for an intermediate file to be monged * by keyanalyze. * * Copyright (c)2001 Thomas Roessler * * This program can be freely distributed under the GNU General Public * License. */ #include #include #include #include #include static int DontRequireSelfSig = 0; #define IDBUF 17 struct sig { struct sig *next; char id[IDBUF]; }; struct uid { struct uid *next; struct sig *sigs; unsigned self : 1; }; struct key { char id[IDBUF]; struct uid *uids; unsigned rev : 1; }; static void free_sig (struct sig **sigpp) { struct sig *sigp, *q; if (!sigpp || !*sigpp) return; for (sigp = *sigpp; sigp; sigp = q) { q = sigp->next; free (sigp); } *sigpp = NULL; } static void free_uid (struct uid **uidpp) { struct uid *uidp, *q; if (!uidpp || !*uidpp) return; for (uidp = *uidpp; uidp; uidp = q) { q = uidp->next; free (uidp); } *uidpp = NULL; } static void free_key (struct key **keypp) { struct key *keyp; if (!keypp || !(keyp = *keypp)) return; free_uid (&keyp->uids); free (keyp); *keypp = NULL; } #define new_sig() calloc (sizeof (struct sig), 1) #define new_uid() calloc (sizeof (struct uid), 1) #define new_key() calloc (sizeof (struct key), 1) /* Is a signature with this ID present? */ static int check_sig_id (struct sig *signatures, char *id) { struct sig *s; for (s = signatures; s; s = s->next) if (!strcmp (s->id, id)) return 1; return 0; } /* Is this user ID self-signed? */ static int check_selfsig (struct uid *uid, struct key *key) { return (uid->self = check_sig_id (uid->sigs, key->id)); } /* Append a list of signatures to a different list of signatures */ static void join_siglists (struct sig **sig_d, struct sig **sig_s) { while (*sig_d) sig_d = &((*sig_d)->next); *sig_d = *sig_s; *sig_s = NULL; } /* Clean up a list of signatures - inefficient! */ static void cleanup_siglist (struct sig **sig, char *keyid) { struct sig **last = sig; struct sig *p, *q; for (p = *sig; p; p = q) { q = p->next; if (!strcmp (keyid, p->id) || check_sig_id (p->next, p->id)) { *last = p->next; p->next = NULL; free_sig (&p); } else last = &p->next; } } /* print the information gathered */ static void do_key (struct key *k) { struct sig *interesting_signatures = NULL, *sigp; struct uid *uidp; if (k->rev) return; for (uidp = k->uids; uidp; uidp = uidp->next) if (DontRequireSelfSig || check_selfsig (uidp, k)) join_siglists (&interesting_signatures, &uidp->sigs); cleanup_siglist (&interesting_signatures, k->id); if (interesting_signatures) { printf ("p%s\n", k->id); for (sigp = interesting_signatures; sigp; sigp = sigp->next) printf ("s%s\n", sigp->id); } free_sig (&interesting_signatures); free_uid (&k->uids); } /* the main routine */ int main (int argc, char *argv[]) { char buff[1024]; char *s; struct sig **lastsig = NULL; struct uid **lastuid = NULL; struct key *k = new_key(); lastuid = &k->uids; if (argc == 2 && !strcmp (argv[1], "-S")) DontRequireSelfSig = 1; while (fgets (buff, sizeof (buff), stdin)) { if ((s = strtok (buff, ":"))) { if (!strcmp (s, "pub")) { do_key (k); k->rev = 0; k->uids = new_uid(); lastuid = &k->uids->next; lastsig = &k->uids->sigs; strtok (NULL, ":"); strtok (NULL, ":"); strtok (NULL, ":"); sprintf (k->id, "%s", strtok (NULL, ":")); } else if (!strcmp (s, "rev")) k->rev = 1; else if (!strcmp (s, "uid")) { struct uid *uid = *lastuid = new_uid(); lastuid = &(*lastuid)->next; lastsig = &uid->sigs; } else if (!strcmp (s, "sig")) { struct sig *sig = *lastsig = new_sig(); lastsig = &sig->next; sprintf (sig->id, "%s", strtok (NULL, ":")); } } } do_key (k); return 0; } ./keyanalyze/allkeys.sh0000755000175000017500000000050413233575632015250 0ustar guilhemguilhem#!/bin/bash -- # usage ./analyze.sh path/to/pubring.pgp set -e make # comment these next lines out if you are working with an existing # preprocess.keys file pgpring/pgpring -S -k $1 \ | grep "\(pub\|sig\|rev\|uid\)" \ | sed -e "s/^\([a-z]*\).*:\([0-9A-F]\{16\}\):.*/\1 \2/g" \ -e "s/^uid:.*/uid/" > all.keys ./keyanalyze/willy/0000755000175000017500000000000013233575632014406 5ustar guilhemguilhem./keyanalyze/willy/non-recip0000644000175000017500000000137613233575632016232 0ustar guilhemguilhem#!/usr/bin/perl $cmd = "gpg --option $ARGV[0]/options --list-keys"; print "Non-reciprocating signers\n"; print "\n"; print `cat $ARGV[0]/non-recip.in`; print "\n"; while () { s/Key //; ($key,$number) = split(/ has not signed /, $_, 2); $name = `$cmd $key |head -1`; chomp $name; $name =~ s#.*-[0-9][0-9] ##; $name =~ s/&/&/; $name =~ s//>/; $name =~ s/\([^)]*\)//; $number =~ s/://; chomp $number; $excuse = `grep -s $key $ARGV[0]/excuses`; chomp $excuse; $excuse =~ s/$key //; print "\n"; } print "
NameKey IDSlacknessExcuse
$name$key$number$excuse
"; ./keyanalyze/willy/README0000644000175000017500000000041513233575632015266 0ustar guilhemguilhemMy scripts require a copy of the global keyring msd-sorted.txt file in the current directory. You can download it from http://keyserver.kjsl.com/~jharris/ka/current/msd-sorted.txt.bz2 and use bunzip to uncompress it. This file is updated approximately every 2 weeks. ./keyanalyze/willy/report0000644000175000017500000000524313233575632015650 0ustar guilhemguilhem#!/bin/sh input=${1%%/} output=$1/output if [ ! -e $input ]; then echo Directory $input not found exit 1 fi # functions update() { if [ -e $input/pubring.ids ]; then grep -v '^#' $input/pubring.ids | xargs \ gpg --options $input/options --recv-keys fi test -e $input/pubring.gpg && chmod 644 $input/pubring.gpg rm -f $input/pubring.gpg~ } analyse() { rm -rf $output pgpring -S -k $input/pubring.gpg \ | grep "\(pub\|sig\|rev\|uid\)" \ | sed -e "s/^\([a-z]*\).*:\([0-9A-F]\{16\}\):.*/\1 \2/g" \ -e "s/^uid:.*/uid/" \ | process_keys > $input/preprocess.keys keyanalyze -i $input/preprocess.keys -o $output/ rm $input/preprocess.keys sort -n -k 3 < $output/msd.txt | nl -s ' ' > $output/msd-sorted.txt ./unsign $input cat $output/msd-sorted.txt | ./msd2html $input/options \ > $output/top50table.html cp $input/pubring.ids $input/pubring.gpg $output/ gpg --options $input/options --export -a >$output/pubring.asc } graph() { if [ -e $input/showall ]; then ALL=-a fi if [ -e $input/nograph ]; then RSYNC_DELETE=no else echo -n "Graph generation started at " date gpg --options $input/options --list-sigs | \ sig2dot $ALL 2>/dev/null | \ neato -Tps > $output/graph.ps echo -n "Finished at " date convert $output/graph.ps $output/graph.jpg convert -geometry 320x240 $output/graph.ps \ $output/graph-small.jpg fi } party() { if [ -e $input/party.ids ]; then gpg --options $input/options --export \ `grep -v '^#' $input/party.ids` >$output/party.gpg ./party-table.pl $output/party.gpg >$output/party.html gpg --options $input/options --fingerprint \ `grep -v '^#' $input/party.ids` >$output/sassaman.txt md5sum $output/sassaman.txt >$output/sassaman.md5 sha1sum $output/sassaman.txt >$output/sassaman.sha1 fi } report() { if [ -e $input/report_top.$1.in ]; then date=$(date '+%d %B %Y') sed "s/&date;/$date/" <$input/report_top.$1.in \ > $output/report.$1 cat $output/top50table.html $input/report_bottom.$1.in \ >> $output/report.$1 rm $output/top50table.html fi if [ -e $input/index.$1 ]; then cp -a $input/index.$1 $output/ fi } nonrecip() { if [ -e $input/non-recip.in ]; then ./cosign $output/*/* | \ grep -E 'has not signed ([1-9][0-9]|[5-9])' | \ sort -g -k 1.28 |tac | \ ./non-recip $input >$output/non-recip.html fi } upload() { if [ -e $input/destination ]; then dest=$(sed -e "s/\$input/$input/" < $input/destination) if [ "$RSYNC_DELETE" = "no" ]; then rsync -az $output/ $dest else rsync -az --delete $output/ $dest fi fi } # program update analyse graph party report html report php nonrecip if [ -x $input/update.sh ]; then $input/update.sh $input $output fi upload ./keyanalyze/willy/msd2html0000644000175000017500000000251413233575632016065 0ustar guilhemguilhem#!/usr/bin/perl # this short script is for making the HTML for the top50 report monthly # Copyright (c)2001 M. Drew Streib # This code is released under the GPL version 2 or later. # Modifications (c) Copyright 2003-2004 Matthew Wilcox for subset analyses. $options = $ARGV[0]; $server='http://pool.sks-keyservers.net:11371/'; $oldmsd = 0; $oldrank = 0; while ($line = ) { $line =~ /\s+(\d+)\s+((\w|\d)+)\s+((\w|\d)+)\s+((\d|\.)+)/; $rank = $1; $key0 = $2; $key = $4; $msd = $6; $keylink = substr($key, 0, 2)."/$key"; if ($msd == $oldmsd) { $rank = $oldrank; } else { $oldrank = $rank; $oldmsd = $msd; } $command = "gpg --options $options --list-keys $key"; $output = `$command`; if ($output =~ /\d\d\d\d\-\d\d\-\d\d (.*) <.*/) { $name = $1; } elsif ($output =~ /\d\d\d\d\-\d\d\-\d\d (.*)\n/) { $name = $1; } else { print "failed to parse: $output"; $name = $rank; } $command = 'grep "'.$key.'" msd-sorted.txt'; $rawmsd = `$command`; if ($rawmsd) { $rawmsd =~ / *(\d+) \w+ \w+ *((\d|\.)+)/; $grank = $1; $gmsd = $2; } else { $grank = ''; $gmsd = ''; } print "$rank$key$name$msd$grank$gmsd\n"; } ./keyanalyze/willy/unsign0000644000175000017500000000440513233575632015637 0ustar guilhemguilhem#!/usr/bin/perl # unsign takes the output from keyanalyze and adds useful information such as # the keys that haven't signed you and you haven't signed within this set. # This functionality should probably be added as an option to keyanalyze. $group=$ARGV[0]; sub print_keys { my ($title, @array) = @_; my $size = $#array + 1; print "\n$title:\n"; foreach (@array) { print " $_ $names{$_}\n"; } print "Total: $size keys in this set\n"; } sub set_diff { my ($firstref, $secondref) = @_; my @result; ELEMENT: foreach $element (@$firstref) { foreach $test (@$secondref) { next ELEMENT if $element eq $test; } push @result, $element; } return @result; } sub read_keyfile { my ($name, $toref, $fromref) = @_; open(KEY, $name) or return 1; my $state = 0; while (my $line = ) { if ($line =~ "^Signatures to") { $state = 1; } elsif ($line =~ "^Total:") { $state = 0; } elsif ($line =~ "^Signatures from") { $state = 2; } elsif ($state == 1) { my @key = split(' ', $line); push @$toref, @key[1]; } elsif ($state == 2) { my @key = split(' ', $line); push @$fromref, @key[1]; } } close(KEY); return 0; } open(IDS, "$group/pubring.ids") or die "Could not open $group\n"; while ($id = ) { next if ($id =~ /^#/); next if ($id =~ /^$/); # chomp $id; $id =~ s/\s+$//; $id = substr($id, -8) if length($id) > 8; push @ids, $id; $name = `gpg --options $group/options --list-keys $id`; $name =~ s/\n.*//s; $name =~ s/^.*[0-9][0-9] //; $name =~ s/@/-at-/g; $names{$id} = $name; } close(IDS); foreach $key (@ids) { my $name = $group . "/output/" . substr($key, 0, 2) . "/" . $key; my @to; my @from; next if read_keyfile($name, \@to, \@from); push @to, $key; push @from, $key; my @nonsigned = set_diff(\@ids, \@from); my @nonsigners = set_diff(\@ids, \@to); my @first = set_diff(\@nonsigned, \@nonsigners); my @third = set_diff(\@nonsigned, \@first); my @second = set_diff(\@nonsigners, \@nonsigned); open(KEY, '>>', $name) or die "Cannot open $name\n"; my $oldfh = select(KEY); print_keys("This key has been signed by, but has not signed", @first); print_keys("This key has signed, but has not been signed by", @second); print_keys("This key is not directly connected to", @third); select($oldfd); close(KEY); } ./keyanalyze/willy/party-table.pl0000644000175000017500000000467313233575632017201 0ustar guilhemguilhem#!/usr/bin/perl # Version: 1.0 # Date: 2001.01.07 # Author: V. Alex Brennen # http://www.cryptnet.net/people/vab/ # License: GPL # Description: # This script was written as part of the gpg keysigning # party howto. It generates a checklist for individuals # participating in a keysigning party. The keysigning # howto lives at: # http://www.cryptnet.net/fdp/crypto/gpg-party.html if($ARGV[0] eq "") { print "\nUsage: party-table.pl > out_file.html\n"; print "\nThe keyring should be the keyring where the public keys for the\n"; print "party participants are stored.\n\n"; exit; } @fps = `gpg --fingerprint --no-default-keyring --keyring $ARGV[0]`; my @parsed; while($line = shift(@fps)) { if($line =~ /^pub/) { $key_info = substr($line,5,14); ($size_type,$id) = split(/\//,$key_info); $size = substr($size_type,0,4); $type = substr($size_type,-1,1); $owner = substr($line,31,-1); $fp_line = shift(@fps); ($trash,$fp) = split(/ = /,$fp_line); chomp $fp; ($fp1,$fp2) = split(/ /,$fp); $fp1 =~ s/ / /g; $fp2 =~ s/ / /g; if($type eq "D"){$type = "DSA";} elsif($type eq "R"){$type = "RSA";} elsif($type eq "G"){$type = "ElG";} $owner =~ s//>\;/; $owner =~ s/@/-at-/; push @parsed, { id => $id, owner => $owner, fp1 => $fp1, fp2 => $fp2, size => $size, type => $type, }; } } print "\n"; print "\n"; print "\n"; foreach my $f (sort {uc($a->{owner}) cmp uc($b->{owner})} @parsed) { $id = $f->{id}; $owner = $f->{owner}; $fp1 = $f->{fp1}; $fp2 = $f->{fp2}; $size = $f->{size}; $type = $f->{type}; print ""; print "\n"; } print "
Key IDKey OwnerKey FingerprintKey SizeKey TypeKey Info Matches?Owner ID Matches?
$id$owner$fp1 $fp2$size$type  
\n"; print ""; ./keyanalyze/willy/sigids0000644000175000017500000000033413233575632015613 0ustar guilhemguilhem#!/bin/sh if [ $# -eq 0 ]; then echo "Must provide at least a key ID" exit 1 fi while [ $# -gt 1 ]; do gpgopts="$gpgopts $1" shift done gpg $gpgopts --fast-list-mode --list-sigs $1 | grep ^sig | cut -c13-20 |sort -u ./keyanalyze/willy/cosign0000644000175000017500000000155113233575632015615 0ustar guilhemguilhem#!/usr/bin/perl for (@ARGV) { open(KEY, $_); ($name = $_) =~ s#.*/##; $state = 0; $#to = -1; $#from = -1; $#onlyto = -1; while ($line = ) { if ($line =~ "^Signatures to") { $state = 1; } elsif ($line =~ "^Total:") { $state = 0; } elsif ($line =~ "^Signatures from") { $state = 2; } elsif ($state == 1) { $to[++$#to] = $line; } elsif ($state == 2) { $from[++$#from] = $line; } } close(KEY); @to = sort @to; @from = sort @from; TO: foreach $sigto (@to) { foreach $index (0 .. @from) { if ($sigto eq $from[$index]) { splice(@from, $index, 1); next TO; } } $onlyto[++$#onlyto] = $sigto; } $signed = $#onlyto + 1; $signedby = $#from + 1; print "The following $signedby keys have not signed key $name:\n"; print @from; print " \nKey $name has not signed $signed keys:\n"; print @onlyto; print "\n"; } ./keyanalyze/Changelog0000644000175000017500000000071713233575632015065 0ustar guilhemguilhem200203 * Significant improvements to scripts to create HTML output. 200112 * Applied Steve Langasek's patch to include farthest hop statistic and a fix for a one-off bug causing the last hop count not to be displayed. * Cleaned up some packaging, so that a few more of the simple command lines are automated via analyze.sh 200111 * Applied Matt Kraai's patch to use Tarjan's algorighm and increse efficiency somewhat in finding set connectivity. ./keyanalyze/pgpring/0000755000175000017500000000000013233575632014714 5ustar guilhemguilhem./keyanalyze/pgpring/Makefile.am0000644000175000017500000000013613233575632016750 0ustar guilhemguilhembin_PROGRAMS = pgpring pgpring_SOURCES = pgppubring.c pgplib.c lib.c extlib.c \ pgppacket.c ./keyanalyze/pgpring/pgpring.10000644000175000017500000000223713233575632016450 0ustar guilhemguilhem.\" pgpring, a key ring dumper .\" Manpage Copyright (c) 2004 Matthew Wilcox .\" .\" 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 2 .\" of the License, or (at your option) any later version. .\" .TH PGPRING 1 .SH NAME pgpring \- key ring dumper .SH SYNTAX \fBpgpring\fP [ \fB\-k\fP \fIkeyring\fP | \fB\-2\fP | \fB\-5\fP ] [ \fB\-s\fP ] [ \fB\-S\fP ] [ \fB\-e\fP ] [ \fB\-E\fP ] .SH DESCRIPTION \fIpgpring\fP is a key ring dumper. It extracts information from PGP's binary key ring and emits it in an (almost) readable output format understood by .BR mutt (1) and .BR process_keys (1). This output format mimics the one used by the GNU Privacy Guard (GPG). .SH OPTIONS .TP .BI \-k " keyring" Dump the contents of the specified keyring. .TP .B \-2 Use the default keyring for PGP 2.x. .TP .B \-5 Use the default keyring for PGP 5. .TP .B \-s Dump the secret keyring. .TP .B \-S Include signatures. .TP .B \-e Exclude expired signatures. .TP .B \-E Exclude expired keys. .SH AUTHORS Thomas Roessler ./keyanalyze/pgpring/pgplib.c0000644000175000017500000001006713233575632016341 0ustar guilhemguilhem/* * Copyright (C) 1997-2000 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111, USA. */ /* Generally useful, pgp-related functions. */ #include #include #include #include #include #include "lib.h" #include "pgplib.h" const char *pgp_pkalgbytype (unsigned char type) { switch (type) { case 1: return "RSA"; case 2: return "RSA"; case 3: return "RSA"; case 16: return "ElG"; case 17: return "DSA"; case 20: return "ElG"; default: return "unk"; } } /* unused */ #if 0 static const char *hashalgbytype (unsigned char type) { switch (type) { case 1: return "MD5"; case 2: return "SHA1"; case 3: return "RIPE-MD/160"; case 4: return "HAVAL"; default: return "unknown"; } } #endif short pgp_canencrypt (unsigned char type) { switch (type) { case 1: case 2: case 16: case 20: return 1; default: return 0; } } short pgp_cansign (unsigned char type) { switch (type) { case 1: case 3: case 17: case 20: return 1; default: return 0; } } /* return values: * 1 = sign only * 2 = encrypt only * 3 = both */ short pgp_get_abilities (unsigned char type) { return (pgp_canencrypt (type) << 1) | pgp_cansign (type); } void pgp_free_sig (pgp_sig_t **sigp) { pgp_sig_t *sp, *q; if (!sigp || !*sigp) return; for (sp = *sigp; sp; sp = q) { q = sp->next; safe_free (&sp); } *sigp = NULL; } void pgp_free_uid (pgp_uid_t ** upp) { pgp_uid_t *up, *q; if (!upp || !*upp) return; for (up = *upp; up; up = q) { q = up->next; pgp_free_sig (&up->sigs); safe_free (&up->addr); safe_free (&up); } *upp = NULL; } pgp_uid_t *pgp_copy_uids (pgp_uid_t *up, pgp_key_t *parent) { pgp_uid_t *l = NULL; pgp_uid_t **lp = &l; for (; up; up = up->next) { *lp = safe_calloc (1, sizeof (pgp_uid_t)); (*lp)->trust = up->trust; (*lp)->flags = up->flags; (*lp)->addr = safe_strdup (up->addr); (*lp)->parent = parent; lp = &(*lp)->next; } return l; } static void _pgp_free_key (pgp_key_t ** kpp) { pgp_key_t *kp; if (!kpp || !*kpp) return; kp = *kpp; pgp_free_uid (&kp->address); safe_free (&kp->keyid); safe_free (kpp); } pgp_key_t *pgp_remove_key (pgp_key_t ** klist, pgp_key_t * key) { pgp_key_t **last; pgp_key_t *p, *q, *r; if (!klist || !*klist || !key) return NULL; if (key->parent && key->parent != key) key = key->parent; last = klist; for (p = *klist; p && p != key; p = p->next) last = &p->next; if (!p) return NULL; for (q = p->next, r = p; q && q->parent == p; q = q->next) r = q; if (r) r->next = NULL; *last = q; return q; } void pgp_free_key (pgp_key_t ** kpp) { pgp_key_t *p, *q, *r; if (!kpp || !*kpp) return; if ((*kpp)->parent && (*kpp)->parent != *kpp) *kpp = (*kpp)->parent; /* Order is important here: * * - First free all children. * - If we are an orphan (i.e., our parent was not in the key list), * free our parent. * - free ourselves. */ for (p = *kpp; p; p = q) { for (q = p->next; q && q->parent == p; q = r) { r = q->next; _pgp_free_key (&q); } if (p->parent) _pgp_free_key (&p->parent); _pgp_free_key (&p); } *kpp = NULL; } ./keyanalyze/pgpring/pgppacket.c0000644000175000017500000000733513233575632017046 0ustar guilhemguilhem/* * Copyright (C) 2001 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111, USA. */ #include "config.h" #include #include #include #include #include #include "sha1.h" #include "lib.h" #include "pgplib.h" #include "pgppacket.h" #define CHUNKSIZE 1024 static unsigned char *pbuf = NULL; static size_t plen = 0; static int read_material (size_t material, size_t * used, FILE * fp) { if (*used + material >= plen) { unsigned char *p; size_t nplen; nplen = *used + material + CHUNKSIZE; if (!(p = realloc (pbuf, nplen))) /* __MEM_CHECKED__ */ { perror ("realloc"); return -1; } plen = nplen; pbuf = p; } if (fread (pbuf + *used, 1, material, fp) < material) { perror ("fread"); return -1; } *used += material; return 0; } unsigned char *pgp_read_packet (FILE * fp, size_t * len) { size_t used = 0; long startpos; unsigned char ctb; unsigned char b; size_t material; startpos = ftell (fp); if (!plen) { plen = CHUNKSIZE; pbuf = safe_malloc (plen); } if (fread (&ctb, 1, 1, fp) < 1) { if (!feof (fp)) perror ("fread"); goto bail; } if (!(ctb & 0x80)) { goto bail; } if (ctb & 0x40) /* handle PGP 5.0 packets. */ { int partial = 0; pbuf[0] = ctb; used++; do { if (fread (&b, 1, 1, fp) < 1) { perror ("fread"); goto bail; } if (b < 192) { material = b; partial = 0; /* material -= 1; */ } else if (192 <= b && b <= 223) { material = (b - 192) * 256; if (fread (&b, 1, 1, fp) < 1) { perror ("fread"); goto bail; } material += b + 192; partial = 0; /* material -= 2; */ } else if (b < 255) { material = 1 << (b & 0x1f); partial = 1; /* material -= 1; */ } else /* b == 255 */ { unsigned char buf[4]; if (fread (buf, 4, 1, fp) < 1) { perror ("fread"); goto bail; } /*assert( sizeof(material) >= 4 ); */ material = buf[0] << 24; material |= buf[1] << 16; material |= buf[2] << 8; material |= buf[3]; partial = 0; /* material -= 5; */ } if (read_material (material, &used, fp) == -1) goto bail; } while (partial); } else /* Old-Style PGP */ { int bytes = 0; pbuf[0] = 0x80 | ((ctb >> 2) & 0x0f); used++; switch (ctb & 0x03) { case 0: { if (fread (&b, 1, 1, fp) < 1) { perror ("fread"); goto bail; } material = b; break; } case 1: bytes = 2; case 2: { int i; if (!bytes) bytes = 4; material = 0; for (i = 0; i < bytes; i++) { if (fread (&b, 1, 1, fp) < 1) { perror ("fread"); goto bail; } material = (material << 8) + b; } break; } default: goto bail; } if (read_material (material, &used, fp) == -1) goto bail; } if (len) *len = used; return pbuf; bail: fseek (fp, startpos, SEEK_SET); return NULL; } void pgp_release_packet (void) { plen = 0; safe_free (&pbuf); } ./keyanalyze/pgpring/extlib.c0000644000175000017500000000215313233575632016350 0ustar guilhemguilhem/* * Copyright (C) 1999-2000 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111, USA. */ /* * Some simple dummies, so we can reuse the routines from * lib.c in external programs. */ #include #define WHERE #define _EXTLIB_C #include "lib.h" void (*mutt_error) (const char *, ...) = mutt_nocurses_error; void mutt_exit (int code) { exit (code); } ./keyanalyze/pgpring/pgppacket.h0000644000175000017500000000335013233575632017044 0ustar guilhemguilhem/* * Copyright (C) 2001 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111, USA. */ /* * Definitions for a rudimentary PGP packet parser which is shared * by mutt proper and the PGP public key ring lister. */ #ifndef _PGPPACKET_H # define _PGPPACKET_H enum packet_tags { PT_RES0 = 0, /* reserved */ PT_ESK, /* Encrypted Session Key */ PT_SIG, /* Signature Packet */ PT_CESK, /* Conventionally Encrypted Session Key Packet */ PT_OPS, /* One-Pass Signature Packet */ PT_SECKEY, /* Secret Key Packet */ PT_PUBKEY, /* Public Key Packet */ PT_SUBSECKEY, /* Secret Subkey Packet */ PT_COMPRESSED, /* Compressed Data Packet */ PT_SKE, /* Symmetrically Encrypted Data Packet */ PT_MARKER, /* Marker Packet */ PT_LITERAL, /* Literal Data Packet */ PT_TRUST, /* Trust Packet */ PT_NAME, /* Name Packet */ PT_SUBKEY, /* Subkey Packet */ PT_RES15, /* Reserved */ PT_COMMENT /* Comment Packet */ }; unsigned char *pgp_read_packet (FILE * fp, size_t * len); void pgp_release_packet (void); #endif ./keyanalyze/pgpring/pgplib.h0000644000175000017500000000552113233575632016345 0ustar guilhemguilhem/* * Copyright (C) 1996,1997 Michael R. Elkins * Copyright (C) 1999-2000 Thomas Roessler * * 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ #ifdef HAVE_PGP #define PGPENCRYPT (1 << 0) #define PGPSIGN (1 << 1) #define PGPKEY (1 << 2) #define PGPGOODSIGN (1 << 3) #define KEYFLAG_CANSIGN (1 << 0) #define KEYFLAG_CANENCRYPT (1 << 1) #define KEYFLAG_SECRET (1 << 7) #define KEYFLAG_EXPIRED (1 << 8) #define KEYFLAG_REVOKED (1 << 9) #define KEYFLAG_DISABLED (1 << 10) #define KEYFLAG_SUBKEY (1 << 11) #define KEYFLAG_CRITICAL (1 << 12) #define KEYFLAG_PREFER_ENCRYPTION (1 << 13) #define KEYFLAG_PREFER_SIGNING (1 << 14) #define KEYFLAG_CANTUSE (KEYFLAG_DISABLED|KEYFLAG_REVOKED|KEYFLAG_EXPIRED) #define KEYFLAG_RESTRICTIONS (KEYFLAG_CANTUSE|KEYFLAG_CRITICAL) #define KEYFLAG_ABILITIES (KEYFLAG_CANSIGN|KEYFLAG_CANENCRYPT|KEYFLAG_PREFER_ENCRYPTION|KEYFLAG_PREFER_SIGNING) #define SIGFLAG_EXPIRED (1 << 8) typedef struct pgp_signature { struct pgp_signature *next; unsigned char sigtype; unsigned long sid1; unsigned long sid2; int flags; time_t gen_time; time_t exp_time; } pgp_sig_t; typedef struct pgp_keyinfo { char *keyid; struct pgp_uid *address; int flags; short keylen; time_t gen_time; time_t exp_time; int numalg; const char *algorithm; struct pgp_keyinfo *parent; struct pgp_signature *sigs; struct pgp_keyinfo *next; } pgp_key_t; typedef struct pgp_uid { char *addr; short trust; int flags; struct pgp_keyinfo *parent; struct pgp_uid *next; struct pgp_signature *sigs; } pgp_uid_t; enum pgp_version { PGP_V2, PGP_V3, PGP_GPG, PGP_UNKNOWN }; enum pgp_ring { PGP_PUBRING, PGP_SECRING }; typedef enum pgp_ring pgp_ring_t; /* prototypes */ const char *pgp_pkalgbytype (unsigned char); pgp_key_t *pgp_remove_key (pgp_key_t **, pgp_key_t *); pgp_uid_t *pgp_copy_uids (pgp_uid_t *, pgp_key_t *); short pgp_canencrypt (unsigned char); short pgp_cansign (unsigned char); short pgp_get_abilities (unsigned char); void pgp_free_key (pgp_key_t **kpp); #define pgp_new_keyinfo() safe_calloc (sizeof (pgp_key_t), 1) #endif /* HAVE_PGP */ ./keyanalyze/pgpring/configure.ac0000644000175000017500000000057513233575632017211 0ustar guilhemguilhemAC_INIT([pgpring], [0.0]) AC_CONFIG_SRCDIR([pgppubring.c]) AM_INIT_AUTOMAKE([-Wall foreign]) AC_CANONICAL_HOST AC_PROG_CC AC_ISC_POSIX AC_PROG_CPP AC_PROG_MAKE_SET AC_PROG_INSTALL AC_C_INLINE AC_C_CONST AC_SEARCH_LIBS([SHA1Update], [md]) AC_DEFINE([HAVE_PGP], [1], [Do you want PGP support (--enable-pgp)?]) AC_CONFIG_FILES([Makefile]) AC_CONFIG_HEADERS([config.h]) AC_OUTPUT ./keyanalyze/pgpring/pgppubring.c0000644000175000017500000004337613233575632017252 0ustar guilhemguilhem/* * Copyright (C) 1997-2001 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111, USA. */ /* * This is a "simple" PGP key ring dumper. * * The output format is supposed to be compatible to the one GnuPG * emits and Mutt expects. * * Note that the code of this program could be considerably less * complex, but most of it was taken from mutt's second generation * key ring parser. * * You can actually use this to put together some fairly general * PGP key management applications. * */ #include "config.h" #include #include #include #include #include #ifdef HAVE_GETOPT_H # include #endif #include extern char *optarg; extern int optind; #include "lib.h" #include "pgplib.h" #include "pgppacket.h" #ifdef HAVE_FGETPOS #define FGETPOS(fp,pos) fgetpos((fp),&(pos)) #define FSETPOS(fp,pos) fsetpos((fp),&(pos)) #else #define FGETPOS(fp,pos) pos=ftell((fp)); #define FSETPOS(fp,pos) fseek((fp),(pos),SEEK_SET) #endif static short dump_signatures = 0; static short exclude_exp_sigs = 0; static short exclude_exp_keys = 0; static void pgpring_find_candidates (char *ringfile, const char *hints[], int nhints); static void pgpring_dump_keyblock (pgp_key_t *p); int main (int argc, char * const argv[]) { int c; short version = 2; short secring = 0; const char *_kring = NULL; char *env_pgppath, *env_home; char pgppath[_POSIX_PATH_MAX]; char kring[_POSIX_PATH_MAX]; while ((c = getopt (argc, argv, "eE25sk:S")) != EOF) { switch (c) { case 'S': { dump_signatures = 1; break; } case 'e': { exclude_exp_sigs = 1; break; } case 'E': { exclude_exp_keys = 1; break; } case 'k': { _kring = optarg; break; } case '2': case '5': { version = c - '0'; break; } case 's': { secring = 1; break; } default: { fprintf (stderr, "usage: %s [-k | [-2 | -5] [ -s]] [hints]\n", argv[0]); exit (1); } } } if (_kring) strfcpy (kring, _kring, sizeof (kring)); else { if ((env_pgppath = getenv ("PGPPATH"))) strfcpy (pgppath, env_pgppath, sizeof (pgppath)); else if ((env_home = getenv ("HOME"))) snprintf (pgppath, sizeof (pgppath), "%s/.pgp", env_home); else { fprintf (stderr, "%s: Can't determine your PGPPATH.\n", argv[0]); exit (1); } if (secring) snprintf (kring, sizeof (kring), "%s/secring.%s", pgppath, version == 2 ? "pgp" : "skr"); else snprintf (kring, sizeof (kring), "%s/pubring.%s", pgppath, version == 2 ? "pgp" : "pkr"); } pgpring_find_candidates (kring, (const char**) argv + optind, argc - optind); return 0; } /* The actual key ring parser */ static pgp_key_t *pgp_parse_pgp2_key (unsigned char *buff, size_t l) { pgp_key_t *p; unsigned char alg; size_t expl; unsigned long id; time_t gen_time = 0; unsigned short exp_days = 0; size_t j; int i, k; unsigned char scratch[LONG_STRING]; if (l < 12) return NULL; p = pgp_new_keyinfo(); for (i = 0, j = 2; i < 4; i++) gen_time = (gen_time << 8) + buff[j++]; p->gen_time = gen_time; for (i = 0; i < 2; i++) exp_days = (exp_days << 8) + buff[j++]; if (exp_days) { p->exp_time = gen_time + exp_days * 24 * 3600; if (time (NULL) > p->exp_time) p->flags |= KEYFLAG_EXPIRED; } alg = buff[j++]; p->numalg = alg; p->algorithm = pgp_pkalgbytype (alg); p->flags |= pgp_get_abilities (alg); expl = 0; for (i = 0; i < 2; i++) expl = (expl << 8) + buff[j++]; p->keylen = expl; expl = (expl + 7) / 8; if (expl < 4) goto bailout; j += expl - 8; for (k = 0; k < 2; k++) { for (id = 0, i = 0; i < 4; i++) id = (id << 8) + buff[j++]; snprintf ((char *) scratch + k * 8, sizeof (scratch) - k * 8, "%08lX", id); } p->keyid = safe_strdup ((char *) scratch); return p; bailout: safe_free ((void *)&p); return NULL; } static void pgp_make_pgp3_fingerprint (unsigned char *buff, size_t l, unsigned char *digest) { unsigned char dummy; SHA1_CTX context; SHA1Init (&context); dummy = buff[0] & 0x3f; if (dummy == PT_SUBSECKEY || dummy == PT_SUBKEY || dummy == PT_SECKEY) dummy = PT_PUBKEY; dummy = (dummy << 2) | 0x81; SHA1Update (&context, &dummy, 1); dummy = ((l - 1) >> 8) & 0xff; SHA1Update (&context, &dummy, 1); dummy = (l - 1) & 0xff; SHA1Update (&context, &dummy, 1); SHA1Update (&context, buff + 1, l - 1); SHA1Final (digest, &context); } static void skip_bignum (unsigned char *buff, size_t l, size_t j, size_t * toff, size_t n) { size_t len; do { len = (buff[j] << 8) + buff[j + 1]; j += (len + 7) / 8 + 2; } while (j <= l && --n > 0); if (toff) *toff = j; } static pgp_key_t *pgp_parse_pgp3_key (unsigned char *buff, size_t l) { pgp_key_t *p; unsigned char alg; unsigned char digest[SHA1_DIGEST_LENGTH]; unsigned char scratch[LONG_STRING]; time_t gen_time = 0; unsigned long id; int i, k; short len; size_t j; p = pgp_new_keyinfo (); j = 2; for (i = 0; i < 4; i++) gen_time = (gen_time << 8) + buff[j++]; p->gen_time = gen_time; alg = buff[j++]; p->numalg = alg; p->algorithm = pgp_pkalgbytype (alg); p->flags |= pgp_get_abilities (alg); len = (buff[j] << 8) + buff[j + 1]; p->keylen = len; if (alg >= 1 && alg <= 3) skip_bignum (buff, l, j, &j, 2); else if (alg == 16 || alg == 20) skip_bignum (buff, l, j, &j, 3); else if (alg == 17) skip_bignum (buff, l, j, &j, 4); pgp_make_pgp3_fingerprint (buff, j, digest); for (k = 0; k < 2; k++) { for (id = 0, i = SHA1_DIGEST_LENGTH - 8 + k * 4; i < SHA1_DIGEST_LENGTH + (k - 1) * 4; i++) id = (id << 8) + digest[i]; snprintf ((char *) scratch + k * 8, sizeof (scratch) - k * 8, "%08lX", id); } p->keyid = safe_strdup ((char *) scratch); return p; } static pgp_key_t *pgp_parse_keyinfo (unsigned char *buff, size_t l) { if (!buff || l < 2) return NULL; switch (buff[1]) { case 2: case 3: return pgp_parse_pgp2_key (buff, l); case 4: return pgp_parse_pgp3_key (buff, l); default: return NULL; } } static int pgp_parse_pgp2_sig (unsigned char *buff, size_t l, pgp_key_t * p, pgp_sig_t *s) { unsigned char sigtype; time_t sig_gen_time; unsigned long signerid1; unsigned long signerid2; size_t j; int i; if (l < 22) return -1; j = 3; sigtype = buff[j++]; sig_gen_time = 0; for (i = 0; i < 4; i++) sig_gen_time = (sig_gen_time << 8) + buff[j++]; signerid1 = signerid2 = 0; for (i = 0; i < 4; i++) signerid1 = (signerid1 << 8) + buff[j++]; for (i = 0; i < 4; i++) signerid2 = (signerid2 << 8) + buff[j++]; if (sigtype == 0x20 || sigtype == 0x28) p->flags |= KEYFLAG_REVOKED; if (s) { s->sigtype = sigtype; s->sid1 = signerid1; s->sid2 = signerid2; s->gen_time = sig_gen_time; } return 0; } static int pgp_parse_pgp3_sig (unsigned char *buff, size_t l, pgp_key_t * p, pgp_sig_t *s) { unsigned char sigtype; unsigned char pkalg; unsigned char hashalg; unsigned char skt; time_t sig_gen_time = -1; long validity = -1; long key_validity = -1; unsigned long signerid1 = 0; unsigned long signerid2 = 0; size_t ml; size_t j; int i; short ii; short have_critical_spks = 0; if (l < 7) return -1; j = 2; sigtype = buff[j++]; pkalg = buff[j++]; hashalg = buff[j++]; for (ii = 0; ii < 2; ii++) { size_t skl; size_t nextone; ml = (buff[j] << 8) + buff[j + 1]; j += 2; if (j + ml > l) break; nextone = j; while (ml) { j = nextone; skl = buff[j++]; if (!--ml) break; if (skl >= 192) { skl = (skl - 192) * 256 + buff[j++] + 192; if (!--ml) break; } if ((int) ml - (int) skl < 0) break; ml -= skl; nextone = j + skl; skt = buff[j++]; switch (skt & 0x7f) { case 2: /* creation time */ { if (skl < 4) break; sig_gen_time = 0; for (i = 0; i < 4; i++) sig_gen_time = (sig_gen_time << 8) + buff[j++]; break; } case 3: /* expiration time */ { if (skl < 4) break; validity = 0; for (i = 0; i < 4; i++) validity = (validity << 8) + buff[j++]; break; } case 9: /* key expiration time */ { if (skl < 4) break; key_validity = 0; for (i = 0; i < 4; i++) key_validity = (key_validity << 8) + buff[j++]; if (key_validity > 0) p->exp_time = p->gen_time + key_validity; break; } case 16: /* issuer key ID */ { if (skl < 8) break; signerid2 = signerid1 = 0; for (i = 0; i < 4; i++) signerid1 = (signerid1 << 8) + buff[j++]; for (i = 0; i < 4; i++) signerid2 = (signerid2 << 8) + buff[j++]; break; } case 10: /* CMR key */ break; case 4: /* exportable */ case 5: /* trust */ case 6: /* regexp */ case 7: /* revocable */ case 11: /* Pref. symm. alg. */ case 12: /* revocation key */ case 20: /* notation data */ case 21: /* pref. hash */ case 22: /* pref. comp.alg. */ case 23: /* key server prefs. */ case 24: /* pref. key server */ default: { if (skt & 0x80) have_critical_spks = 1; } } } j = nextone; } if (sigtype == 0x20 || sigtype == 0x28) p->flags |= KEYFLAG_REVOKED; if (key_validity != -1 && time (NULL) > p->gen_time + key_validity) p->flags |= KEYFLAG_EXPIRED; if (have_critical_spks) p->flags |= KEYFLAG_CRITICAL; if (s) { s->sigtype = sigtype; s->sid1 = signerid1; s->sid2 = signerid2; if (sig_gen_time > 0) { s->gen_time = sig_gen_time; if (validity > 0) { s->exp_time = sig_gen_time + validity; if (time (NULL) > s->exp_time) s->flags |= SIGFLAG_EXPIRED; } } } return 0; } static int pgp_parse_sig (unsigned char *buff, size_t l, pgp_key_t * p, pgp_sig_t *sig) { if (!buff || l < 2 || !p) return -1; switch (buff[1]) { case 2: case 3: return pgp_parse_pgp2_sig (buff, l, p, sig); case 4: return pgp_parse_pgp3_sig (buff, l, p, sig); default: return -1; } } /* parse one key block, including all subkeys. */ static pgp_key_t *pgp_parse_keyblock (FILE * fp) { unsigned char *buff; unsigned char pt = 0; unsigned char last_pt; size_t l; short err = 0; #ifdef HAVE_FGETPOS fpos_t pos; #else long pos; #endif pgp_key_t *root = NULL; pgp_key_t **last = &root; pgp_key_t *p = NULL; pgp_uid_t *uid = NULL; pgp_uid_t **addr = NULL; pgp_sig_t **lsig = NULL; FGETPOS(fp,pos); while (!err && (buff = pgp_read_packet (fp, &l)) != NULL) { last_pt = pt; pt = buff[0] & 0x3f; /* check if we have read the complete key block. */ if ((pt == PT_SECKEY || pt == PT_PUBKEY) && root) { FSETPOS(fp, pos); return root; } switch (pt) { case PT_SECKEY: case PT_PUBKEY: case PT_SUBKEY: case PT_SUBSECKEY: { if (!(*last = p = pgp_parse_keyinfo (buff, l))) { err = 1; break; } last = &p->next; addr = &p->address; lsig = &p->sigs; if (pt == PT_SUBKEY || pt == PT_SUBSECKEY) { p->flags |= KEYFLAG_SUBKEY; if (p != root) { p->parent = root; p->address = pgp_copy_uids (root->address, p); while (*addr) addr = &(*addr)->next; } } if (pt == PT_SECKEY || pt == PT_SUBSECKEY) p->flags |= KEYFLAG_SECRET; break; } case PT_SIG: { if (lsig) { pgp_sig_t *signature = safe_calloc (sizeof (pgp_sig_t), 1); *lsig = signature; lsig = &signature->next; pgp_parse_sig (buff, l, p, signature); } break; } case PT_TRUST: { if (p && (last_pt == PT_SECKEY || last_pt == PT_PUBKEY || last_pt == PT_SUBKEY || last_pt == PT_SUBSECKEY)) { if (buff[1] & 0x20) { p->flags |= KEYFLAG_DISABLED; } } else if (last_pt == PT_NAME && uid) { uid->trust = buff[1]; } break; } case PT_NAME: { char *chr; if (!addr) break; chr = safe_malloc (l); memcpy (chr, buff + 1, l - 1); chr[l - 1] = '\0'; *addr = uid = safe_calloc (1, sizeof (pgp_uid_t)); /* XXX */ uid->addr = chr; uid->parent = p; uid->trust = 0; addr = &uid->next; lsig = &uid->sigs; /* the following tags are generated by * pgp 2.6.3in. */ if (strstr (chr, "ENCR")) p->flags |= KEYFLAG_PREFER_ENCRYPTION; if (strstr (chr, "SIGN")) p->flags |= KEYFLAG_PREFER_SIGNING; break; } } FGETPOS(fp,pos); } if (err) pgp_free_key (&root); return root; } static int pgpring_string_matches_hint (const char *s, const char *hints[], int nhints) { int i; if (!hints || !nhints) return 1; for (i = 0; i < nhints; i++) { if (mutt_stristr (s, hints[i]) != NULL) return 1; } return 0; } /* * Go through the key ring file and look for keys with * matching IDs. */ static void pgpring_find_candidates (char *ringfile, const char *hints[], int nhints) { FILE *rfp; #ifdef HAVE_FGETPOS fpos_t pos, keypos; #else long pos, keypos; #endif unsigned char *buff = NULL; unsigned char pt = 0; size_t l = 0; short err = 0; if ((rfp = fopen (ringfile, "r")) == NULL) { perror ("fopen"); return; } FGETPOS(rfp,pos); FGETPOS(rfp,keypos); while (!err && (buff = pgp_read_packet (rfp, &l)) != NULL) { pt = buff[0] & 0x3f; if (l < 1) continue; if ((pt == PT_SECKEY) || (pt == PT_PUBKEY)) { keypos = pos; } else if (pt == PT_NAME) { char *tmp = safe_malloc (l); memcpy (tmp, buff + 1, l - 1); tmp[l - 1] = '\0'; /* mutt_decode_utf8_string (tmp, chs); */ if (pgpring_string_matches_hint (tmp, hints, nhints)) { pgp_key_t *p; FSETPOS(rfp, keypos); /* Not bailing out here would lead us into an endless loop. */ if ((p = pgp_parse_keyblock (rfp)) == NULL) err = 1; pgpring_dump_keyblock (p); pgp_free_key (&p); } safe_free (&tmp); } FGETPOS(rfp,pos); } fclose (rfp); } static void print_userid (const char *id) { for (; id && *id; id++) { if (*id >= ' ' && *id <= 'z' && *id != ':') putchar (*id); else printf ("\\x%02x", *id); } } static void pgpring_dump_signatures (pgp_sig_t *sig) { struct tm *tp; time_t t; for (; sig; sig = sig->next) { if (exclude_exp_sigs && (sig->flags & SIGFLAG_EXPIRED)) continue; if (sig->sigtype == 0x10 || sig->sigtype == 0x11 || sig->sigtype == 0x12 || sig->sigtype == 0x13) { printf ("sig::::%08lX%08lX:", sig->sid1, sig->sid2); t = sig->gen_time; tp = gmtime (&t); printf ("%04d-%02d-%02d:", 1900 + tp->tm_year, tp->tm_mon + 1, tp->tm_mday); if (sig->exp_time) { t = sig->exp_time; tp = gmtime (&t); printf ("%04d-%02d-%02d", 1900 + tp->tm_year, tp->tm_mon + 1, tp->tm_mday); } printf ("::::%X:\n", sig->sigtype); } else if (sig->sigtype == 0x20) printf ("rev::::%08lX%08lX::::::%X:\n", sig->sid1, sig->sid2, sig->sigtype); } } static char gnupg_trustletter (int t) { switch (t) { case 1: return 'n'; case 2: return 'm'; case 3: return 'f'; } return 'q'; } static void pgpring_dump_keyblock (pgp_key_t *p) { pgp_uid_t *uid; short first; struct tm *tp; time_t t; for (; p; p = p->next) { if (exclude_exp_keys && \ (p->flags & KEYFLAG_EXPIRED || p->flags & KEYFLAG_REVOKED)) continue; first = 1; if (p->flags & KEYFLAG_SECRET) { if (p->flags & KEYFLAG_SUBKEY) printf ("ssb:"); else printf ("sec:"); } else { if (p->flags & KEYFLAG_SUBKEY) printf ("sub:"); else printf ("pub:"); } if (p->flags & KEYFLAG_REVOKED) putchar ('r'); if (p->flags & KEYFLAG_EXPIRED) putchar ('e'); if (p->flags & KEYFLAG_DISABLED) putchar ('d'); for (uid = p->address; uid; uid = uid->next, first = 0) { if (!first) { printf ("uid:%c::::::::", gnupg_trustletter (uid->trust)); print_userid (uid->addr); printf (":\n"); } else { if (p->flags & KEYFLAG_SECRET) putchar ('u'); else putchar (gnupg_trustletter (uid->trust)); t = p->gen_time; tp = gmtime (&t); printf (":%d:%d:%s:%04d-%02d-%02d:", p->keylen, p->numalg, p->keyid, 1900 + tp->tm_year, tp->tm_mon + 1, tp->tm_mday); if (p->exp_time) { t = p->exp_time; tp = gmtime (&t); printf ("%04d-%02d-%02d", 1900 + tp->tm_year, tp->tm_mon + 1, tp->tm_mday); } printf (":::"); print_userid (uid->addr); printf (":\n"); } if (dump_signatures) { if (first) pgpring_dump_signatures (p->sigs); pgpring_dump_signatures (uid->sigs); } } } } /* * The mutt_gettext () defined in gettext.c requires iconv, * so we do without charset conversion here. */ char *mutt_gettext (const char *message) { return (char *)message; } ./keyanalyze/pgpring/lib.h0000644000175000017500000000715413233575632015642 0ustar guilhemguilhem/* * Copyright (C) 1996-2000 Michael R. Elkins * Copyright (C) 1999-2000 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111, USA. */ /* mutt functions which are generally useful. */ #ifndef _LIB_H # define _LIB_H # include "config.h" # include # include # ifdef HAVE_UNISTD_H # include /* needed for SEEK_SET */ # endif # include # include # include # include # include # include # ifndef _POSIX_PATH_MAX # include # endif # ifdef ENABLE_NLS # include # define _(a) (gettext (a)) # ifdef gettext_noop # define N_(a) gettext_noop (a) # else # define N_(a) (a) # endif # else # define _(a) (a) # define N_(a) a # endif # define TRUE 1 # define FALSE 0 # define HUGE_STRING 5120 # define LONG_STRING 1024 # define STRING 256 # define SHORT_STRING 128 # define FREE(x) safe_free(x) # define NONULL(x) x?x:"" # define ISSPACE(c) isspace((unsigned char)c) # define strfcpy(A,B,C) strncpy(A,B,C), *(A+(C)-1)=0 #define FOREVER while (1) /* this macro must check for *c == 0 since isspace(0) has unreliable behavior on some systems */ # define SKIPWS(c) while (*(c) && isspace ((unsigned char) *(c))) c++; /* * These functions aren't defined in lib.c, but * they are used there. * * A non-mutt "implementation" (ahem) can be found in extlib.c. */ # ifndef _EXTLIB_C extern void (*mutt_error) (const char *, ...); # endif void mutt_exit (int); /* The actual library functions. */ FILE *safe_fopen (const char *, const char *); char *mutt_read_line (char *, size_t *, FILE *, int *); char *mutt_skip_whitespace (char *); char *mutt_strlower (char *); char *mutt_substrcpy (char *, const char *, const char *, size_t); char *mutt_substrdup (const char *, const char *); char *safe_strdup (const char *); const char *mutt_stristr (const char *, const char *); int mutt_copy_stream (FILE *, FILE *); int mutt_copy_bytes (FILE *, FILE *, size_t); int mutt_rx_sanitize_string (char *, size_t, const char *); int mutt_strcasecmp (const char *, const char *); int mutt_strcmp (const char *, const char *); int mutt_strncasecmp (const char *, const char *, size_t); int mutt_strncmp (const char *, const char *, size_t); int safe_open (const char *, int); int safe_symlink (const char *, const char *); int safe_rename (const char *, const char *); int safe_fclose (FILE **); size_t mutt_quote_filename (char *, size_t, const char *); size_t mutt_strlen (const char *); void *safe_calloc (size_t, size_t); void *safe_malloc (size_t); void mutt_nocurses_error (const char *, ...); void mutt_remove_trailing_ws (char *); void mutt_sanitize_filename (char *, short); void mutt_str_replace (char **p, const char *s); void mutt_str_adjust (char **p); void mutt_unlink (const char *); void safe_free (void *); void safe_realloc (void *, size_t); #endif ./keyanalyze/pgpring/lib.c0000644000175000017500000002715113233575632015634 0ustar guilhemguilhem/* * Copyright (C) 1996-2000 Michael R. Elkins * Copyright (C) 1999-2000 Thomas Roessler * * 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 2 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, write to the Free * Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111, USA. */ /* * This file used to contain some more functions, namely those * which are now in muttlib.c. They have been removed, so we have * some of our "standard" functions in external programs, too. */ #include #include #include #include #include #include #include #include #include #include "lib.h" void mutt_nocurses_error (const char *fmt, ...) { va_list ap; va_start (ap, fmt); vfprintf (stderr, fmt, ap); va_end (ap); fputc ('\n', stderr); } void *safe_calloc (size_t nmemb, size_t size) { void *p; if (!nmemb || !size) return NULL; if (!(p = calloc (nmemb, size))) { mutt_error _("Out of memory!"); sleep (1); mutt_exit (1); } return p; } void *safe_malloc (size_t siz) { void *p; if (siz == 0) return 0; if ((p = (void *) malloc (siz)) == 0) /* __MEM_CHECKED__ */ { mutt_error _("Out of memory!"); sleep (1); mutt_exit (1); } return (p); } void safe_realloc (void *p2, size_t siz) { void **p = (void **)p2; void *r; if (siz == 0) { if (*p) { free (*p); /* __MEM_CHECKED__ */ *p = NULL; } return; } if (*p) r = (void *) realloc (*p, siz); /* __MEM_CHECKED__ */ else { /* realloc(NULL, nbytes) doesn't seem to work under SunOS 4.1.x --- __MEM_CHECKED__ */ r = (void *) malloc (siz); /* __MEM_CHECKED__ */ } if (!r) { mutt_error _("Out of memory!"); sleep (1); mutt_exit (1); } *p = r; } void safe_free (void *p2) { void **p = (void **)p2; if (*p) { free (*p); /* __MEM_CHECKED__ */ *p = 0; } } int safe_fclose (FILE **f) { int r = 0; if (*f) r = fclose (*f); *f = NULL; return r; } char *safe_strdup (const char *s) { char *p; size_t l; if (!s || !*s) return 0; l = strlen (s) + 1; p = (char *)safe_malloc (l); memcpy (p, s, l); return (p); } void mutt_str_replace (char **p, const char *s) { safe_free (p); *p = safe_strdup (s); } void mutt_str_adjust (char **p) { if (!p || !*p) return; safe_realloc ((void **) p, strlen (*p) + 1); } /* convert all characters in the string to lowercase */ char *mutt_strlower (char *s) { char *p = s; while (*p) { *p = tolower (*p); p++; } return (s); } void mutt_unlink (const char *s) { FILE *f; struct stat sb; char buf[2048]; if (stat (s, &sb) == 0) { if ((f = fopen (s, "r+"))) { unlink (s); memset (buf, 0, sizeof (buf)); while (sb.st_size > 0) { fwrite (buf, 1, sizeof (buf), f); sb.st_size -= sizeof (buf); } fclose (f); } } } int mutt_copy_bytes (FILE *in, FILE *out, size_t size) { char buf[2048]; size_t chunk; while (size > 0) { chunk = (size > sizeof (buf)) ? sizeof (buf) : size; if ((chunk = fread (buf, 1, chunk, in)) < 1) break; if (fwrite (buf, 1, chunk, out) != chunk) { /* dprint (1, (debugfile, "mutt_copy_bytes(): fwrite() returned short byte count\n")); */ return (-1); } size -= chunk; } return 0; } int mutt_copy_stream (FILE *fin, FILE *fout) { size_t l; char buf[LONG_STRING]; while ((l = fread (buf, 1, sizeof (buf), fin)) > 0) { if (fwrite (buf, 1, l, fout) != l) return (-1); } return 0; } static int compare_stat (struct stat *osb, struct stat *nsb) { if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino || osb->st_rdev != nsb->st_rdev) { return -1; } return 0; } int safe_symlink(const char *oldpath, const char *newpath) { struct stat osb, nsb; if(!oldpath || !newpath) return -1; if(unlink(newpath) == -1 && errno != ENOENT) return -1; if (oldpath[0] == '/') { if (symlink (oldpath, newpath) == -1) return -1; } else { char abs_oldpath[_POSIX_PATH_MAX]; if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) || (strlen (abs_oldpath) + 1 + strlen (oldpath) + 1 > sizeof abs_oldpath)) return -1; strcat (abs_oldpath, "/"); /* __STRCAT_CHECKED__ */ strcat (abs_oldpath, oldpath); /* __STRCAT_CHECKED__ */ if (symlink (abs_oldpath, newpath) == -1) return -1; } if(stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1 || compare_stat(&osb, &nsb) == -1) { unlink(newpath); return -1; } return 0; } /* * This function is supposed to do nfs-safe renaming of files. * * Warning: We don't check whether src and target are equal. */ int safe_rename (const char *src, const char *target) { struct stat ssb, tsb; if (!src || !target) return -1; if (link (src, target) != 0) { /* * Coda does not allow cross-directory links, but tells * us it's a cross-filesystem linking attempt. * * However, the Coda rename call is allegedly safe to use. * * With other file systems, rename should just fail when * the files reside on different file systems, so it's safe * to try it here. * */ if (errno == EXDEV) return rename (src, target); return -1; } /* * Stat both links and check if they are equal. */ if (stat (src, &ssb) == -1) { return -1; } if (stat (target, &tsb) == -1) { return -1; } /* * pretend that the link failed because the target file * did already exist. */ if (compare_stat (&ssb, &tsb) == -1) { errno = EEXIST; return -1; } /* * Unlink the original link. Should we really ignore the return * value here? XXX */ unlink (src); return 0; } int safe_open (const char *path, int flags) { struct stat osb, nsb; int fd; if ((fd = open (path, flags, 0600)) < 0) return fd; /* make sure the file is not symlink */ if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 || compare_stat(&osb, &nsb) == -1) { /* dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */ close (fd); return (-1); } return (fd); } /* when opening files for writing, make sure the file doesn't already exist * to avoid race conditions. */ FILE *safe_fopen (const char *path, const char *mode) { if (mode[0] == 'w') { int fd; int flags = O_CREAT | O_EXCL; #ifdef O_NOFOLLOW flags |= O_NOFOLLOW; #endif if (mode[1] == '+') flags |= O_RDWR; else flags |= O_WRONLY; if ((fd = safe_open (path, flags)) < 0) return (NULL); return (fdopen (fd, mode)); } else return (fopen (path, mode)); } static char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/"; void mutt_sanitize_filename (char *f, short slash) { if (!f) return; for (; *f; f++) { if ((slash && *f == '/') || !strchr (safe_chars, *f)) *f = '_'; } } /* these characters must be escaped in regular expressions */ static char rx_special_chars[] = "^.[$()|*+?{\\"; int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src) { while (*src && --destlen > 2) { if (strchr (rx_special_chars, *src)) { *dest++ = '\\'; destlen--; } *dest++ = *src++; } *dest = '\0'; if (*src) return -1; else return 0; } /* Read a line from ``fp'' into the dynamically allocated ``s'', * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed. * If a line ends with "\", this char and the linefeed is removed, * and the next line is read too. */ char *mutt_read_line (char *s, size_t *size, FILE *fp, int *line) { size_t offset = 0; char *ch; if (!s) { s = safe_malloc (STRING); *size = STRING; } FOREVER { if (fgets (s + offset, *size - offset, fp) == NULL) { safe_free (&s); return NULL; } if ((ch = strchr (s + offset, '\n')) != NULL) { (*line)++; *ch = 0; if (ch > s && *(ch - 1) == '\r') *--ch = 0; if (ch == s || *(ch - 1) != '\\') return s; offset = ch - s - 1; } else { int c; c = getc (fp); /* This is kind of a hack. We want to know if the char at the current point in the input stream is EOF. feof() will only tell us if we've already hit EOF, not if the next character is EOF. So, we need to read in the next character and manually check if it is EOF. */ if (c == EOF) { /* The last line of fp isn't \n terminated */ (*line)++; return s; } else { ungetc (c, fp); /* undo our dammage */ /* There wasn't room for the line -- increase ``s'' */ offset = *size - 1; /* overwrite the terminating 0 */ *size += STRING; safe_realloc (&s, *size); } } } } char * mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen) { size_t len; len = end - beg; if (len > destlen - 1) len = destlen - 1; memcpy (dest, beg, len); dest[len] = 0; return dest; } char *mutt_substrdup (const char *begin, const char *end) { size_t len; char *p; if (end) len = end - begin; else len = strlen (begin); p = safe_malloc (len + 1); memcpy (p, begin, len); p[len] = 0; return p; } /* prepare a file name to survive the shell's quoting rules. * From the Unix programming FAQ by way of Liviu. */ size_t mutt_quote_filename (char *d, size_t l, const char *f) { size_t i, j = 0; if(!f) { *d = '\0'; return 0; } /* leave some space for the trailing characters. */ l -= 6; d[j++] = '\''; for(i = 0; j < l && f[i]; i++) { if(f[i] == '\'' || f[i] == '`') { d[j++] = '\''; d[j++] = '\\'; d[j++] = f[i]; d[j++] = '\''; } else d[j++] = f[i]; } d[j++] = '\''; d[j] = '\0'; return j; } /* NULL-pointer aware string comparison functions */ int mutt_strcmp(const char *a, const char *b) { return strcmp(NONULL(a), NONULL(b)); } int mutt_strcasecmp(const char *a, const char *b) { return strcasecmp(NONULL(a), NONULL(b)); } int mutt_strncmp(const char *a, const char *b, size_t l) { return strncmp(NONULL(a), NONULL(b), l); } int mutt_strncasecmp(const char *a, const char *b, size_t l) { return strncasecmp(NONULL(a), NONULL(b), l); } size_t mutt_strlen(const char *a) { return a ? strlen (a) : 0; } const char *mutt_stristr (const char *haystack, const char *needle) { const char *p, *q; if (!haystack) return NULL; if (!needle) return (haystack); while (*(p = haystack)) { for (q = needle; *p && *q && tolower (*p) == tolower (*q); p++, q++) ; if (!*q) return (haystack); haystack++; } return NULL; } char *mutt_skip_whitespace (char *p) { SKIPWS (p); return p; } void mutt_remove_trailing_ws (char *s) { char *p; for (p = s + mutt_strlen (s) - 1 ; p >= s && ISSPACE (*p) ; p--) *p = 0; } ./keyanalyze/pgpring/stamp-h.in0000644000175000017500000000001213233575632016606 0ustar guilhemguilhemtimestamp ./keyanalyze/process_keys.10000644000175000017500000000157313233575632016047 0ustar guilhemguilhem.\" process_keys, preprocesses keys into input for keyanalyze .\" manpage Copyright (C) 2004 Matthew Wilcox .\" .\" 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 2 .\" of the License, or (at your option) any later version. .\" .TH process_keys 1 .SH NAME process_keys \- Web of Trust analysis .SH SYNTAX \fBprocess_keys\fP [ \fB\-S\fP ] .SH DESCRIPTION \fIprocess_keys\fP takes the output from .BR pgpring (1) and turns it into suitable input for .BR keyanalyze (1). .B pgpring must be called with the .B -S option to also dump signatures. It acts as a filter, reading from stdin and writing to stdout. .SH OPTIONS .TP .B \-S Accept keys that are not selfsigned. Not recommended. .SH AUTHOR Thomas Roessler ./keyanalyze/analyze.sh0000755000175000017500000000146013233575632015251 0ustar guilhemguilhem#!/bin/bash -- # usage ./analyze.sh path/to/pubring.pgp set -e make # comment these next lines out if you are working with an existing # preprocess.keys file pgpring -S -k "$1" | process_keys $2 > preprocess.keys # the actual processing of the main report keyanalyze # html beautification and reports and such # comment this out if you don't want all the stuff in the report # at http://dtype.org/keyanalyze/ cat output/msd.txt | sort -k 3 | nl -s ' ' > output/msd-sorted.txt cat output/msd.txt | scripts/top50.pl > output/top50table.html cat scripts/report_top.php output/top50table.html \ scripts/report_bottom.php > output/report.php cat output/msd.txt | scripts/top50.pl -n 1000 > output/top1000table.html cat scripts/1000_top.php output/top1000table.html \ scripts/1000_bottom.php > output/report_1000.php ./keyanalyze/keyanalyze.c0000644000175000017500000003123413233575632015571 0ustar guilhemguilhem/* keyanalyze.c * Does some analysis of pre-monged pgp keyrings for some interesting data. * Some code (c)2001 M. Drew Streib * Some code (c)2001 Thomas Roessler * Some code (c)2001 Hal J. Burch * Some code (c)2001 Matt Kraai * Some Code (c)2001 Steve Langasek * * You are licenced to use this code under the terms of the GNU General * Public License (GPL) version 2. */ /* some configurables */ static char *infile = "preprocess.keys"; static char *outdir = "output/"; static int outsubdirs = 1; /* create output/12/12345678 or output/12345678 */ #define MAXKEYS 160000 /* MUST be > `grep p preprocess.keys | wc` */ #define MINSETSIZE 10 /* minimum set size we care about for strong sets */ #define MAXHOPS 30 /* max hop count we care about for report */ /* includes */ #include #include #include #include #include #include #include #include /* globals */ struct sig { int id; struct sig *next; }; typedef struct sig sig; struct threadparam { int threadnum; }; typedef struct threadparam threadparam; struct keydata { unsigned int id1; unsigned int id2; sig *to; sig *from; }; struct keydata keys[MAXKEYS]; FILE *fpin,*fpout,*fpstat,*fpsets,*fpmsd; int numkeys = 0; int numsigs = 0; int component[MAXKEYS]; int max_component; int max_size; int reachable[MAXKEYS]; int num_reachable; float meantotal; pthread_mutex_t mean_l; /* declarations */ void AddKey (unsigned char *newid); void AddSig (int src, int dst); void CloseFiles(); int CountSigs(sig *current); unsigned int ConvertFromHex (const unsigned char *c); int GetKeyById(const unsigned char* searchid); void MeanCrawler(int *distset, int id, int len); float MeanDistance(int id, int *hops, int *hophigh, sig **farthest); /* ################################################################# */ /* helper functions, in alpha order */ void AddKey (unsigned char *newid) { struct keydata *key = &keys[numkeys++]; /* assume no dupes for now */ key->id1 = ConvertFromHex(newid); key->id2 = ConvertFromHex(newid+8); } void AddKeyToList(sig **pptr, int id) { while (*pptr) pptr = &(*pptr)->next; *pptr = (sig *) calloc (1,sizeof(sig)); (*pptr)->id = id; } void AddSig (int src, int dst) { /* if GetKeyById returned -1, then we exit here */ if ((src == -1) || (dst == -1)) return; AddKeyToList(&keys[dst].to, src); AddKeyToList(&keys[src].from, dst); numsigs++; } void CloseFiles() { fclose(fpin); fclose(fpout); } int CountSigs(sig *current) { int ret = 0; while (current->next) { current = current->next; ret++; } return ret; } unsigned int ConvertFromHex (const unsigned char *c) { unsigned char buf1[5]; unsigned char buf2[5]; unsigned int ret; buf1[4] = 0; buf2[4] = 0; memcpy (buf1,c,4); memcpy (buf2,c+4,4); ret = strtol(buf1,NULL,16)*65536 + strtol(buf2,NULL,16); return ret; } void DeleteKeyList(sig **pptr) { sig *current = *pptr; while (*pptr) { current = (*pptr)->next; free (*pptr); *pptr = current; } } /* recursive function to mark connected keys in the connected set */ int DFSMarkConnected (int *markset, int id) { sig *psig; int num = 1; /* mark this node, call this function for all subnodes that aren't * marked already */ markset[id] = 1; for (psig = keys[id].from; psig; psig = psig->next) { if (!markset[psig->id]) num += DFSMarkConnected (markset, psig->id); } return num; } int GetKeyById(const unsigned char* searchid) { int i; unsigned int s1,s2; s1 = ConvertFromHex(searchid); s2 = ConvertFromHex(searchid+8); for (i = 0; i < numkeys; i++) { struct keydata *key = &keys[i]; if ((s1 == key->id1) && (s2 == key->id2)) { return i; } } return (-1); } /* new _much_ faster BFS version of MeanCrawler() contributed by * Hal J. Burch */ void MeanCrawler(int *distset, int id, int len) { sig *psig; int queue[MAXKEYS]; int qhead, qtail; memset(queue,0,sizeof(int)*MAXKEYS); queue[0] = id; distset[id] = 0; qhead = 0; qtail = 1; while (qtail > qhead) { id = queue[qhead++]; len = distset[id]; psig = keys[id].to; while (psig) { if ((len+1) < distset[psig->id]) { distset[psig->id] = len+1; queue[qtail++] = psig->id; } psig = psig->next; } } } float MeanDistance(int id, int *hops, int *hophigh, sig **farthest) { int dist[MAXKEYS]; int i; int totaldist = 0; /* init to a large value here, so shortest distance will always be * less */ memset(dist,100,sizeof(int)*MAXKEYS); MeanCrawler(dist,id,0); for (i=0;i *hophigh) { *hophigh = dist[i]; DeleteKeyList(farthest); } if (dist[i] == *hophigh) { AddKeyToList(farthest, i); } } } if (*hophigh > MAXHOPS) *hophigh = MAXHOPS; return ((float)totaldist / max_size); } FILE *OpenFileById(unsigned int id) { char buf[255]; char idchr[9]; sprintf(idchr,"%08X",id); /* first the directory */ buf[0] = '\0'; strcat(buf, outdir); if (outsubdirs) { strncat(buf,idchr,2); mkdir(buf,(mode_t)493); strcat(buf,"/"); } strcat(buf,idchr); return fopen(buf,"w"); } /* ################################################################# */ /* program block functions, not predeclared */ int OpenFiles() { char buf[255]; fpin = fopen(infile, "r"); if (!fpin) return 1; /* create output dir if necessary. this will just fail if it exists */ mkdir(outdir, (mode_t)493); /* status file */ buf[0] = '\0'; strcat(buf, outdir); strcat(buf,"status.txt"); fpstat = fopen(buf,"w"); if (!fpstat) return 1; /* msd output file */ buf[0] = '\0'; strcat(buf, outdir); strcat(buf,"msd.txt"); fpmsd = fopen(buf,"w"); if (!fpmsd) return 1; /* othersets output file */ buf[0] = '\0'; strcat(buf, outdir); strcat(buf,"othersets.txt"); fpsets = fopen(buf,"w"); if (!fpsets) return 1; /* other output file */ buf[0] = '\0'; strcat(buf, outdir); strcat(buf,"other.txt"); fpout = fopen(buf,"w"); if (!fpout) return 1; return 0; } void ParseArgs(int argc, char **argv) { int outdirlen; while (1) { int option = getopt(argc, argv, "hi:o:1"); if (option == -1) break; switch (option) { case 'h': printf ("Usage: %s [-h1] [-i infile] [-o outdir]\n", argv[0]); exit (0); break; case 'i': infile = optarg; break; case 'o': outdir = optarg; outdirlen = strlen(outdir); if (outdir[outdirlen - 1] != '/') { outdir = malloc(outdirlen + 2); memcpy(outdir, optarg, outdirlen); outdir[outdirlen] = '/'; outdir[outdirlen + 1] = '\0'; } break; case '1': outsubdirs = 0; break; } } if (optind < argc) { /* Assume it's infile */ infile = argv[optind]; } } int PrintKeyList(FILE *f, sig *s) { int i = 0; while (s) { struct keydata *key = &keys[s->id]; fprintf(f, " %08X %08X\n", key->id1, key->id2); s = s->next; i++; } return i; } void ReadInput() { unsigned char buf[20]; int currentkey = -1; fprintf(fpstat,"Importing pass 1 (keys)...\n"); while (fread(buf,1,18,fpin) == 18) { if (buf[17] != '\n') continue; if (buf[0] == 'p') { AddKey(buf+1); } } fprintf(fpstat,"done.\n"); fprintf(fpstat,"%d keys imported\n",numkeys); rewind(fpin); fprintf(fpstat,"Importing pass 2 (sigs)...\n"); while (fread(buf,1,18,fpin) == 18) { if (buf[17] != '\n') continue; if (buf[0] == 'p') { currentkey = GetKeyById(buf+1); if (currentkey == -1) { fprintf(fpstat,"Error finding key in pass 2.\n"); exit(EXIT_FAILURE); } } if (buf[0] == 's') { AddSig(GetKeyById(buf+1),currentkey); if ((numsigs%1000) == 0) { fprintf(fpstat,"%d sigs imported...\n",numsigs); fflush(fpstat); } } } fprintf(fpstat,"done.\n"); fprintf(fpstat,"%d sigs imported\n",numsigs); } /* This is intended for later use. As it takes a lot of time for the * signature imports, this will save time for future runs of the program * with the same data set. */ void SaveState() { /* not yet implemented. need to figure out how to best handle the * linked lists of sigs first */ } int dfsnum[MAXKEYS]; int lownum[MAXKEYS]; int removed[MAXKEYS]; int stack[MAXKEYS]; int stackindex; int lastdfsnum; void DFSVisit(int id) { sig *psig; dfsnum[id] = lownum[id] = ++lastdfsnum; stack[stackindex++] = id; for (psig = keys[id].to; psig; psig = psig->next) { int neighbor = psig->id; if (removed[neighbor]) continue; if (!dfsnum[neighbor]) { DFSVisit (neighbor); if (lownum[neighbor] < lownum[id]) lownum[id] = lownum[neighbor]; } else if (dfsnum[neighbor] < lownum[id]) lownum[id] = dfsnum[neighbor]; } if (lownum[id] == dfsnum[id]) { int i, size = 0; do { struct keydata *key; i = stack[--stackindex]; key = &keys[i]; component[i] = id; removed[i] = 1; size++; fprintf(fpsets, "%08X %08X\n", key->id1, key->id2); } while (i != id); fprintf(fpsets, "*** %d keys in this strongly connected set\n\n", size); if (max_size < size) { max_size = size; max_component = id; } } } void TestConnectivity() { int i; for (i = 0; i < numkeys; i++) if (!dfsnum[i]) DFSVisit (i); num_reachable = DFSMarkConnected (reachable, max_component); fprintf(fpstat,"reachable set is size %d\n", num_reachable); fprintf(fpstat,"strongly connected set is size %d\n", max_size); } /* ################################################################# */ /* report functions, sort of top level */ void IndivReport(FILE *fp,int key) { int totalsigsto, totalsigsfrom; /* head of report */ fprintf(fp,"KeyID %08X %08X\n\n", keys[key].id1, keys[key].id2); fprintf(fp,"This individual key report was generated as part of the monthly keyanalyze\n"); fprintf(fp,"report at http://dtype.org/keyanalyze/.\n\n"); fprintf(fp,"Note: Key signature counts and lists are from a pruned list that only\n"); fprintf(fp,"includes keys with signatures other than their own.\n\n"); fprintf(fp,"Signatures to this key:\n"); totalsigsto = PrintKeyList(fp, keys[key].to); fprintf(fp,"Total: %d signatures to this id from this set\n\n",totalsigsto); fprintf(fp,"Signatures from this key:\n"); totalsigsfrom = PrintKeyList(fp, keys[key].from); fprintf(fp,"Total: %d signatures from this id to this set\n\n",totalsigsfrom); } /* ################################################################# */ /* thread routine */ void *thread_slave(void *arg) { int i,j; float threadmean; sig *distant_sigs = NULL; FILE *fpindiv; int hops[MAXHOPS]; /* array for hop histogram */ int hophigh; /* highest number of hops for this key */ threadparam data = *(threadparam *)arg; for (i=0;iid1, key->id2, threadmean); fflush(fpmsd); pthread_mutex_unlock(&mean_l); /* individual report */ fpindiv = OpenFileById(key->id2); IndivReport(fpindiv,i); fprintf(fpindiv, "This key is %sin the strong set.\n", component[i] == max_component ? "" : "not "); fprintf(fpindiv,"Mean distance to this key from strong set: %8.4f\n\n",threadmean); fprintf(fpindiv,"Breakout by hop count (only from strong set):\n"); for (j=0;j<=hophigh;j++) { fprintf(fpindiv,"%2d hops: %5d\n",j,hops[j]); } if (distant_sigs) { fprintf(fpindiv,"\nFarthest keys (%d hops):\n", j-1); PrintKeyList(fpindiv, distant_sigs); DeleteKeyList(&distant_sigs); } fclose(fpindiv); } } return NULL; } /* ################################################################# */ /* main() */ int main(int argc, char **argv) { pthread_t *slave0,*slave1; threadparam arg0,arg1; void *retval; ParseArgs(argc, argv); if (OpenFiles()) { fprintf(stderr, "Error opening files.\n"); exit(EXIT_FAILURE); } ReadInput(); TestConnectivity(); pthread_mutex_init(&mean_l,NULL); slave0 = (pthread_t *) calloc(1, sizeof(pthread_t)); slave1 = (pthread_t *) calloc(1, sizeof(pthread_t)); arg0.threadnum = 0; arg1.threadnum = 1; if (pthread_create(slave0,NULL,thread_slave,&arg0)) { fprintf(stderr,"Cannot create thread 0."); } if (pthread_create(slave1,NULL,thread_slave,&arg1)) { fprintf(stderr,"Cannot create thread 1."); } pthread_join(*slave0, &retval); pthread_join(*slave1, &retval); fprintf(fpout,"Average mean is %9.4f\n",meantotal/num_reachable); /* ReportMostSignatures(); */ CloseFiles(); return 0; } ./keyanalyze/Makefile0000644000175000017500000000130713233575632014707 0ustar guilhemguilhemLDLIBS=-lpthread CFLAGS:=$(shell dpkg-buildflags --get CFLAGS) all: keyanalyze process_keys pgpring/pgpring keyanalyze: keyanalyze.o process_keys: process_keys.o pgpring/configure: pgpring/configure.ac cd pgpring && autoreconf -f -i pgpring/config.status: pgpring/configure cd pgpring && CFLAGS="${CFLAGS}" ./configure pgpring/pgpring: pgpring/config.status $(MAKE) -C pgpring pgpring install: install pgpring/pgpring $(DESTDIR)/usr/bin install keyanalyze $(DESTDIR)/usr/bin install process_keys $(DESTDIR)/usr/bin clean: [ ! -f pgpring/Makefile ] || $(MAKE) -C pgpring distclean -rm -f *.o core *~ keyanalyze process_keys -rm -f test.pre preprocess.keys keyanalyze.out all.keys -rm -rf output ./keyanalyze/keyanalyze.10000644000175000017500000000303313233575632015503 0ustar guilhemguilhem.\" keyanalyze, a program for analysing webs of trust .\" manpage Copyright (C) 2004 Matthew Wilcox .\" .\" 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 2 .\" of the License, or (at your option) any later version. .\" .TH keyanalyze 1 .SH NAME keyanalyze \- Web of Trust analysis .SH SYNTAX \fBkeyanalyze\fP [ \fB\-h1\fP ] [ \fB\-i\fP \fIinfile\fP ] [ \fB\-o\fP \fIoutdir\fP ] .SH DESCRIPTION \fIkeyanalyze\fP analyses the web of trust within a group of keys. It takes preprocessed keys as input (see .BR process_keys (1)) and produces an output directory full of statistics about the keys. Usually called like $ pgpring \-S \-k ./keyring.gpg | process_keys > preprocess.keys $ keyanalyze .SH OPTIONS .TP .BI \-i " infile" Read from \fIinfile\fP instead of \fBpreprocess.keys\fP. .TP .BI \-o " outdir" Put the results in \fIoutdir\fP instead of \fBoutput/\fP. The directory will be created if it does not already exist. .TP .BI \-h Print help. .TP .BI \-1 Per default, \fBkeyanalyze\fP writes the output into subdirectories named after the first two characters of the key ID. This options disables this; useful for small keyrings. .SH AUTHORS M. Drew Streib , .br Thomas Roessler , .br Hal J. Burch , .br Matt Kraai , .br Steve Langasek , .br Matthew Wilcox ./gpglist/0000755000175000017500000000000013233575735012547 5ustar guilhemguilhem./gpglist/Makefile0000644000175000017500000000012613233575632014202 0ustar guilhemguilhemMAN = gpglist.1 all: $(MAN) %.1: % pod2man $< > $@ install: clean: rm -f $(MAN) ./gpglist/gpglist0000755000175000017500000001334613233575735014155 0ustar guilhemguilhem#!/usr/bin/perl # $Id: gpglist 892 2017-05-16 14:45:48Z guilhem $ # small script to show in an intuitive way who signed which of your user ids # # Copyright (c) 2004 Uli Martens # Copyright (c) 2005 Peter Palfrader # 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. =pod =head1 NAME gpglist -- show who signed which of your UIDs =head1 SYNOPSIS =over =item B [B<--signer=>I] [B<--show-revoked>] I =back =head1 DESCRIPTION B takes a keyid and creates a listing showing who signed I's user IDs. $ gpglist 6D8ABE71 +----- 1 Christoph Berg | +-- 2 Christoph Berg 1 2 x 7929AB90F7AC3AF0 Martin Helas x x 29BE5D2268FD549F Martin Michlmayr x 7DDB2B8DB4B462C5 Martin Wanke By default only non-revoked identities are listed, but it can be overridden it with B<--show-revoked>. One or more B<--signer> option can be used to limit signers to the matching keys. See the GnuPG manual for the different ways to specify a key or user ID. The path to the gpg binary can be specified with the I environment variable (default: C). =head1 AUTHORS =over =item Uli Martens =item Peter Palfrader =back =head1 WEBSITE https://pgp-tools.alioth.debian.org/ =head1 SEE ALSO gpgsigs(1), gpg(1), caff(1). =cut use strict; use warnings; use English '-no_match_vars'; use Getopt::Long; my $now = time; my $show_revoked; my @signers; sub usage() { die "Usage: $PROGRAM_NAME [--signer=] [--show-revoked] \n"; } sub gpg(@) { my @cmd = ($ENV{GNUPGBIN} // 'gpg', qw/--no-auto-check-trustdb --fixed-list-mode --with-colons/, @_); my $pid = open my $fh, '-|', @cmd or die "gpg failed: $!"; return ($fh, $pid); } GetOptions("show-revoked" => \$show_revoked, 'signer=s@' => \@signers) or usage(); usage() unless @ARGV and $#ARGV == 0; my $key = shift @ARGV; my @signing_keys; if (@signers) { my ($fh, $pid) = gpg('--list-keys', @signers); while (<$fh>) { push @signing_keys, $1 if /^pub:(?:[^:]*:){3}([0-9A-F]{16}):/; } waitpid $pid, 0; die "gpg exited with value ".($? >> 8)."\n" if $?; close $fh; } my ($SIGS) = gpg(qw/--list-options show-sig-subpackets --list-sigs/, $key); my ($uid, $id) = ('', ''); my (%uids, @uids); my %sigs; my %revs; my %ids; my $longkey; while (<$SIGS>) { if (/^uid:(?:[^:]*:){6}([0-9A-F]{40}):[^:]*:([^:]+)/) { $uid = $1; # use the hash to have proper distinction between UATs push @uids, $uid; # preserve the order $uids{$uid} = $2; } elsif (/^sig:(?:[^:]*:){3}([0-9A-F]{16}):(\d+):(\d*):(?:[^:]*:){2}([^:]+):(1[0-3][lx])(?::.*)?$/) { $id = $1; next if $3 ne '' and $3 < $now; # expired $ids{$id} = $4; # keep only the most recent sig (a more recent sig might appear anywhere in the list) $sigs{$id}->{$uid} = $2 unless defined $sigs{$id}->{$uid} and ($sigs{$id}->{$uid} < 0 or # non revocable sig $sigs{$id}->{$uid} > $2); } elsif (/^spk:7:1:1:%00$/) { # non-revocable signature $sigs{$id}->{$uid} = -1; } elsif (/^rev:(?:[^:]*:){3}([0-9A-F]{16}):(\d+):(?:[^:]*:){4}30x(?::.*)?$/) { $revs{$1}->{$uid} = $2; } elsif (/^uat:(?:[^:]*:){6}([0-9A-F]{40}):/) { $uid = $1; push @uids, $uid; # preserve the order $uids{$uid} = "Photo ID"; # XXX [jpeg image of size ...] } elsif (/^pub:(?:[^:]*:){3}([0-9A-F]{16}):/) { $longkey = $1; } elsif (/^sub:/) { last; } elsif (!/^(?:fpr|tru|rvk|spk):/) { print STDERR "hi, i'm a bug. please report me to my owner\n"; die "input: $_, key: $key"; } } close $SIGS; sub is_revoked($;$) { my $uid = shift; my $keyid = shift // $longkey; defined $revs{$keyid}->{$uid} and defined $sigs{$keyid}->{$uid} and $sigs{$keyid}->{$uid} > 0 and # < 0 means non-revocable $revs{$keyid}->{$uid} > $sigs{$keyid}->{$uid} ? 1 : 0; } @uids = grep { !is_revoked($_) } @uids unless $show_revoked; for ( my $a=0; $a <= $#uids; $a++ ) { printf "| " x $a . "+--" . "---" x ($#uids-$a) . (is_revoked($uids[$a]) ? 'R' : ' ') . "%2i $uids{$uids[$a]}\n", $a+1; } for ( my $a=0; $a <= $#uids; $a++ ) { printf "%-2i ", $a+1; } print "\n"; for my $id (sort {$ids{$a} cmp $ids{$b}} keys %ids) { next unless !@signers or grep {$id eq $_} @signing_keys; foreach my $uid (@uids) { my $x = is_revoked($uid,$id) ? 'R' : defined $sigs{$id}->{$uid} ? 'x' : ' '; print $x.' '; } print "$id $ids{$id}\n"; } ./gpg-key2ps/0000755000175000017500000000000013233575735013066 5ustar guilhemguilhem./gpg-key2ps/README0000644000175000017500000000270513233575632013746 0ustar guilhemguilhemgpg-key2ps ---------- Usage: gpg-key2ps [-p papersize] [-r revoked-style] [-1] [-s] keyid-or-name revoked-style is one of: grey - Print text in grey hide - Don't show revoked uids note - Add "(revoked)" show - List revoked uids normally strike - Strike through lines Output is PostScript which can be sent to e.g. the lpr command. Specifying the paper size only works when libpaper is installed. CREDITS ------- This script comes from the original Signing-Party package which has been originally created and maintained by Simon Richter and Thijs Kinkhorst . COPYRIGHT & LICENSE ------------------- Copyright (C) 2001-2008 Simon Richter and Thijs Kinkhorst 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 2 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 with the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA ./gpg-key2ps/Makefile0000644000175000017500000000014313233575632014520 0ustar guilhemguilhemall: gpg-key2ps.1 gpg-key2ps.1: gpg-key2ps pod2man $< > $@ install: clean: rm -f gpg-key2ps.1 ./gpg-key2ps/gpg-key2ps0000755000175000017500000002263413233575735015013 0ustar guilhemguilhem#!/usr/bin/perl -w # # gpg-key2ps: convert a PGP/GnuPG key into paper slips. # Copyright (C) 2001-2005 Simon Richter # Copyright (C) 2005-2008 Thijs Kinkhorst # Copyright (C) 2005-2008 Christoph Berg # Licenced under the GNU General Public License, # version 2 or later. # # $Id: gpg-key2ps 899 2017-08-10 15:48:31Z guilhem $ use strict; use Getopt::Long; my $version = '$Rev: 899 $'; $version =~ s/\$Rev:\s*(\d+)\s*\$/$1/; my $showsubkeys = 0; my $revokestyle = "hide"; my $columns = 2; my $creationdate = scalar(localtime); sub version($) { my $fd = shift; print $fd "gpg-key2ps $version - (c) 2001-2008 Simon Richter, Thijs Kinkhorst, Christoph Berg\n"; } sub usage($$) { my ($fd, $exitcode) = @_; version ($fd); print $fd < \$opts->{help}, '--help' => \$opts->{help}, '-v' => \$opts->{version}, '--version' => \$opts->{version}, '-s' => \$opts->{showsubkeys}, '--show-subkeys' => \$opts->{showsubkeys}, '-p=s' => \$opts->{papersize}, '--paper-size=s' => \$opts->{papersize}, '-r=s' => \$opts->{revokestyle}, '--revoked-style=s' => \$opts->{revokestyle}, '-1' => \$opts->{1}, )) { usage(\*STDERR, 1); } if ($opts->{help}) { usage (\*STDOUT, 0); } if ($opts->{version}) { version (\*STDOUT); exit 0; } if ( $opts->{papersize} ) { $ENV{'PAPERSIZE'} = $opts->{papersize}; } if ( $opts->{showsubkeys} ) { $showsubkeys = 1; } if ( $opts->{revokestyle} ) { $revokestyle = $opts->{revokestyle}; } if ( $revokestyle !~ /^(grey|hide|note|show|strike)$/ ) { print STDERR "Unknown revoked-style \"$revokestyle\".\n"; usage (\*STDERR, 1); } if ( $opts->{1} ) { $columns = 1; } usage(\*STDERR, 1) unless scalar @ARGV >= 1; # determine the paper size through the paperconf tool my $w; my $h; if ( `which paperconf` && $? == 0 ) { $w=`paperconf -w`; $h=`paperconf -h`; chomp for ($w,$h); y/,/./ for ($w,$h); } else { # Default to A4. print STDERR "Warning: libpaper-utils is not installed, defaulting to A4.\n"; $w=596; $h=842; } # open a gpg process we'll be reading from below # --list-key due to #382794 open GPG, '-|', $ENV{GNUPGBIN} // 'gpg', qw/--list-key --with-fingerprint --with-colons/, @ARGV; sub start_postscript { # start the PostScript output print <> exch get show } def /pub { condhline 50 y moveto (pub) show 70 y moveto showAlgorithm show (/) show show 150 y moveto show 200 y moveto show newline needhline } def /fpr { 70 y moveto (Key fingerprint = ) show show newline } def /uid { 50 y moveto (uid) show 200 y moveto show newline } def /sbk { 50 y moveto (sub) show 70 y moveto showAlgorithm show (/) show show 150 y moveto show newline } def EOF # output the desired display for revoked uids if ( $revokestyle eq "grey" ) { print <) { # we don't use these if ( /^(tru|uat):/ ) { next; } # every primary uid causes an extra line because of the separator if ( /^pub:/ ) { start_postscript() unless $started; $started = 1; $subkey = 0; $numlines++; } # primary uid s/^pub:[^:]*:([^:]*):([0-9]*):.{8,8}(.{8,8}):([^:]*):[^:]*:[^:]*:[^:]*:([^:]*):[^:]*:[^:]*:.*/ ($5) ($4) ($3) ($1) $2 pub/; # fingerprint, format it nicely with spaces if ( /^fpr:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]*):.*/ ) { next if $subkey; my $fpr = $1; # v4 key $fpr =~ s/(\w{4})(\w{4})(\w{4})(\w{4})(\w{4})(\w{4})(\w{4})(\w{4})(\w{4})(\w{4})/$1 $2 $3 $4 $5 $6 $7 $8 $9 $10/; # v3 key $fpr =~ s/(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})(\w{2})/$1 $2 $3 $4 $5 $6 $7 $8 $9 $10 $11 $12 $13 $14 $15 $16/g; $_ = " ($fpr) fpr\n"; } # user ids s/\\x(\p{AHex}{2})/ chr(hex($1)) /ge; $_ = `echo "$_" | iconv -c -f utf-8 -t latin1`; s/^uid:[^:r]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]*):.*/ ($1) uid/; # revoked user id if (s/^uid:r[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:([^:]*):.*/ ($1) revuid/) { next if $revokestyle eq "hide"; } # subkey $subkey = 1 if /^sub:/; if (s/^sub:[^r:]*:([^:]*):([0-9]*):.{8,8}(.{8,8}):([^:]*):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:.*/ ($4) ($3) ($1) $2 sbk/) { next if ! $showsubkeys; } if (s/^sub:r[^:]*:([^:]*):([0-9]*):.{8,8}(.{8,8}):([^:]*):[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:.*/ ($4) ($3) ($1) $2 revsbk/) { next if (!$showsubkeys) || $revokestyle eq "hide"; } $numlines++; # print this line print; } close(GPG); unless ($started) { print STDERR "No public key found.\n"; exit 1; } # output the remaining postscript print < - generates a PS file from a GnuPG keyring =head1 SYNOPSIS B [B<-r> I] [B<-p> I] [B<-1>] [B<-s>] I [ I<...> ] =head1 DESCRIPTION gpg-key2ps generates a PostScript file with your OpenPGP key fingerprint (repeated as often as it fits) useful for keysigning parties. The only argument is the same as you would pass to GPG's list-keys command, either a key-id or a (partial) name. The PS data is written to stdout. =head1 OPTIONS =over =item B<-p> B<--paper-size> I Select the output paper size. Default is to look into /etc/papersize or A4 if libpaper isn't installed. =item B<-s> B<--show-subkeys> Enable subkey information to be printed on the slips. Subkey information is normally not relevant to the key signing process. =item B<-r> B<--revoked-style> I Select how to mark revoked UIDs and subkeys. Five styles are available: B don't show at all (default), B show normally, B display in 50% grey, B add "[revoked]", and B strike through. =item B<-1> Print only one column of paper slips instead of the default two. Useful for keys with long UIDs that otherwise won't fit. =item I Keyids to print. Multiple can be separated by spaces. =item B<-h> B<--help> Print usage and exit. =item B<-v> B<--version> Print version and exit. =back =head1 ENVIRONMENT =over =item I The default home directory. =item I The gpg binary. Default: C<"gpg">. =item I The default working directory for gpg. Default: C<$HOME/.gnupg>. =back =head1 SEE ALSO =over =item gpg(1) GNU Privacy Guard. =item gpg-key2latex(1) Like B, but produces LaTeX output instead. =item https://pgp-tools.alioth.debian.org/ The homepage of B and the other tools bundled in B. =item https://www.debian.org/events/materials/business-cards/ B prints plain fingerprint slips. If you are looking for something more stylish, look at these latex templates for business cards that also include fingerprints. =back =head1 AUTHORS AND COPYRIGHT =over =item (c) 2001-2005 Simon Richter =item (c) 2005-2008 Thijs Kinkhorst =item (c) 2005-2008 Christoph Berg =back ./gpg-key2latex/0000755000175000017500000000000013233575735013561 5ustar guilhemguilhem./gpg-key2latex/Makefile0000644000175000017500000000013413233575632015213 0ustar guilhemguilhemMAN = gpg-key2latex.1 all: $(MAN) %.1: % pod2man $< > $@ install: clean: rm -f $(MAN) ./gpg-key2latex/gpg-key2latex0000755000175000017500000004603213233575735016177 0ustar guilhemguilhem#!/usr/bin/perl # gpg-key2latex -- Generate a LaTeX file for fingerprint slips. # $Id: gpg-key2latex 891 2017-05-16 14:45:45Z guilhem $ # # Copyright (c) 2014-2016 Guilhem Moulin # # 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 . use warnings; use strict; my $REVISION = '$Rev: 891 $'; 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 qrcode-data=s 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->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; $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(); $gpg->call( $ENV{GNUPGBIN} ) if defined $ENV{GNUPGBIN}; # 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, $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}([^:]*)(:.*)?$/) { my $keyid = $4; %key = (); undef $sub; if ($1 =~ /[eir]/ or $7 =~ /D/) { warn "Ignoring unusable key $keyid.\n"; } else { $key{length} = $2; $key{algo} = $3; $key{creation} = $5; $key{expiration} = $6 if $6 ne ''; $key{caps} = $7; $key{curve} = $1 if defined $8 and $8 =~ /^(?::[^:]*){4}:([^:]+)/; } next; } next unless %key; if (/^uid:([^:]+):(?:[^:]*:){6}[^:]*:([^:]+)/) { undef $sub; next if $1 =~ /[er]/; my $text = $2; $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}([^:]*)(:.*)?$/) { $sub = {}; $sub->{validity} = $1; $sub->{length} = $2; $sub->{algo} = $3; $sub->{creation} = $5; $sub->{expiration} = $6 if $6 ne ''; $sub->{caps} = $7; $sub->{curve} = $1 if defined $8 and $8 =~ /^(?::[^:]*){4}:([^:]+)/; next; } if (/^fpr:(?:[^:]*:){8}([0-9A-F]{40})(?::.*)?$/) { if (defined $sub) { # subkey fingerprint $sub->{fpr} = $1; push @{$key{sub}}, $sub # ignore unusable subkeys unless $sub->{validity} =~ /[eir]/ or $sub->{caps} =~ /D/; } else { # key fingerprint $key{fpr} = $1; } next; } if (!/^(?:rvk|tru|uat):/) { # revoke/revoker/trust/uat 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/gnupg/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+|SIGEXPIRED(?: deprecated-use-keyexpired-instead)?|KEY_CONSIDERED [0-9A-F]{40} \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'} or defined $options{'qrcode-data'}) { $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 "system epstopdf failed: $?.\n" if $?; waitpid $pid, 0; } else { close $rfd or die "Can't close: $!"; dup2 (fileno $wfd, 1); close $wfd or die "Can't close: $!"; my $uri = $options{'qrcode-data'} // 'OPENPGP4FPR:%f'; $uri =~ s/%([fk%])/ $1 eq 'f' ? $key{fpr} : $1 eq 'k' ? substr($key{fpr},-16) : $1 eq '%' ? '%' : die /ge; exec qw/qrencode -i -lM -d300 -tEPS -o -/, $uri or die "Can't exec: $!"; } } push @KEYS, \%key if %key; } die "No usable key found.\n" unless @KEYS; 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{Noto Mono} \ifxetex \usepackage[quiet]{xeCJK} \CJKfontspec{Noto Sans Mono CJK TC} \CJKfontspec{Noto Sans Mono CJK SC} \CJKfontspec{Noto Sans Mono CJK JP} \CJKfontspec{Noto Sans Mono CJK KR} \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 ? 'rsa'.$key->{length} : # RSA $key->{algo} == 2 ? 'rsa'.$key->{length} : # RSA encrypt only (legacy) $key->{algo} == 3 ? 'rsa'.$key->{length} : # RSA sign only $key->{algo} == 16 ? 'elg'.$key->{length} : # Elgamal encrypt only $key->{algo} == 17 ? 'dsa'.$key->{length} : # DSA $key->{algo} == 20 ? 'xxx'.$key->{length} : # Elgamal encrypt+sign (legacy) $key->{algo} == 18 ? $key->{curve} // ('ecdh'.$key->{length}) : # ECDH $key->{algo} == 19 ? $key->{curve} // ('ecdsa'.$key->{length}) : # ECDSA $key->{algo} == 22 ? $key->{curve} // ('eddsa'.$key->{length}) : # EDDSA "unknown_$key->{algo}"; $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->{caps}) - 2) . '['.$key->{caps}.']'; $str . sprintf "\n Key fingerprint = %s %s %s %s %s %s %s %s %s %s\n", map { substr($key->{fpr}, ($_ * 4), 4) } (0..9); } 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]; if ($options{'show-subkeys'}) { $key->{caps} =~ y/[A-Z]//d; # ignore usable capabilities of the entire key $_->{caps} =~ y/[a-z]/[A-Z]/ foreach ($key, @{$key->{sub}}); } else { $key->{caps} =~ y/[a-z]//d; # ignore subkey capabilities } 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[padding=-1ex,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@table \newtoks\toks@row \newtoks\toks@cell \newcounter{n@column} \newcounter{n@key} \def\free@space{\textheight} \newlength\Row@Height \newlength\Cell@Height \toks@table = {\setcounter{n@key}{0}} \toks@cell = {\expandafter\usebox\csname Key\then@key\endcsname} \loop { \setcounter{n@column}{0} \setlength\Row@Height{0pt} \toks@row = {} % generate a single row \loop \ifnum \value{n@column} < \COLUMNS \settototalheight\Cell@Height{\the\toks@cell} \ifnum \Cell@Height>\Row@Height \global\Row@Height=\Cell@Height \fi \ifnum \value{n@column} > 0 \toks@row = \expandafter{\the\toks@row & } \fi \global\toks@row = \expandafter{\the\toks@row% \the\toks@cell% \stepcounter{n@key}% \ifnum \value{n@key}>\MAXKEY \setcounter{n@key}{0} \fi% } \stepcounter{n@key} \ifnum \value{n@key}>\MAXKEY \setcounter{n@key}{0} \fi \stepcounter{n@column} \repeat } \edef\free@space{\number \numexpr \free@space - \Row@Height \relax} \ifnum \free@space > 0 \toks@table = \expandafter{\the\toks@table\the\toks@row \\ \hline} \repeat \begin{tabular}{@{}|*\COLUMNS{@{}l@{}|@{}}}\hline% \the\toks@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 [B<-p> I] [B<-s>] [B<--show-photo>] [B<--show-qrcode>] I [I...] =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, B<--paper-size=>I Select the output paper size. The default is the output of paperconf(1), or I 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 string specified by B<--qrcode-data>. Note: This writes PDF files to the current directory. Requires qrencode(1) and epstopdf(1). =item B<--qrcode-data=>I The data to encode in a QR code (implies B<--show-qrcode>). The string "%f" is expanded to the OpenPGP key fingerprint (40 hexadecimal digits, without spaces); "%k" is expanded to the long (16 hexadecimal digits) key ID; "%%" is replaced by a single "%". The default data to encode, if B<--qrcode-data> is not specified, is the string "OPENPGP4FPR:%f". =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 ENVIRONMENT =over =item I The default home directory. =item I The gpg binary. Default: C<"gpg">. =item I The default working directory for gpg. Default: C<$HOME/.gnupg>. =back =head1 WEBSITE L =head1 SEE ALSO gpg(1), gpg-key2ps(1) =head1 BUGS AND FEEDBACK Bugs or feature requests for B should be filed with the Debian project's bug tracker at L. =head1 AUTHOR Guilhem Moulin Eguilhem@debian.orgE =head1 COPYRIGHT AND LICENSE Copyright (c) 2014 Guilhem Moulin. B is free software, distributed under the GNU Public License, version 3 or later.