diff options
author | Dylan William Hardison <dylan@hardison.net> | 2018-05-08 00:52:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-05-08 00:52:29 +0200 |
commit | dcb3506da0be31ec92950e85f51688cecef1b0f8 (patch) | |
tree | af35019609314a2f328a6da6ce21a9b1540029fc | |
parent | 779252b143809c134aae82333e8456b566b054be (diff) | |
download | bugzilla-dcb3506da0be31ec92950e85f51688cecef1b0f8.tar.gz bugzilla-dcb3506da0be31ec92950e85f51688cecef1b0f8.tar.xz |
Bug 1413328 - Use tct (tocotrienol on npm) to encrypt bugmail
-rw-r--r-- | .circleci/config.yml | 4 | ||||
-rw-r--r-- | Bugzilla/Install/Localconfig.pm | 4 | ||||
-rw-r--r-- | Dockerfile | 2 | ||||
-rw-r--r-- | extensions/SecureMail/Extension.pm | 61 | ||||
-rw-r--r-- | extensions/SecureMail/lib/TCT.pm | 112 | ||||
-rw-r--r-- | template/en/default/setup/strings.txt.pl | 3 |
6 files changed, 149 insertions, 37 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index e6db6b7fa..cef28d2be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ version: 2 defaults: bmo_slim_image: &bmo_slim_image - image: mozillabteam/bmo-slim:20180410.1 + image: mozillabteam/bmo-slim:20180503.1 user: app mysql_image: &mysql_image @@ -184,7 +184,7 @@ jobs: [[ -f build_info/only_version_changed.txt ]] && exit 0 perl -I/app -I/app/local/lib/perl5 -c -E 'use Bugzilla; BEGIN { Bugzilla->extensions }' - run: | - [[ -f build_info/only_version_changed.txt ]] && exit 0 + [[ -f build_info/only_version_changed.txt ]] && exit 0 perl Makefile.PL - run: name: run sanity tests diff --git a/Bugzilla/Install/Localconfig.pm b/Bugzilla/Install/Localconfig.pm index 55394bc2e..e1a8e0909 100644 --- a/Bugzilla/Install/Localconfig.pm +++ b/Bugzilla/Install/Localconfig.pm @@ -126,6 +126,10 @@ use constant LOCALCONFIG_VARS => ( default => sub { dirname( bin_loc('diff') ) }, }, { + name => 'tct_bin', + default => sub { bin_loc('tct') }, + }, + { name => 'site_wide_secret', # 64 characters is roughly the equivalent of a 384-bit key, which diff --git a/Dockerfile b/Dockerfile index 78152531c..bd16516da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM mozillabteam/bmo-slim:20180410.1 +FROM mozillabteam/bmo-slim:20180503.1 ARG CI ARG CIRCLE_SHA1 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; + diff --git a/template/en/default/setup/strings.txt.pl b/template/en/default/setup/strings.txt.pl index 5fc860519..8726a8b13 100644 --- a/template/en/default/setup/strings.txt.pl +++ b/template/en/default/setup/strings.txt.pl @@ -181,6 +181,7 @@ For the "Difference Between Two Patches" feature to work, we need to know what directory the "diff" bin is in. (You only need to set this if you are using that feature of the Patch Viewer.) END + localconfig_tct_bin => 'Path to tct (tocotrienol) a gpg replacement.', localconfig_inbound_proxies => <<'END', This is a list of IP addresses that we expect proxies to come from. This can be '*' if only the load balancer can connect. @@ -270,7 +271,7 @@ This is the max amount of unshared memory the apache process is allowed to use before Apache::SizeLimit kills it. This is only applicable when run under mod_perl. EOT localconfig_shadowdb_user => <<EOT, -The username used to authenticate to the shadow db. +The username used to authenticate to the shadow db. EOT localconfig_shadowdb_pass => <<EOT, The password used to authenticate to the shadow db. |