From dcb3506da0be31ec92950e85f51688cecef1b0f8 Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Mon, 7 May 2018 18:52:29 -0400 Subject: Bug 1413328 - Use tct (tocotrienol on npm) to encrypt bugmail --- extensions/SecureMail/Extension.pm | 61 ++++++++++---------- extensions/SecureMail/lib/TCT.pm | 112 +++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 33 deletions(-) create mode 100644 extensions/SecureMail/lib/TCT.pm (limited to 'extensions/SecureMail') diff --git a/extensions/SecureMail/Extension.pm b/extensions/SecureMail/Extension.pm index d684b5a39..508b1f5e8 100644 --- a/extensions/SecureMail/Extension.pm +++ b/extensions/SecureMail/Extension.pm @@ -27,6 +27,7 @@ use warnings; use base qw(Bugzilla::Extension); +use Bugzilla::Logging; use Bugzilla::Attachment; use Bugzilla::Comment; use Bugzilla::Group; @@ -35,6 +36,7 @@ use Bugzilla::User; use Bugzilla::Util qw(trim trick_taint is_7bit_clean); use Bugzilla::Error; use Bugzilla::Mailer; +use Bugzilla::Extension::SecureMail::TCT; use Crypt::OpenPGP::Armour; use Crypt::OpenPGP::KeyRing; @@ -125,9 +127,12 @@ sub object_validators { if ($value =~ /PUBLIC KEY/) { # PGP keys must be ASCII-armoured. - if (!Crypt::OpenPGP::Armour->unarmour($value)) { - ThrowUserError('securemail_invalid_key', - { errstr => Crypt::OpenPGP::Armour->errstr }); + my $tct = Bugzilla::Extension::SecureMail::TCT->new( + public_key => $value, + command => Bugzilla->localconfig->{tct_bin}, + ); + unless ($tct->is_valid->get) { + ThrowUserError( 'securemail_invalid_key', { errstr => 'key is invalid or expired' } ); } } elsif ($value =~ /BEGIN CERTIFICATE/) { @@ -468,8 +473,10 @@ sub _make_secure { # PGP Encryption # ################## - my $pubring = new Crypt::OpenPGP::KeyRing(Data => $key); - my $pgp = new Crypt::OpenPGP(PubRing => $pubring); + my $tct = Bugzilla::Extension::SecureMail::TCT->new( + public_key => $key, + command => Bugzilla->localconfig->{tct_bin}, + ); if (scalar $email->parts > 1) { my $old_boundary = $email->{ct}{attributes}{boundary}; @@ -508,7 +515,7 @@ sub _make_secure { disposition => 'inline', encoding => '7bit', }, - body => _pgp_encrypt($pgp, $to_encrypt, $bug_id) + body => _tct_encrypt($tct, $to_encrypt, $bug_id) ), ); $email->parts_set(\@new_parts); @@ -525,7 +532,7 @@ sub _make_secure { if ($sanitise_subject) { _insert_subject($email, $subject); } - $email->body_set(_pgp_encrypt($pgp, $email->body, $bug_id)); + $email->body_set(_tct_encrypt($tct, $email->body, $bug_id)); } } @@ -601,33 +608,21 @@ sub _make_secure { } } -sub _pgp_encrypt { - my ($pgp, $text, $bug_id) = @_; - # "@" matches every key in the public key ring, which is fine, - # because there's only one key in our keyring. - # - # We use the CAST5 cipher because the Rijndael (AES) module doesn't - # like us for some reason I don't have time to debug fully. - # ("key must be an untainted string scalar") - my $encrypted = $pgp->encrypt( - Data => $text, - Recipients => "@", - Cipher => 'CAST5', - Armour => 0 - ); - if (!defined $encrypted) { - return 'Error during Encryption: ' . $pgp->errstr; +sub _tct_encrypt { + my ($tct, $text, $bug_id) = @_; + + my $comment = Bugzilla->localconfig->{urlbase} . ( $bug_id ? 'show_bug.cgi?id=' . $bug_id : '' ); + my $encrypted; + my $ok = eval { $encrypted = $tct->encrypt( $text, $comment )->get; 1 }; + if (!$ok) { + WARN("Error: $@"); + $encrypted = "$comment\nOpenPGP Encryption failed. Check if your key is expired."; } - $encrypted = Crypt::OpenPGP::Armour->armour( - Data => $encrypted, - Object => 'MESSAGE', - Headers => { - Comment => Bugzilla->localconfig->{urlbase} . ($bug_id ? 'show_bug.cgi?id=' . $bug_id : ''), - }, - ); - # until Crypt::OpenPGP makes the Version header optional we have to strip - # it out manually (bug 1181406). - $encrypted =~ s/\nVersion:[^\n]+//; + elsif (!$encrypted) { + WARN('message empty!'); + $encrypted = "$comment\nOpenPGP Encryption failed for unknown reason."; + } + return $encrypted; } diff --git a/extensions/SecureMail/lib/TCT.pm b/extensions/SecureMail/lib/TCT.pm new file mode 100644 index 000000000..3a16309c2 --- /dev/null +++ b/extensions/SecureMail/lib/TCT.pm @@ -0,0 +1,112 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This Source Code Form is "Incompatible With Secondary Licenses", as +# defined by the Mozilla Public License, v. 2.0. + +package Bugzilla::Extension::SecureMail::TCT; +use 5.10.1; +use Moo; + +use Bugzilla::DaemonControl qw( on_finish on_exception ); +use File::Temp; +use Future::Utils qw(call); +use Future; +use IO::Async::Process; + +has 'public_key' => ( is => 'ro', required => 1 ); +has 'public_key_file' => ( is => 'lazy' ); +has 'is_valid' => ( is => 'lazy' ); +has 'command' => ( is => 'ro', default => 'tct' ); + +sub _build_public_key_file { + my ($self) = @_; + my $fh = File::Temp->new(SUFFIX => '.pubkey'); + $fh->print($self->public_key); + $fh->close; + return $fh; +} + +sub _build_is_valid { + my ($self) = @_; + + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my ($stderr, $stdout); + my $process = IO::Async::Process->new( + command => [$self->command, 'check', '-k', $self->public_key_file ], + stderr => { + into => \$stderr, + }, + stdout => { + into => \$stdout, + }, + on_finish => on_finish($exit_f), + on_exception => on_exception($self->command, $exit_f), + ); + $loop->add($process); + + return $exit_f->then( + sub { + my ($rv) = @_; + Future->wrap($rv == 0); + } + ); +} + +sub encrypt { + my ($self, $input, $comment) = @_; + $self->is_valid->then( + sub { + my ($is_valid) = @_; + call { + die 'invalid public key!' unless $is_valid; + + my $output; + my $loop = IO::Async::Loop->new; + my $exit_f = $loop->new_future; + my @command = ( $self->command, 'encrypt', '-k', $self->public_key_file ); + push @command, '--comment', $comment if $comment; + my $process = IO::Async::Process->new( + command => \@command, + stdin => { + from => $input, + }, + stdout => { + into => \$output, + }, + on_finish => on_finish($exit_f), + on_exception => on_exception($self->command, $exit_f), + ); + $loop->add($process); + + return $exit_f->then(sub { Future->wrap($output) }); + } + } + ); +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Extension::SecureMail::TCT - An interface to the tct program + +=head1 SYNOPSIS + + my $key = <<'PUBLIC_KEY'; + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQINBFakJSsBEACbDwHztgZaVhIb6f4PN0KbXv5BEciqKNbdVLgWQJyqgEMIwTF7 + ... + o858gRM= + =t9lA + -----END PGP PUBLIC KEY BLOCK----- + PUBLIC_KEY + + my $tct = Bugzilla::Extension::SecureMail::TCT->new(public_key => $key); + my $encrypted = $tct->encrypt("message", "comment goes here")->get; + -- cgit v1.2.3-24-g4f1b