summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Pritz <bluewind@xinu.at>2015-12-23 23:53:55 +0100
committerFlorian Pritz <bluewind@xinu.at>2015-12-23 23:53:55 +0100
commite69efcdfa17a69ca86931d1106f96d92dc270067 (patch)
tree024446b164d10c48dc13c1dd926cd18069f8799c
parent151b606189542704f787cfd550651fbb5bbb628e (diff)
downloadbin-e69efcdfa17a69ca86931d1106f96d92dc270067.tar.gz
bin-e69efcdfa17a69ca86931d1106f96d92dc270067.tar.xz
Add masterkey.pl
Signed-off-by: Florian Pritz <bluewind@xinu.at>
-rwxr-xr-xmasterkey.pl176
1 files changed, 176 insertions, 0 deletions
diff --git a/masterkey.pl b/masterkey.pl
new file mode 100755
index 0000000..a43dba3
--- /dev/null
+++ b/masterkey.pl
@@ -0,0 +1,176 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use v5.10;
+
+use Data::Dumper;
+use Email::Date;
+use Email::MessageID;
+use Getopt::Long;
+use GnuPG::Interface;
+use JSON;
+use Mail::GPG;
+use MIME::Entity;
+use Pod::Usage;
+use String::Random qw(random_string);
+use Text::Template;
+use Try::Tiny;
+
+=head1 NAME
+
+masterkey.pl
+
+=head1 SYNOPSIS
+
+masterkey.pl [options] <keyid ...>
+
+Send verification mails to the owners of the listed GPG keys.
+
+ Options:
+ --help, -h short help message
+ --dry-run, -n do not perform any permanent actions
+ --from, -f <gpg id> GPG ID used to send the email
+ --tokenfile <file> Record tokens in this file
+ --debug Output debugging information
+
+=cut
+
+sub main {
+my $mail_subject = 'Master Key Verification for {$recipient_name} ({$recipient_key})';
+my $mail_template = 'Hi,
+
+This mail is about having your GPG key signed by an Arch Linux master key.
+Please reply with an email that is signed with your key ({$recipient_key})
+and contains the token listed below.
+
+Your token: {$token}
+
+Best Regards,
+SAMKIVS (Simple Automated Master Key Identity Verification System)
+on behalf of {$sender_name} ({$sender_key})
+';
+
+ my %opts = ();
+
+ Getopt::Long::Configure ("bundling");
+ GetOptions(\%opts, "help|h", "dry-run|n", "from|f=s", "tokenfile=s", "debug") or pod2usage(2);
+ pod2usage(0) if $opts{help};
+ pod2usage(-verbose => 0) if (@ARGV== 0);
+
+ # TODO: print all errors at once
+ die "Error: --from option is required but not set\n" if not $opts{from};
+ die "Error: --tokenfile option is required but not set\n" if not $opts{tokenfile};
+
+ for my $id (@ARGV) {
+ say STDERR "Processing $id";
+ try {
+ my $token = random_string('.' x 25);
+
+ my $msg = build_email($opts{from}, $id, $mail_subject, $mail_template, $token);
+
+ save_token($id, $token, $opts{tokenfile}) unless $opts{'dry-run'};
+ send_email($msg) unless $opts{'dry-run'};
+ say $msg->as_string if $opts{debug};
+ } catch {
+ warn "$_\nSkipping $id due to uncaught error\n";
+ }
+ }
+}
+
+sub save_token {
+ my $id = shift;
+ my $token = shift;
+ my $file = shift;
+
+ open my $fh, '>>', $file or die "Failed to open '$file': $!";
+ say $fh "$id $token";
+ close $fh;
+}
+
+sub fill_template {
+ my $template = shift;
+ my $values = shift;
+
+ my $result = Text::Template::fill_in_string($template, HASH => $values)
+ or die "Failed to fill in template: $Text::Template::ERROR";
+
+ return $result;
+}
+
+sub gpg_get_user {
+ my $key = shift;
+
+ my $gpg = GnuPG::Interface->new();
+ my @keys = $gpg->get_public_keys($key);
+
+ die "No key found" if 0+@keys == 0;
+
+ my $user = $keys[0]->user_ids_ref->[0]->as_string;
+
+ unless ($user =~ m/^(?<name>.*?) (?:\((?<comment>.*?)\) )?\<(?<email>.*?@.*?)\>$/) {
+ die "Failed to parse GPG user information for key $key";
+ }
+
+ my $name = $+{name};
+ my $email = $+{email};
+
+ return ($name, $email);
+}
+
+sub build_email {
+ my $sender_key = shift;
+ my $recipient_key = shift;
+ my $subject = shift;
+ my $body = shift;
+ my $token = shift;
+
+ # get from gpg keys
+ my ($sender_name, $sender_addr) = gpg_get_user($sender_key);
+ my ($recipient_name, $recipient_addr) = gpg_get_user($recipient_key);
+
+ my %values;
+ $values{token} = $token;
+ $values{sender_key} = $sender_key;
+ $values{sender_name} = $sender_name;
+ $values{sender_addr} = $sender_addr;
+ $values{recipient_key} = $recipient_key;
+ $values{recipient_name} = $recipient_name;
+ $values{recipient_addr} = $recipient_addr;
+
+ $subject = fill_template($subject, \%values);
+ $body = fill_template($body, \%values);
+
+ my $mgpg = Mail::GPG->new(
+ default_key_id => $sender_key,
+ default_passphrase => '',
+ );
+
+ my $msg = MIME::Entity->build(
+ From => Encode::encode('iso-8859-1', $sender_name). " <$sender_addr>",
+ To => $recipient_addr,
+ Subject => Encode::encode('iso-8859-1', $subject),
+ # TODO: necessary?
+ #TimeZone => 'Europe/Vienna',
+ Encoding => 'quoted-printable',
+ Charset => 'utf8',
+ Date => Email::Date::format_date(),
+ Data => [$body],
+ );
+
+ $msg->add("Message-ID", Email::MessageID->new->in_brackets);
+ $msg->replace("Return-Path", "<$sender_addr>");
+
+ return $mgpg->mime_sign_encrypt(
+ entity => $msg,
+ recipients => [$sender_key, $recipient_key],
+ );
+}
+
+sub send_email {
+ my $msg = shift;
+ open my $mail, "|msmtp -t";
+ print $mail $msg->as_string;
+ close $mail;
+}
+
+main();