summaryrefslogtreecommitdiffstats
path: root/extensions/SecureMail
diff options
context:
space:
mode:
authorDylan William Hardison <dylan@hardison.net>2018-05-08 00:52:29 +0200
committerGitHub <noreply@github.com>2018-05-08 00:52:29 +0200
commitdcb3506da0be31ec92950e85f51688cecef1b0f8 (patch)
treeaf35019609314a2f328a6da6ce21a9b1540029fc /extensions/SecureMail
parent779252b143809c134aae82333e8456b566b054be (diff)
downloadbugzilla-dcb3506da0be31ec92950e85f51688cecef1b0f8.tar.gz
bugzilla-dcb3506da0be31ec92950e85f51688cecef1b0f8.tar.xz
Bug 1413328 - Use tct (tocotrienol on npm) to encrypt bugmail
Diffstat (limited to 'extensions/SecureMail')
-rw-r--r--extensions/SecureMail/Extension.pm61
-rw-r--r--extensions/SecureMail/lib/TCT.pm112
2 files changed, 140 insertions, 33 deletions
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;
+