Skip to content
This repository was archived by the owner on Jul 4, 2023. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 96 additions & 67 deletions docs/en/update_signing-keys.pl
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long;
use Time::Piece;

# This script automatically updates the .wmi file with gpg as per:
my $keysfile = "include/keys.txt";
my $wmifile = 'include/keys.wmi';
my $fpfile = 'include/subkey_fingerprints.wmi';
my $forcekeyupdates = 0;
my $skipkeyupdates = 0;
# Set Defaults
my %opts = (
'keysfile' => 'include/keys.txt',
'wmifile' => 'include/keys.wmi',
'fpfile' => 'include/subkey_fingerprints.wmi',
'force-key-updates' => 0,
'no-refresh-keys' => 0,
'refresh-keys' => 1, # set >1 to always force
);

sub print_help {
print "
This script loads the GPG keys of Tor Project release teams from
$opts{'keysfile'}, refreshes them and updates
$opts{'fpfile'} and $opts{'wmifile'}
which is included in docs/en/signing-keys.wml.

It expects gpg >= v2.1 to be installed and working.

Usage: $0 [options]
\t--keysfile\t\tPath to file with GPG key IDs (Default: $opts{'keysfile'})
\t--wmifile\t\tPath to wmi file to be generated (Default: $opts{'wmifile'})
\t--fpfile\t\tPath to wmi file for Tor Browser subkey fingerprints to be generated (Default: $opts{'fpfile'})
\t--refresh-keys\t\tRefresh GPG keys no matter what.
\t--no-refresh-keys\tDon't refresh GPG keys.\n";
exit 0;
}

# Parse Options
GetOptions(
'keysfile=s' => \$opts{'keysfile'},
'wmifile=s' => \$opts{'wmifile'},
'fpfile=s' => \$opts{'fpfile'},
'refresh-keys' => \$opts{'force-key-updates'},
'no-refresh-keys' => \$opts{'no-refresh-keys'},
'help' => sub { &print_help }
) or die "Error parsing arguments. Please try again.\n";

# First we load the keys, then we create a wmi file which is included by
# https://www.torproject.org/docs/signing-keys.html.en
Expand All @@ -17,9 +50,9 @@
$0 =~ /^(.+)\/[^\/]+$/;
my $root = "$1/../..";
chdir $root or die "Could not enter $root: $! (script path: $0)\n";
open my $kf, '<', $opts{'keysfile'} or die "Could not open $opts{'keysfile'}: $!\n";

open my $kf, '<', "$keysfile" # read keys
or die "Could not open $keysfile: $!\n";
## Load keys txt

my %sections; # project => key owners
my %owners; # key owner => string with all keys
Expand All @@ -39,72 +72,65 @@
my $owner = "$1";
my $keys = "$2";
push( @{$sections{"$section"}}, $owner);
$owners{"$owner"} = "$keys";
$owners{"$owner.$section"} = "$keys";
# tell about unrecognized lines
} else { print "Ignored line: $_\n"; }
}
close $kf;
my @owners = keys %owners;
print "Loaded $keysfile. Found $#owners key owners for $#apps applications.\n";
print "Loaded $opts{'keysfile'}. Found $#owners key owners for $#apps applications.\n";

# If the keysfile did not change since the last run, we will not update them.
# To update all keys anyway, set $forcekeyupdates = 1 above, or comment:
if (-f $wmifile && qx/[ $wmifile -nt $keysfile ]/) {
$forcekeyupdates or $skipkeyupdates++;
}

## Update keys and generate wmi

if ($opts{'no-refresh-keys'}) {
print "Not refreshing keys.\n";
undef $opts{'refresh-keys'};
} elsif ($opts{'force-key-updates'}) {
print "Refreshing keys requested.\n";
$opts{'refresh-keys'}++;
# If the keysfile did not change since the last run, we will not update them.
} elsif (-f $opts{'wmifile'} && system "test $opts{'wmifile'} -ot $opts{'keysfile'}") {
print "No need to refresh keys.\n";
$opts{'refresh-keys'}--;
} else { print "Wil refresh GPG keys.\n"; }

my $buffer = ''; # project overview string
my %fingerprints;
foreach my $app (@apps) {
print "\nUpdating keys for '$app':\n";
my ($keys, $subkey_fingerprints, $owners, $suf) = ('', '', '', 's');
print "\nUpdating keys for '$app':\n" if ($opts{'refresh-keys'});
my ($keys, $subkey_fingerprints, $owners) = ('', '', '');
my @keysforapp;
# we grab the key owners for each project and iterate over their keys
foreach my $owner (@{$sections{"$app"}}) { # iterate over owners
my $keys = $owners{"$owner"};
my $keys = $owners{"$owner.$app"};
# example for $keys: 0x165733EA, 0x8D29319A(signing key)
my ($inbrackets, $inbrackets_html) = ('', '');
$suf = '' if ($owners ne '');
my ($inbrackets, $inbrackets_html);
my @keys = split (',', $keys);
foreach my $key (@keys) { # iterate over keys
# validate key format. all regexp are beautiful.
if ($key =~ /^\s?(0x[^\(]+)(\(([^\)]+)\))?/) {
my $key = $1;
my $keylink = "<a href='https://pgp.mit.edu/pks/lookup?search=$key&op=vindex&exact=on'>$key</a>";
push (@keysforapp, $key);
# named alternative key
if ($2) {
$inbrackets .= " with its $3 $key";
# first key
} elsif ($inbrackets eq '') {
$inbrackets = "$key";
$inbrackets_html = "$keylink";
# second key
} else {
$inbrackets = " and $key";
$key =~ s/\s*//g;
my $keylink = "<a href='https://pgp.mit.edu/pks/lookup?search=$key&op=vindex&exact=on'>$key</a>";
push (@keysforapp, $key);
unless ($inbrackets) { # first key
$inbrackets = $key;
$inbrackets_html = $keylink;
} else { # second key
$inbrackets .= " and $key";
$inbrackets_html .= " and $keylink";
}
} else { # tell if the format is wrong
print "Unrecognized key format: $key\n";
}
}
my $sep = ($owners eq '') ? '' : ', ';
# Add owner to the list
$owners .= "$sep$owner ($inbrackets_html)";
print " - $owner ($inbrackets)\n";
}
if ($app eq 'other') {
$buffer .= "<li>Other developers include $owners.</li>\n";
} else {
$suf = 'ed' if ($app =~ /older/);
$buffer .= "<li>$owners sign$suf <strong>$app</strong></li>\n";
}
$buffer .= "<li><strong>$app</strong>: $owners</li>\n";

# we update collected keys for this application and create a string of them
my $gpgcmd = "gpg --keyid-format 0xlong --fingerprint --with-subkey-fingerprints";
foreach my $key (@keysforapp) {
# update keys
if ($forcekeyupdates or not $skipkeyupdates) {
if ($opts{'refresh-keys'}) {
print "\nFetching $key\n";
my $gpgresult;
do { $gpgresult = system "gpg --recv-key $key"; sleep 1; }
Expand All @@ -115,56 +141,59 @@
my $str = qx/$gpgcmd $key/;
# replace html codes
$str =~ s/</&lt;/g; $str =~ s/>/&gt;/g; $str =~ s/@/#/g; $str =~ s/@/&at;/g;
$keys .= "$str";
$keys .= "\n$str";
}
# save formatted string for project
$fingerprints{"$app"} = "<pre>\n$keys</pre>\n";

if ($app eq "Tor Browser releases") {
my $owner = "The Tor Browser Developers";
die "Did not findTor Browser signing key.\n" if ($owners{$owner} eq '');
die "Did not findTor Browser signing key.\n" unless ($owners{"$owner.$app"});
# save Tor Browser signing key subkey fingerprints to $fpfile
my @fp = qx/$gpgcmd $owners{$owner}|grep "Key fingerprint"/;
my @fp = qx/$gpgcmd $owners{"$owner.$app"}|grep "Key fingerprint"/;
shift @fp; # remove primary key fingerprint
$subkey_fingerprints .= join ('', map { s/^\s+Key fingerprint = //; "$_" } @fp);
if (open my $fpout, '>', "$fpfile.temp") {
if (open my $fpout, '>', "$opts{'fpfile'}.temp") {
print $fpout "#!/usr/bin/env wml\n$subkey_fingerprints";
close $fpout;
# check that the written file is not empty
my $written_lines = qx/wc -l "$fpfile.temp"|wc -l/;
my $written_lines = qx/wc -l "$opts{'fpfile'}.temp"|wc -l/;
if ($written_lines gt 0) {
rename "$fpfile.temp", "$fpfile" and
print "\nWrote following subkey fingerprints to $fpfile:\n$subkey_fingerprints"
or die "Could not overwrite $fpfile: $!\n";
} else { die "Created $fpfile.temp but it is empty.\n"; }
} else { die "Could not create temporary file $fpfile.temp.\n"; }
rename "$opts{'fpfile'}.temp", $opts{'fpfile'} and
print "\nWrote following subkey fingerprints to $opts{'fpfile'}:\n$subkey_fingerprints\n"
or die "Could not overwrite $opts{'fpfile'}: $!\n";
} else { die "Created $opts{'fpfile'}.temp but it is empty.\n"; }
} else { die "Could not create temporary file $opts{'fpfile'}.temp.\n"; }
}
}
my @date = localtime;
my $date = "$date[4]/$date[5]"; # Month/Year
my $date = localtime;
my $date_str = $date->strftime("%B %Y"); # month name year

# print keys for each project to file
open my $html, '>', "$wmifile"
or die "Could not write to $wmifile; $!\n";
open my $html, '>', $opts{'wmifile'}
or die "Could not write to $opts{'wmifile'}; $!\n";

print $html "#!/usr/bin/env wml
<p>
This page was automatically generated page from
<a href='/include/keys.txt'>this file listing the gpg keys of our release teams</a>.
This page lists <a href='../include/keys.txt'>GPG signing keys of our release
teams</a> as of $date_str.<br/>
To learn how to verify signatures, see <a href=\"<page docs/signing-keys>\">
our manual</a>.
</p>
<p>
As of $date the signing keys we use are:
</p>

<ul>
$buffer
</ul>
<h2>Fingerprints</h2>\n<p>The fingerprints for the keys are:</p>\n";
</ul>\n";

foreach my $app (@apps) {
print $html "<h3>$app</h3>\n". $fingerprints{"$app"};
}
close $html;
print "\nWrote $wmifile.\n";
print "\nWrote $opts{'wmifile'}.\n";

__END__
things to improve:
- migrate keysfile to YAML
- above code has several TODOs
- run script through perltidy: https://metacpan.org/pod/Perl::Tidy - https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy
- run it through perl critic, a Perl source code analyzer with policies based on Perl Best Practices (PBP): Perl::Critic::Freenode http://p3rl.org/
4 changes: 2 additions & 2 deletions include/keys.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ Arthur Edelstein: 0xD752F538C0D38C3A

[Tor source tarballs]
Roger Dingledine: 0x28988BF5, 0x19F78451
Nick Mathewson: 0xFE43009C4607B1FB, 0x6AFEE6D49E92B601(signing key)
Nick Mathewson: 0xFE43009C4607B1FB

[older Tor tarballs]
Nick Mathewson: 0x165733EA, 0x8D29319A(signing key)
Nick Mathewson: 0x165733EA, 0x8D29319A

[deb.torproject.org repositories and archives]
Tor Project Archive: 0xEE8CBC9E886DDD89
Expand Down
Loading