From e69efcdfa17a69ca86931d1106f96d92dc270067 Mon Sep 17 00:00:00 2001 From: Florian Pritz Date: Wed, 23 Dec 2015 23:53:55 +0100 Subject: Add masterkey.pl Signed-off-by: Florian Pritz --- masterkey.pl | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100755 masterkey.pl (limited to 'masterkey.pl') 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] + +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 used to send the email + --tokenfile 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/^(?.*?) (?:\((?.*?)\) )?\<(?.*?@.*?)\>$/) { + 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(); -- cgit v1.2.3-24-g4f1b