]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 1413328 - Use tct (tocotrienol on npm) to encrypt bugmail
authorDylan William Hardison <dylan@hardison.net>
Mon, 7 May 2018 22:52:29 +0000 (18:52 -0400)
committerGitHub <noreply@github.com>
Mon, 7 May 2018 22:52:29 +0000 (18:52 -0400)
.circleci/config.yml
Bugzilla/Install/Localconfig.pm
Dockerfile
extensions/SecureMail/Extension.pm
extensions/SecureMail/lib/TCT.pm [new file with mode: 0644]
template/en/default/setup/strings.txt.pl

index e6db6b7fa53aa754c5ce1672efdb9fb842854e79..cef28d2be02654cf9f33ec4602bca2a93b59eb03 100644 (file)
@@ -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
index 55394bc2eb0a9fd59152fc91d994729dc5381cf8..e1a8e0909423ff64aedd21980b597fbb8cc1ab4c 100644 (file)
@@ -125,6 +125,10 @@ use constant LOCALCONFIG_VARS => (
         name    => 'diffpath',
         default => sub { dirname( bin_loc('diff') ) },
     },
+    {
+        name => 'tct_bin',
+        default => sub { bin_loc('tct') },
+    },
     {
         name => 'site_wide_secret',
 
index 78152531c7286e4cf5b68655435ad83a900769f2..bd16516da070a317e6fb09970f41d21baf529f51 100644 (file)
@@ -1,4 +1,4 @@
-FROM mozillabteam/bmo-slim:20180410.1
+FROM mozillabteam/bmo-slim:20180503.1
 
 ARG CI
 ARG CIRCLE_SHA1
index d684b5a399361bd8de1d9f354a05cb106dbd3a66..508b1f5e80ea7e1b9802b25c2efe86ad4a4b1d8c 100644 (file)
@@ -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 (file)
index 0000000..3a16309
--- /dev/null
@@ -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;
+
index 5fc860519f93549aa95144dbf036af8404efd3d1..8726a8b1388c948b56a7371d9c31e4c40e5f1da1 100644 (file)
@@ -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.