[Rt-commit] rt branch, 4.2/smime, repushed
Alex Vandiver
alexmv at bestpractical.com
Thu Aug 29 12:49:55 EDT 2013
The branch 4.2/smime was deleted and repushed:
was e702e73564a20d6b3da2cb9b333765877c314831
now ead85becddcd4a5170ec56c0e198da6ac5ecb55d
162: 4c2fe9c ! 1: 81da587 Process Sign/Encrypt values later on update
@@ -1,17 +1,13 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- processes Sign/Encrypt values later on update
+ Process Sign/Encrypt values later on update
- Undef value is indication of "do whatever queue's options say".
- Code in Interface::Web was changed to fix some bug when value is not
- a boolean but an array (d3341b787e3a734c441596b84ce621339f327851).
- This was correct thing to do, but in a wrong place.
-
- Also, we don't need Encode now as we just turn value into ascii
- scalar without UTF8 flag.
-
- The bug shouldn't affect system unless admin wants to hide GnuPG
- widget that allows people to change crypt preferences on reply.
+ A value for Sign or Encrypt of undef is an indication of "do whatever
+ the queue's options say." d3341b7 resolved a bug of inserting array
+ references into X-RT-Sign, but in doing so lost the possibility to pass
+ undef into ProcessUpdateMessage, and have the queue default kick in. In
+ practice, this is only relevant if the signing controls were not
+ submitted, but customizations may do so.
diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
--- a/lib/RT/Interface/Web.pm
1: 106495d ! 2: ac0d3a1 Refactor code which calls GPG::Interface to elimate duplicate code
@@ -14,10 +14,6 @@
# ...
# );
-+=head2 CallGnuPG
-+
-+=cut
-+
+sub CallGnuPG {
+ my %args = (
+ Options => undef,
2: ce5881a < ---: ------- Factor out code which finalizes GnuPG output into a data structure
3: 42a5f38 ! 3: bcb1f89 Generalize CallGnuPG slightly more, allowing more code reuse
@@ -69,7 +69,7 @@
- print { $handle{'command'} } "y\n";
+ return CallGnuPG(
+ Method => "--delete-secret-and-public-key",
-+ CommandArgs => [\--', $key],
++ CommandArgs => ["--", $key],
+ Callback => sub {
+ my %handle = @_;
+ while ( my $str = readline $handle{'status'} ) {
@@ -83,7 +83,20 @@
- my $err = $@;
- close $handle{'stdout'};
-
-- return Finalize( $err, \%handle );
+- my %res;
+- $res{'exit_code'} = $?;
+- foreach ( qw(stderr logger status) ) {
+- $res{$_} = do { local $/ = undef; readline $handle{$_} };
+- delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+- close $handle{$_};
+- }
+- $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+- $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+- $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+- if ( $err || $res{'exit_code'} ) {
+- $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
+- }
+- return %res;
+ },
+ );
}
4: 51ae5fd = 4: b0dba81 Minor cleanups to Probe, the one remaining non-CallGnuPG gpg interaction
5: 9f5f01c ! 5: 0da7281 Catch errors on close()
@@ -24,7 +24,7 @@
+ }
}
- return Finalize( $err, \%handle );
+ my %res;
@@
foreach ( qw(stderr logger status) ) {
$res{$_} = do { local $/ = undef; readline $handle{$_} };
6: 333b2b7 = 6: 80c0798 Rename Key to Signer for clarity and consistency
7: 72edb8b ! 7: 1194665 Rename Method to Command for clarity; "--foo" is not a "method"
@@ -146,7 +146,7 @@
return CallGnuPG(
- Method => "--delete-secret-and-public-key",
+ Command => "--delete-secret-and-public-key",
- CommandArgs => [\--', $key],
+ CommandArgs => ["--", $key],
Callback => sub {
my %handle = @_;
@@
8: 76e3e68 ! 8: 079e3e1 Only set the default key if we actually have one
@@ -1,6 +1,6 @@
Author: Alex Vandiver <alexmv at bestpractical.com>
- Only set the defualt key if we actually have one
+ Only set the default key if we actually have one
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
9: e92c425 = 9: 8938e19 Only set the passphrase if we have one
10: 992b410 ! 10: 312cb6e Split IO::Handle::CRLF into its own file in RT::Crypt::GnuPG::CRLFHandle
@@ -51,6 +51,54 @@
--- /dev/null
+++ b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
@@
++# BEGIN BPS TAGGED BLOCK {{{
++#
++# COPYRIGHT:
++#
++# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
++# <sales at bestpractical.com>
++#
++# (Except where explicitly superseded by other copyright notices)
++#
++#
++# LICENSE:
++#
++# This work is made available to you under the terms of Version 2 of
++# the GNU General Public License. A copy of that license should have
++# been provided with this software, but in any event can be snarfed
++# from www.gnu.org.
++#
++# This work is distributed in the hope that it will be useful, but
++# WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++# General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program; if not, write to the Free Software
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
++# 02110-1301 or visit their web page on the internet at
++# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
++#
++#
++# CONTRIBUTION SUBMISSION POLICY:
++#
++# (The following paragraph is not intended to limit the rights granted
++# to you to modify and distribute this software under the terms of
++# the GNU General Public License and is only of importance to you if
++# you choose to contribute your changes and enhancements to the
++# community by submitting them to Best Practical Solutions, LLC.)
++#
++# By intentionally submitting any modifications, corrections or
++# derivatives to this work, or any other work intended for use with
++# Request Tracker, to Best Practical Solutions, LLC, you confirm that
++# you are the copyright holder for those contributions and you grant
++# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
++# royalty-free, perpetual, license to use, copy, create derivative
++# works based on those contributions, and sublicense and distribute
++# those contributions and any derivatives thereof.
++#
++# END BPS TAGGED BLOCK }}}
++
+package RT::Crypt::GnuPG::CRLFHandle;
+use strict;
+use warnings;
11: 2db4d8b < ---: ------- make pseudo OO API out of functions, it was always the plan
12: 8503800 < ---: ------- first pass on generic Crypt API for RT
13: 36343b6 < ---: ------- switch SignEncrypt to new API
---: ------- > 11: 09d54d2 Switch out RT::Crypt::GnuPG function calls for class methods
---: ------- > 12: 6b99048 Create a generic RT::Crypt class to dispatch methods from
---: ------- > 13: 6d2312e Move data-storage UseKeyFor... methods onto RT::Crypt
---: ------- > 14: aad107d Provide a RT::Crypt->LoadImplementation method to load RT::Crypt::...
---: ------- > 15: 30ab442 Add RT::Crypt->Protocols, to return the supported encryption protocols
---: ------- > 16: 3ffc9fd Make protocol loading case-insensitive
---: ------- > 17: 217e572 Add a role for encryption classes
---: ------- > 18: 57c195c Move GetPassphrase onto the role
---: ------- > 19: 967cfef Move SignEncrypt to dispatch from RT::Crypt
161: a2bf5f9 ! 20: d0ecfdc Do not error if no From address is provided
@@ -1,11 +1,10 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- avoid scrip death when correspond address is not set
+ Do not error if no From address is provided
- It's misconfiguration when correspond address is not
- set in the config and for a queue, but still stack
- trace from Crypt.pm into scrip and further is not
- helpful.
+ If the correspond address is neither set globally nor on the queue, the
+ From adress will be empty. While this is a misconfiguration, avoid
+ erroring out when attempting to guess the signing address.
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
--- a/lib/RT/Crypt.pm
@@ -32,7 +31,7 @@
+use warnings;
+
+use RT::Test::GnuPG
-+ tests => 5,
++ tests => 6,
+ gnupg_options => {
+ passphrase => 'rt-test',
+ 'trust-model' => 'always',
@@ -43,13 +42,14 @@
+{
+ $queue = RT::Test->load_or_create_queue(
+ Name => 'Regression',
-+ Sign => 1,
++ SignAuto => 1,
+ );
+ ok $queue && $queue->id, 'loaded or created queue';
+ ok !$queue->CorrespondAddress, 'address not set';
+}
+
-+{
++use Test::Warn;
++warnings_like {
+ my $ticket = RT::Ticket->new( RT->SystemUser );
+ my ($status, undef, $msg) = $ticket->Create(
+ Queue => $queue->id,
@@ -61,6 +61,6 @@
+ my $log = RT::Test->file_content([RT::Test->temp_directory, 'rt.debug.log']);
+ like $log, qr{secret key not available}, 'error in the log';
+ unlike $log, qr{Scrip .*? died}m, "scrip didn't die";
-+}
++} [qr{gpg: keyring .*? created}];
+
14: adc53ba ! 21: b3a3025 Turn FindProtectedParts into a two-step process
@@ -1,43 +1,97 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- re-write FindProtectedParts
+ Turn FindProtectedParts into a two-step process
- we have to split this functionality into two separate steps:
- 1) CheckIfProtected
- 2) FindScatteredParts
+ In order to simplify and genericize detection of encrypted or signed
+ messages, the monolithic FindProtectedParts has been split into three
+ pieces:
+
+ CheckIfProtected - Determines if the particular entity (and its
+ children) is a signed or encrypted component. This deals with
+ RFC3156 'multipart/signed' or 'multipart/encrypted' messages.
+
+ FindScatteredParts - Passed all unclaimed non-multipart parts, and
+ examines them as a whole; it looks for detached signatures at a
+ distance, or inline PGP messages.
+
+ FindProtectedParts - Calls the above two in series. This method is
+ not tied to GnuPG encryption in any way.
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@
- return %res;
- }
-
--sub FindProtectedParts {
+
+ sub FindProtectedParts {
+ my $self = shift;
- my %args = ( Entity => undef, CheckBody => 1, @_ );
-+sub CheckIfProtected {
-+ my $self = shift;
-+ my %args = ( Entity => undef, @_ );
++ my %args = (
++ Entity => undef,
++ Skip => {},
++ Scattered => 1,
++ @_
++ );
+
my $entity = $args{'Entity'};
++ return () if $args{'Skip'}{ $entity };
- # inline PGP block, only in singlepart
- unless ( $entity->is_multipart ) {
- my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
--
++ my %info = $self->CheckIfProtected( Entity => $entity );
++ if (keys %info) {
++ $args{'Skip'}{ $entity } = 1;
++ return \%info;
++ }
+
- my $io = $entity->open('r');
- unless ( $io ) {
- $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
- return ();
- }
-+ # we check inline PGP block later in another sub
-+ return () unless $entity->is_multipart;
-
- # Deal with "partitioned" PGP mail, which (contrary to common
- # sense) unnecessarily applies a base64 transfer encoding to PGP
-@@
++ my @res;
+
+- # Deal with "partitioned" PGP mail, which (contrary to common
+- # sense) unnecessarily applies a base64 transfer encoding to PGP
+- # mail (whose content is already base64-encoded).
+- if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
+- my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
+- if ($decoder) {
+- local $@;
+- eval {
+- my $buf = '';
+- open my $fh, '>', \$buf
+- or die "Couldn't open scalar for writing: $!";
+- binmode $fh, ":raw";
+- $decoder->decode($io, $fh);
+- close $fh or die "Couldn't close scalar: $!";
+-
+- open $fh, '<', \$buf
+- or die "Couldn't re-open scalar for reading: $!";
+- binmode $fh, ":raw";
+- $io = $fh;
+- 1;
+- } or do {
+- $RT::Logger->error("Couldn't decode body: $@");
+- }
++ # not protected itself, look inside
++ push @res, $self->FindProtectedParts(
++ %args, Entity => $_, Scattered => 0,
++ ) foreach grep !$args{'Skip'}{$_}, $entity->parts;
++
++ if ( $args{'Scattered'} ) {
++ my %parent;
++ my $filter; $filter = sub {
++ $parent{$_[0]} = $_[1];
++ unless ( $_[0]->is_multipart ) {
++ return () if $args{'Skip'}{$_[0]};
++ return $_[0];
}
- }
+- }
++ return map $filter->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts;
++ };
++ my @parts = $filter->($entity);
++ return @res unless @parts;
- while ( defined($_ = $io->getline) ) {
- next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
@@ -48,22 +102,39 @@
- Format => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
- Data => $entity,
- };
-- }
++ my @list = $self->FindScatteredParts( Parts => \@parts, Parents => \%parent, Skip => $args{'Skip'} );
++ if (@list) {
++ push @res, @list;
++ @parts = grep !$args{'Skip'}{$_}, @parts;
+ }
- $io->close;
-+
-+ # RFC3156, multipart/{signed,encrypted}
-+ my $type = $entity->effective_type;
-+ return () unless $type = =~ /^multipart\/(?:encrypted|signed)$/;
-+
-+ unless ( $entity->parts == 2 ) {
-+ $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
- return ();
+- return ();
}
-- # RFC3156, multipart/{signed,encrypted}
++ return @res;
++}
++
++sub CheckIfProtected {
++ my $self = shift;
++ my %args = ( Entity => undef, @_ );
++
++ my $entity = $args{'Entity'};
++
++ # we check inline PGP block later in another sub
++ return () unless $entity->is_multipart;
++
+ # RFC3156, multipart/{signed,encrypted}
- if ( ( my $type = $entity->effective_type ) =~ /^multipart\/(?:encrypted|signed)$/ ) {
- unless ( $entity->parts == 2 ) {
- $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
++ my $type = $entity->effective_type;
++ return () unless $type =~ /^multipart\/(?:encrypted|signed)$/;
++
++ unless ( $entity->parts == 2 ) {
++ $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
++ return ();
++ }
++
+ my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
+ unless ( $protocol ) {
+ $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
@@ -104,18 +175,19 @@
+ return ();
+}
+
++
+sub FindScatteredParts {
+ my $self = shift;
+ my %args = ( Parts => [], Skip => {}, @_ );
++
++ my @res;
++
++ my @parts = @{ $args{'Parts'} };
- if ( $type eq 'multipart/encrypted' ) {
- unless ( $protocol eq 'application/pgp-encrypted' ) {
- $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
- return ();
-+ my @res;
-+
-+ my @parts = @{ $args{'Parts'} };
-+
+ # attachments signed with signature in another part
+ {
+ my @file_indices;
@@ -187,9 +259,19 @@
- }
- elsif ( $fname =~ /\.sig$/i && $part->effective_type eq 'application/octet-stream' ) {
- push @file_indices, $i;
-- }
-- }
+ $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
++
++ $args{'Skip'}{$data_part_in} = 1;
++ $args{'Skip'}{$sig_part} = 1;
++ push @res, {
++ Type => 'signed',
++ Format => 'Attachment',
++ Top => $args{'Parents'}{$data_part_in},
++ Data => $data_part_in,
++ Signature => $sig_part,
++ };
+ }
+ }
- my (@res, %skip);
- foreach my $i ( @file_indices ) {
@@ -205,30 +287,34 @@
- unless ( defined $data_part_idx ) {
- $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
- next;
-+ $args{'Skip'}{$data_part_in} = 1;
-+ $args{'Skip'}{$sig_part} = 1;
-+ push @res, {
-+ Type => 'signed',
-+ Format => 'Attachment',
-+ Top => $entity,
-+ Data => $data_part_in,
-+ Signature => $sig_part,
-+ };
- }
+- }
- my $data_part_in = $entity->parts($data_part_idx);
--
++ # attachments with inline encryption
++ foreach my $part ( @parts ) {
++ next if $args{'Skip'}{$part};
++
++ my $fname = $part->head->recommended_filename || '';
++ next unless $fname =~ /\.${RE_FILE_EXTENSIONS}$/;
+
- $skip{"$data_part_in"}++;
- $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
-- push @res, {
++ $RT::Logger->debug("Found encrypted attachment '$fname'");
++
++ $args{'Skip'}{$part} = 1;
+ push @res, {
- Type => 'signed',
- Format => 'Attachment',
- Top => $entity,
- Data => $data_part_in,
- Signature => $sig_part,
-- };
++ Type => 'encrypted',
++ Format => 'Attachment',
++ Top => $args{'Parents'}{$part},
++ Data => $part,
+ };
}
- # attachments with inline encryption
+- # attachments with inline encryption
- my @encrypted_indices =
- grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.${RE_FILE_EXTENSIONS}$/}
- 0 .. $entity->parts - 1;
@@ -237,44 +323,30 @@
- my $part = $entity->parts($i);
- $skip{"$part"}++;
- $RT::Logger->debug("Found encrypted attachment '". $part->head->recommended_filename ."'");
++ # inline PGP block
+ foreach my $part ( @parts ) {
+ next if $args{'Skip'}{$part};
+
-+ my $fname = $part->head->recommended_filename || '';
-+ next unless $fname =~ /\.${RE_FILE_EXTENSIONS}$/;
-+
-+ $RT::Logger->debug("Found encrypted attachment '$fname'");
++ my $type = $self->_CheckIfProtectedInline( $part );
++ next unless $type;
++
++ my $file = ($part->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
+
+ $args{'Skip'}{$part} = 1;
push @res, {
- Type => 'encrypted',
- Format => 'Attachment',
-+ Type => 'encrypted',
-+ Format => 'Attachment',
- Top => $entity,
- Data => $part,
+- Top => $entity,
+- Data => $part,
++ Type => $type,
++ Format => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
++ Data => $part,
};
}
-- push @res, FindProtectedParts( Entity => $_ )
+- push @res, $self->FindProtectedParts( Entity => $_ )
- foreach grep !$skip{"$_"}, $entity->parts;
-+ # inline PGP block
-+ foreach my $part ( @parts ) {
-+ next if $args{'Skip'}{$part};
-+
-+ my $type = $self->_CheckIfProtectedInline( $entity );
-+ next unless $type;
-+
-+ my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
-+
-+ $args{'Skip'}{$part} = 1;
-+ push @res, {
-+ Type => $type,
-+ Format => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
-+ Data => $entity,
-+ };
-+ }
-
+-
return @res;
}
@@ -287,6 +359,33 @@
+ $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
+ return '';
+ }
++
++ # Deal with "partitioned" PGP mail, which (contrary to common
++ # sense) unnecessarily applies a base64 transfer encoding to PGP
++ # mail (whose content is already base64-encoded).
++ if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
++ my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
++ if ($decoder) {
++ local $@;
++ eval {
++ my $buf = '';
++ open my $fh, '>', \$buf
++ or die "Couldn't open scalar for writing: $!";
++ binmode $fh, ":raw";
++ $decoder->decode($io, $fh);
++ close $fh or die "Couldn't close scalar: $!";
++
++ open $fh, '<', \$buf
++ or die "Couldn't re-open scalar for reading: $!";
++ binmode $fh, ":raw";
++ $io = $fh;
++ 1;
++ } or do {
++ $RT::Logger->error("Couldn't decode body: $@");
++ }
++ }
++ }
++
+ while ( defined($_ = $io->getline) ) {
+ next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
+ return $1? 'signed': 'encrypted';
15: d42a1a1 < ---: ------- VerifyDecrypt works with one part only now
16: c7bd48b < ---: ------- make sure Auth::Crypt is used as mail plugin
17: 3027b9a < ---: ------- Auth::Crypt mail plugin out of Auth::GnuPG
---: ------- > 22: d24d92e Move FindProtectedParts into RT::Crypt
30: 8a81cae ! 23: 621a397 Unclaimed multipart/signed or multipart/encrypted parts should be skipped
@@ -1,6 +1,10 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- don't recurse into multipart/{signed,encrypted} messages
+ Unclaimed multipart/signed or multipart/encrypted parts should be skipped
+
+ If the initial CheckIfProtected calls do not claim a multipart/signed or
+ multipart/encrypted part, prune its sub-parts from being passed to
+ FindScatteredParts.
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
--- a/lib/RT/Crypt.pm
@@ -16,9 +20,7 @@
+ return ();
+ }
+
-+ my @res;
-+
+ my @res;
+
# not protected itself, look inside
- push @res, $self->FindProtectedParts(
- %args, Entity => $_, Scattered => 0,
33: 18776f1 ! 24: 23c6432 Assume multipart/{signed,encrypted} parts may be GPG-encrypted
@@ -1,6 +1,9 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- handle multipart/{singned,encrypted} without protocol set
+ Assume multipart/{signed,encrypted} parts may be GPG-encrypted
+
+ If the protocol is unset on a multipart/signed or multipart/encrypted
+ part, assume that it may be GPG and check for GPG inline markers.
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
@@ -51,8 +54,8 @@
my $io = $entity->open('r');
unless ( $io ) {
@@
- return '';
}
+
while ( defined($_ = $io->getline) ) {
- next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
- return $1? 'signed': 'encrypted';
@@ -66,3 +69,37 @@
$io->close;
return '';
+diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
+--- a/t/mail/crypt-gnupg.t
++++ b/t/mail/crypt-gnupg.t
+@@
+ qw/data gnupg keyrings/ );
+ }
+
+-use RT::Test::GnuPG tests => 96, gnupg_options => { homedir => $homedir };
++use RT::Test::GnuPG tests => 100, gnupg_options => { homedir => $homedir };
+ use Test::Warn;
+
++use_ok('RT::Crypt');
+ use_ok('MIME::Entity');
+
+ diag 'only signing. correct passphrase';
+@@
+ $entity->head->mime_attr( 'Content-Type.protocol' => undef );
+
+ my @parts;
+- warning_like {
+- @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
+- } qr{Entity is 'multipart/encrypted', but has no protocol defined. Skipped};
+-
+- is( scalar @parts, 0, 'no protected parts' );
++ warning_like { @parts = RT::Crypt->FindProtectedParts( Entity => $entity ) }
++ qr{Entity is 'multipart/encrypted', but has no protocol defined. Checking for PGP part};
++ is( scalar @parts, 1, 'one protected part' );
++ is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
++ is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
++ is( $parts[0]->{'Top'}, $entity, "it's the same entity" );
+ }
+
+ diag 'wrong signed/encrypted parts: not enought parts';
+
---: ------- > 25: cf8ee45 Move VerifyDecrypt to dispatch from RT::Crypt
---: ------- > 26: acdfefb Fix which part is labelled "Top" in signature attachments
---: ------- > 27: 9dca0e0 Remove Top argument from where it is not needed
---: ------- > 28: f24b69f Merge two identical AddStatus/SetStatus blocks into one
---: ------- > 29: 24f5c2a Remove unused Detach argument
---: ------- > 30: 8c935dc Remove unnecessary passing of SetStatus to VerifyRFC3156
---: ------- > 31: bd32d0a Remove AddStatus/SetStatus arguments to VerifyDecrypt
---: ------- > 32: e5db507 Move status header setting into RT::Crypt
---: ------- > 33: 73d06cf Move alteration of Top component into Verify/Decrypt methods
---: ------- > 34: 5a31c00 Move ParseStatus to dispatch from RT::Crypt
---: ------- > 35: 084345c Move key retrieval to dispatch from RT::Crypt
---: ------- > 36: a397c66 Refactor UseKeyFor* and GetKeysFor* for generic use
---: ------- > 37: 3522a04 Add Protocol information to GetPublicKeyInfo call
74: a33f42b ! 38: f0a5142 Move DrySign into RT::Crypt
@@ -1,18 +1,26 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- move DrySign into Crypt::Base
+ Move DrySign into RT::Crypt
-diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
---- a/lib/RT/Crypt/Base.pm
-+++ b/lib/RT/Crypt/Base.pm
+diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
+--- a/lib/RT/Crypt.pm
++++ b/lib/RT/Crypt.pm
@@
- return (exit_code => 1, status => []);
+ return %res;
}
++=head2 DrySign Signer => KEY
++
++Signs a small message with the key, to make sure the key exists and we
++have a useable passphrase. The Signer argument MUST be a key identifier
++of the signer: either email address, key id or finger print.
++
++Returns a true value if all went well.
++
++=cut
++
+sub DrySign {
+ my $self = shift;
-+ my %args = ( Signer => undef, @_ );
-+ my $from = $args{'Signer'};
+
+ my $mime = MIME::Entity->build(
+ Type => "text/plain",
@@ -23,30 +31,39 @@
+ );
+
+ my %res = $self->SignEncrypt(
++ @_,
+ Sign => 1,
+ Encrypt => 0,
+ Entity => $mime,
-+ Signer => $from,
+ );
+
+ return $res{exit_code} == 0;
+}
+
- sub CheckIfProtected { return () }
+ =head2 VerifyDecrypt Entity => ENTITY [, Passphrase => undef ]
- sub FindScatteredParts { return () }
+ Locates all protected parts of the L<MIME::Entity> object C<ENTITY>, as
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@
+ );
+ }
- =cut
-
+-=head2 KEY
+-
+-Signs a small message with the key, to make sure the key exists and
+-we have a useable passphrase. The first argument MUST be a key identifier
+-of the signer: either email address, key id or finger print.
+-
+-Returns a true value if all went well.
+-
+-=cut
+-
-sub DrySign {
- my $self = shift;
-- my %args = ( Signer => undef, @_ );
-- my $from = $args{'Signer'};
+- my $from = shift;
-
- my $mime = MIME::Entity->build(
- Type => "text/plain",
@@ -72,3 +89,16 @@
This routine returns true if RT's GnuPG support is configured and working
+diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
+--- a/share/html/Elements/GnuPG/SignEncryptWidget
++++ b/share/html/Elements/GnuPG/SignEncryptWidget
+@@
+ if ($address ne $private and $address ne $queue) {
+ push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
+ $checks_failure = 1;
+- } elsif ( not RT::Crypt::GnuPG->DrySign( $address ) ) {
++ } elsif ( not RT::Crypt->DrySign( Signer => $address ) ) {
+ push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
+ $checks_failure = 1;
+ } else {
+
67: 9003549 ! 39: b65c6eb move ParseDate method into RT::Crypt::Role to allow re-use
@@ -1,33 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- move ParseDate method upper so SMIME can re-use it
-
-diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
---- a/lib/RT/Crypt/Base.pm
-+++ b/lib/RT/Crypt/Base.pm
-@@
- );
- }
-
-+sub ParseDate {
-+ my $self = shift;
-+ my $value = shift;
-+
-+ # never
-+ return $value unless $value;
-+
-+ require RT::Date;
-+ my $obj = RT::Date->new( RT->SystemUser );
-+ # unix time
-+ if ( $value =~ /^\d+$/ ) {
-+ $obj->Set( Value => $value );
-+ } else {
-+ $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
-+ }
-+ return $obj;
-+}
-+
- 1;
+ move ParseDate method into RT::Crypt::Role to allow re-use
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
@@ -55,7 +28,7 @@
@info{ qw(Trust Created Expire String) }
= (split /:/, $line)[0,4,5,8];
- $info{ $_ } = _ParseDate( $info{ $_ } )
-+ $info{'Validity'}{ $_ } = $self->ParseDate( $info{ $_ } )
++ $info{ $_ } = $self->ParseDate( $info{ $_ } )
foreach qw(Created Expire);
push @{ $res[-1]{'User'} ||= [] }, \%info;
}
@@ -83,3 +56,38 @@
my $self = shift;
my $key = shift;
+diff --git a/lib/RT/Crypt/Role.pm b/lib/RT/Crypt/Role.pm
+--- a/lib/RT/Crypt/Role.pm
++++ b/lib/RT/Crypt/Role.pm
+@@
+
+ requires 'GetKeysForSigning';
+
++=head2 ParseDate STRING
++
++Takes a string, and parses and returns a L<RT::Date>; if the string is
++purely numeric, assumes is a epoch timestamp.
++
++=cut
++
++sub ParseDate {
++ my $self = shift;
++ my $value = shift;
++
++ # never
++ return $value unless $value;
++
++ require RT::Date;
++ my $obj = RT::Date->new( RT->SystemUser );
++ # unix time
++ if ( $value =~ /^\d+$/ ) {
++ $obj->Set( Value => $value );
++ } else {
++ $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
++ }
++ return $obj;
++}
++
++
+ 1;
+
65: fbd755b ! 40: 30beaa8 Move CheckRecipients into RT::Crypt
@@ -1,13 +1,13 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- move CheckRecipients into RT::Crypt
+ Move CheckRecipients into RT::Crypt
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@
- return ();
- } }
+
+ =cut
+sub CheckRecipients {
+ my $self = shift;
@@ -77,7 +77,7 @@
+
sub GetKeysForEncryption {
my $self = shift;
- my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
+ my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ );
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
--- a/lib/RT/Crypt/GnuPG.pm
@@ -156,3 +156,16 @@
my $self = shift;
my %args = (
+diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
+--- a/share/html/Elements/GnuPG/SignEncryptWidget
++++ b/share/html/Elements/GnuPG/SignEncryptWidget
+@@
+ keys %$self
+ );
+
+- my ($status, @issues) = RT::Crypt::GnuPG->CheckRecipients( @recipients );
++ my ($status, @issues) = RT::Crypt->CheckRecipients( @recipients );
+ push @{ $self->{'GnuPGRecipientsKeyIssues'} ||= [] }, @issues;
+ $checks_failure = 1 unless $status;
+ }
+
18: 1f2bdcf ! 41: 86d3d32 Move logic from RT::Interface::Email::Auth::GnuPG into ::Crypt
@@ -1,44 +1,288 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- shrink lib/RT/Interface/Email/Auth/Crypt.pm to shortcut
+ Move logic from RT::Interface::Email::Auth::GnuPG into ::Crypt
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
---- a/lib/RT/Interface/Email/Auth/Crypt.pm
+new file mode 100644
+--- /dev/null
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@
- Entity => $args{'Message'}, AddStatus => 1,
- );
- if ( $status && !@res ) {
-- $args{'Message'}->head->add(
++# BEGIN BPS TAGGED BLOCK {{{
++#
++# COPYRIGHT:
++#
++# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
++# <sales at bestpractical.com>
++#
++# (Except where explicitly superseded by other copyright notices)
++#
++#
++# LICENSE:
++#
++# This work is made available to you under the terms of Version 2 of
++# the GNU General Public License. A copy of that license should have
++# been provided with this software, but in any event can be snarfed
++# from www.gnu.org.
++#
++# This work is distributed in the hope that it will be useful, but
++# WITHOUT ANY WARRANTY; without even the implied warranty of
++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++# General Public License for more details.
++#
++# You should have received a copy of the GNU General Public License
++# along with this program; if not, write to the Free Software
++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
++# 02110-1301 or visit their web page on the internet at
++# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
++#
++#
++# CONTRIBUTION SUBMISSION POLICY:
++#
++# (The following paragraph is not intended to limit the rights granted
++# to you to modify and distribute this software under the terms of
++# the GNU General Public License and is only of importance to you if
++# you choose to contribute your changes and enhancements to the
++# community by submitting them to Best Practical Solutions, LLC.)
++#
++# By intentionally submitting any modifications, corrections or
++# derivatives to this work, or any other work intended for use with
++# Request Tracker, to Best Practical Solutions, LLC, you confirm that
++# you are the copyright holder for those contributions and you grant
++# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
++# royalty-free, perpetual, license to use, copy, create derivative
++# works based on those contributions, and sublicense and distribute
++# those contributions and any derivatives thereof.
++#
++# END BPS TAGGED BLOCK }}}
++
++package RT::Interface::Email::Auth::Crypt;
++
++use strict;
++use warnings;
++
++=head1 NAME
++
++RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
++
++=head2 DESCRIPTION
++
++This mail plugin decrypts and verifies incoming emails. Supported
++encryption protocols are GnuPG and SMIME.
++
++This code is independant from code that encrypts/sign outgoing emails, so
++it's possible to decrypt data without bringing in encryption. To enable
++it put the module in the mail plugins list:
++
++ Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filters...);
++
++=head3 GnuPG
++
++To use the gnupg-secured mail gateway, you need to do the following:
++
++Set up a GnuPG key directory with a pubring containing only the keys
++you care about and specify the following in your SiteConfig.pm
++
++ Set(%GnuPGOptions, homedir => '/opt/rt4/var/data/GnuPG');
++
++Read also: L<RT::Crypt> and L<RT::Crypt::GnuPG>.
++
++=head3 SMIME
++
++Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
++
++=cut
++
++sub ApplyBeforeDecode { return 1 }
++
++use RT::Crypt;
++use RT::EmailParser ();
++
++sub GetCurrentUser {
++ my %args = (
++ Message => undef,
++ RawMessageRef => undef,
++ @_
++ );
++
++ foreach my $p ( $args{'Message'}->parts_DFS ) {
++ $p->head->delete($_) for qw(
++ X-RT-GnuPG-Status X-RT-Incoming-Encryption
++ X-RT-Incoming-Signature X-RT-Privacy
++ X-RT-Sign X-RT-Encrypt
++ );
++ }
++
++ my $msg = $args{'Message'}->dup;
++
++ my ($status, @res) = VerifyDecrypt(
++ Entity => $args{'Message'},
++ );
++ if ( $status && !@res ) {
+ $args{'Message'}->head->replace(
- 'X-RT-Incoming-Encryption' => 'Not encrypted'
- );
- return 1;
-@@
- $decrypted = 1;
- }
- if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
-- $part->head->add(
++ 'X-RT-Incoming-Encryption' => 'Not encrypted'
++ );
++
++ return 1;
++ }
++
++ # FIXME: Check if the message is encrypted to the address of
++ # _this_ queue. send rejecting mail otherwise.
++
++ unless ( $status ) {
++ $RT::Logger->error("Had a problem during decrypting and verifying");
++ my $reject = HandleErrors( Message => $args{'Message'}, Result => \@res );
++ return (0, 'rejected because of problems during decrypting and verifying')
++ if $reject;
++ }
++
++ # attach the original encrypted message
++ $args{'Message'}->attach(
++ Type => 'application/x-rt-original-message',
++ Disposition => 'inline',
++ Data => ${ $args{'RawMessageRef'} },
++ );
++
++ $args{'Message'}->head->replace( 'X-RT-Privacy' => 'PGP' );
++
++ foreach my $part ( $args{'Message'}->parts_DFS ) {
++ my $decrypted;
++
++ my $status = $part->head->get( 'X-RT-GnuPG-Status' );
++ if ( $status ) {
++ for ( RT::Crypt::GnuPG->ParseStatus( $status ) ) {
++ if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
++ $decrypted = 1;
++ }
++ if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
+ $part->head->replace(
- 'X-RT-Incoming-Signature' => $_->{UserString}
- );
- }
- }
- }
-
-- $part->head->add(
++ 'X-RT-Incoming-Signature' => $_->{UserString}
++ );
++ }
++ }
++ }
++
+ $part->head->replace(
- 'X-RT-Incoming-Encryption' =>
- $decrypted ? 'Success' : 'Not encrypted'
- );
- }
-
- my %seen;
-- $args{'Message'}->head->add( 'X-RT-Privacy' => $_ )
-+ $args{'Message'}->head->replace( 'X-RT-Privacy' => $_ )
- foreach grep !$seen{$_}++, @found;
-
- return 1;
++ 'X-RT-Incoming-Encryption' =>
++ $decrypted ? 'Success' : 'Not encrypted'
++ );
++ }
++
++ return 1;
++}
++
++sub HandleErrors {
++ my %args = (
++ Message => undef,
++ Result => [],
++ @_
++ );
++
++ my $reject = 0;
++
++ my %sent_once = ();
++ foreach my $run ( @{ $args{'Result'} } ) {
++ my @status = RT::Crypt::GnuPG->ParseStatus( $run->{'status'} );
++ unless ( $sent_once{'NoPrivateKey'} ) {
++ unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
++ $sent_once{'NoPrivateKey'}++;
++ $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnMissingPrivateKey'};
++ }
++ }
++ unless ( $sent_once{'BadData'} ) {
++ unless ( CheckBadData( Message => $args{'Message'}, Status => \@status ) ) {
++ $sent_once{'BadData'}++;
++ $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnBadData'};
++ }
++ }
++ }
++ return $reject;
++}
++
++sub CheckNoPrivateKey {
++ my %args = (Message => undef, Status => [], @_ );
++ my @status = @{ $args{'Status'} };
++
++ my @decrypts = grep $_->{'Operation'} eq 'Decrypt', @status;
++ return 1 unless @decrypts;
++ foreach my $action ( @decrypts ) {
++ # if at least one secrete key exist then it's another error
++ return 1 if
++ grep !$_->{'User'}{'SecretKeyMissing'},
++ @{ $action->{'EncryptedTo'} };
++ }
++
++ $RT::Logger->error("Couldn't decrypt a message: have no private key");
++
++ my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
++ my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
++ To => $address,
++ Template => 'Error: no private key',
++ Arguments => {
++ Message => $args{'Message'},
++ TicketObj => $args{'Ticket'},
++ },
++ InReplyTo => $args{'Message'},
++ );
++ unless ( $status ) {
++ $RT::Logger->error("Couldn't send 'Error: no private key'");
++ }
++ return 0;
++}
++
++sub CheckBadData {
++ my %args = (Message => undef, Status => [], @_ );
++ my @bad_data_messages =
++ map $_->{'Message'},
++ grep $_->{'Status'} ne 'DONE' && $_->{'Operation'} eq 'Data',
++ @{ $args{'Status'} };
++ return 1 unless @bad_data_messages;
++
++ $RT::Logger->error("Couldn't process a message: ". join ', ', @bad_data_messages );
++
++ my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
++ my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
++ To => $address,
++ Template => 'Error: bad GnuPG data',
++ Arguments => {
++ Messages => [ @bad_data_messages ],
++ TicketObj => $args{'Ticket'},
++ },
++ InReplyTo => $args{'Message'},
++ );
++ unless ( $status ) {
++ $RT::Logger->error("Couldn't send 'Error: bad GnuPG data'");
++ }
++ return 0;
++}
++
++sub VerifyDecrypt {
++ my %args = (
++ Entity => undef,
++ @_
++ );
++
++ my @res = RT::Crypt->VerifyDecrypt( %args );
++ unless ( @res ) {
++ $RT::Logger->debug("No more encrypted/signed parts");
++ return 1;
++ }
++
++ $RT::Logger->debug('Found GnuPG protected parts');
++
++ # return on any error
++ if ( grep $_->{'exit_code'}, @res ) {
++ $RT::Logger->debug("Error during verify/decrypt operation");
++ return (0, @res);
++ }
++
++ # nesting
++ my ($status, @nested) = VerifyDecrypt( %args );
++ return $status, @res, @nested;
++}
++
++RT::Base->_ImportOverlays();
++
++1;
diff --git a/lib/RT/Interface/Email/Auth/GnuPG.pm b/lib/RT/Interface/Email/Auth/GnuPG.pm
--- a/lib/RT/Interface/Email/Auth/GnuPG.pm
@@ -54,11 +298,11 @@
sub ApplyBeforeDecode { return 1 }
--use RT::Crypt::GnuPG;
+-use RT::Crypt;
-use RT::EmailParser ();
+use RT::Interface::Email::Auth::Crypt;
--sub GetCurrentUser {
+ sub GetCurrentUser {
- my %args = (
- Message => undef,
- RawMessageRef => undef,
@@ -76,7 +320,7 @@
- my $msg = $args{'Message'}->dup;
-
- my ($status, @res) = VerifyDecrypt(
-- Entity => $args{'Message'}, AddStatus => 1,
+- Entity => $args{'Message'},
- );
- if ( $status && !@res ) {
- $args{'Message'}->head->replace(
@@ -110,7 +354,7 @@
-
- my $status = $part->head->get( 'X-RT-GnuPG-Status' );
- if ( $status ) {
-- for ( RT::Crypt::GnuPG::ParseStatus( $status ) ) {
+- for ( RT::Crypt::GnuPG->ParseStatus( $status ) ) {
- if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
- $decrypted = 1;
- }
@@ -142,7 +386,7 @@
-
- my %sent_once = ();
- foreach my $run ( @{ $args{'Result'} } ) {
-- my @status = RT::Crypt::GnuPG::ParseStatus( $run->{'status'} );
+- my @status = RT::Crypt::GnuPG->ParseStatus( $run->{'status'} );
- unless ( $sent_once{'NoPrivateKey'} ) {
- unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
- $sent_once{'NoPrivateKey'}++;
@@ -222,7 +466,7 @@
- @_
- );
-
-- my @res = RT::Crypt::GnuPG::VerifyDecrypt( %args );
+- my @res = RT::Crypt->VerifyDecrypt( %args );
- unless ( @res ) {
- $RT::Logger->debug("No more encrypted/signed parts");
- return 1;
@@ -239,9 +483,23 @@
- # nesting
- my ($status, @nested) = VerifyDecrypt( %args );
- return $status, @res, @nested;
--}
-+sub GetCurrentUser { return RT::Interface::Email::Auth::Crypt::GetCurrentUser(@_) }
++ return RT::Interface::Email::Auth::Crypt::GetCurrentUser( @_ );
+ }
RT::Base->_ImportOverlays();
-
+diff --git a/share/html/Elements/ShowGnuPGStatus b/share/html/Elements/ShowGnuPGStatus
+--- a/share/html/Elements/ShowGnuPGStatus
++++ b/share/html/Elements/ShowGnuPGStatus
+@@
+ return (0, "Couldn't parse content of attachment #". $original->id);
+ }
+
+- use RT::Interface::Email::Auth::GnuPG;
+- my ($status, @res) = RT::Interface::Email::Auth::GnuPG::VerifyDecrypt( Entity => $entity );
++ use RT::Interface::Email::Auth::Crypt;
++ my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
+ if ( $status && !@res ) {
+ # imposible in this situation
+ return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
+
19: bfea306 < ---: ------- abstracting and maintaining currently used protocol
20: 2d27d0d < ---: ------- ParseStatus in RT::Crypt
21: 8aa3347 < ---: ------- more on methods instead of functions
22: 45556ff < ---: ------- more conversions from funstions to class methods
23: 8cc7aca < ---: ------- when we SignEncrypt store protocol in results
24: 1554a6a < ---: ------- switch to using class methods, for example generic ParseStatus
25: 3529aa4 < ---: ------- convert t/mail/crypt-gnupg.t over new API
26: 46ce34c < ---: ------- partialy convert ShowGnuPGStatus over new API
27: 6698157 < ---: ------- more on ::ParseStatus -> ->ParseStatus
28: 5912857 < ---: ------- syntax error fixes
29: 1c46a51 < ---: ------- RT::Crypt->LoadImplementation method
31: 09467a4 < ---: ------- pass parents into FindScatteredParts
32: 431cca9 < ---: ------- set Protocol when we find scattered part(s)
34: cdd23e3 < ---: ------- without parents we can not complete detection of some parts
35: 0f985c4 < ---: ------- adjust tests, now we support additional detection
---: ------- > 42: 602dc5a Warn about Auth::GnuPG and Auth::SMIME MailPlugins, and switch to Auth::Crypt
---: ------- > 43: f81211a Remove unnecessary GnuPG disabling during testing
---: ------- > 44: dad0e76 Generalize RT::Interface::Email::Auth::Crypt for multiple protocols
---: ------- > 45: 64a9b43 By default, VerifyDecrypt should iterate to fixed-point
---: ------- > 46: 1c68fcb Generalize GnuPG re-verification
---: ------- > 47: 71a39b4 Remove an unused variable
---: ------- > 48: 45002d0 Abstract out a general Crypt setting, and split incoming and outgoing
---: ------- > 49: c6d664d Handle if the incoming protocol is but a scalar
---: ------- > 50: 32147d9 Move GnuPG enabling/disabling to GnuPG PostLoadCheck
---: ------- > 51: b4b6f18 Move canonicalization of GnuPG homedir to PostLoadCheck
---: ------- > 52: 9aed02a Ensure that RT->Config->Options returns keys in consistent order
---: ------- > 53: d5c6da1 Add "Probe" as a requirement of RT::Crypt::Role
---: ------- > 54: e03e44b Allow safe_run_child to run before ConnectToDatabase runs
---: ------- > 55: 8b9d934 Genericize loading and ->Probe of RT::Crypt::* classes during PostLoadCheck
154: 748e2cf ! 56: 9cf1908 Don't load crypt implementations upon RT::Crypt load
@@ -1,19 +1,19 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- don't load crypt implementations right away
+ Don't load crypt implementations upon RT::Crypt load
- loading GnuPG module fails if you don't have required
- modules
+ Loading RT::Crypt::GnuPG fails if the optional depedencies are not met,
+ for example. As RT::Crypt is always loaded, delay loading of the
+ implementation class until we know that we are enabling it.
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@
-
package RT::Crypt;
+ use 5.010;
-require RT::Crypt::GnuPG;
--require RT::Crypt::SMIME;
-
=head1 NAME
---: ------- > 57: 12bd3b0 Drop extraneous "require RT::Crypt" lines
---: ------- > 58: 66826e7 Switch iterations over all protocols to merely enabled ones
---: ------- > 59: 90d1eef Place Passphrase configuration on individual configurations
---: ------- > 60: a7b88b0 Move RejectOnMissingPrivateKey and RejectOnBadData to generic Crypt settings
135: 35a7130 ! 61: c0e3f4b Ensure that ContentType is only updated after successful encryption/decryption
@@ -1,10 +1,19 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- delay setting content type until all error checks are passed
+ Ensure that ContentType is only updated after successful encryption/decryption
diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
+@@
+ return (1, $self->loc('Already encrypted'));
+ } elsif ( $type =~ /^multipart\//i ) {
+ return (1, $self->loc('No need to encrypt'));
+- } else {
+- $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
+ }
+
+ my $queue = $txn->TicketObj->QueueObj;
@@
return (0, $self->loc('No key suitable for encryption'));
}
@@ -13,12 +22,14 @@
- $self->SetHeader( 'Content-Type' => $type );
-
my $content = $self->Content;
- my %res = RT::Crypt->SignEncryptContent(
+ my %res = RT::Crypt::GnuPG->SignEncryptContent(
Content => \$content,
@@
unless ( $status ) {
return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
}
++
++ $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
+ $self->__Set( Field => 'ContentType', Value => $type );
+ $self->SetHeader( 'Content-Type' => $type );
+
@@ -33,7 +44,7 @@
- $self->SetHeader( 'Content-Type' => $type );
my $content = $self->Content;
- my %res = RT::Crypt->DecryptContent( Content => \$content );
+ my %res = RT::Crypt::GnuPG->DecryptContent( Content => \$content, );
@@
unless ( $status ) {
return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
---: ------- > 62: 086b633 Refactor encryption of attachment content into the role, and move config
---: ------- > 63: c7cfc5a Switch to generic Crypt checks instead of GnuPG
---: ------- > 64: 79e541e All outgoing defaults should default to UseForOutgoing, not GnuPG
---: ------- > 65: f44e951 Fix a typo in a comment
52: 24cbe5e ! 66: 5dd001a Don't report unsafe permissions on gpg tests
@@ -1,6 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- don't report unsafe permissions on gpg tests
+ Don't report unsafe permissions on gpg tests
diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
--- a/t/mail/gnupg-incoming.t
86: 20f6b23 ! 67: bc0e44b One entity may have information about multiple crypt runs
@@ -1,6 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- one entity may have information about multiple crypt runs
+ One entity may have information about multiple crypt runs
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
---: ------- > 68: 34ac88b Rename "bad data" template to not be GnuPG-specific
---: ------- > 69: 7f3785e Move non-GPG specific docs to RT::Crypt
---: ------- > 70: 82e3e61 Wording fixes for RT::Crypt::GnuPG documentation
36: dc71bc5 ! 71: e854b0c Add a 'configure' option to enable SMIME support
@@ -1,6 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- add option --enable-smime
+ Add a 'configure' option to enable SMIME support
diff --git a/configure.ac b/configure.ac
--- a/configure.ac
@@ -11,7 +11,7 @@
+dnl RT's SMIME support
+AC_CHECK_PROG([RT_SMIME], [openssl], "yes", "no")
-+AC_ARG_ENABLE(gpg,
++AC_ARG_ENABLE(smime,
+ AC_HELP_STRING([--enable-smime],
+ [Turns on Secure MIME (SMIME) support]),
+ RT_SMIME=$enableval)
37: dec2fd8 < ---: ------- bring new config options required for SMIM and genric interface
38: bb2f597 < ---: ------- check old and new crypt related config options
39: da3496f < ---: ------- base class for cryptography protocols
40: c184eb2 < ---: ------- change cryptography config
41: 6b09e47 < ---: ------- minor GnuPG changes
42: 39718d5 < ---: ------- config based methods in RT::Crypt
43: eca79bd < ---: ------- return asap if there is no parts for scattered search
44: d5d8bdd < ---: ------- generic crypt interface for keys information retrieval
45: 59bbcfe < ---: ------- clean GnuPG's keys info implementation
46: 3ed0f64 < ---: ------- in Admin interface show keys info for every enabled protocol
47: 94254af < ---: ------- variouse fixes in web ui regarding crypto changes
48: 8b649de < ---: ------- update tests
49: a4a38f8 < ---: ------- refactor UseKeyFor* and GetKeysFor* for generic use
50: a2b4c59 < ---: ------- missing semicolon
51: 4bb971a < ---: ------- fix parts_DFS like filter we have, we need only leafs
53: b550a4e < ---: ------- s/Incomming/Incoming/
---: ------- > 72: 782b13f Add the skeleton of SMIME support, in RT::Crypt::SMIME
---: ------- > 73: f751318 SMIME: Store the path to openssl in a configuration option
---: ------- > 74: 7825d71 SMIME: probe for openssl existance, and smime subcommand
---: ------- > 75: 5ccb49b SMIME: part detection
---: ------- > 76: 80e63c5 SMIME: Format status into headers
---: ------- > 77: f1b1536 SMIME: Read and parse key content from a Keyring directory
54: 02e8403 ! 78: a5383fa SMIME: Ensure that the keyring path is absolute, and exists
@@ -1,6 +1,14 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- PostLoadCheck for SMIME config option
+ SMIME: Ensure that the keyring path is absolute, and exists
+
+ If it does not exist, verification and outgoing encryption (anything
+ that does not require a private key) is possible; as such, warn but do
+ not disable.
+
+ Because PostLoadChecks for SMIME and GnuPG may run more than once (as
+ called by the Crypt PostLoadCheck), also ensure that Keyring warning
+ does not occur more than once.
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
--- a/lib/RT/Config.pm
@@ -17,27 +25,21 @@
+ my $opt = $self->Get('SMIME');
+ return unless $opt->{'Enable'};
+
-+ my $path = $opt->{'OpenSSL'};
-+ if ( defined $path ) {
-+ unless ( -e $path && -x _) {
-+ $RT::Logger->debug(
-+ "'$path' doesn't exist or is not an executable."
-+ ." Please change SMIME->OpenSSL option."
-+ ." SMIME support has been disabled"
-+ );
-+ $opt->{'Enable'} = 0;
-+ return;
++ if (exists $opt->{Keyring}) {
++ unless ( File::Spec->file_name_is_absolute( $opt->{Keyring} ) ) {
++ $opt->{Keyring} = File::Spec->catfile( $RT::BasePath, $opt->{Keyring} );
+ }
-+ } else {
-+ $opt->{'OpenSSL'} = 'openssl';
-+ }
-+
-+ unless ( ref $opt->{'Passphrase'} eq 'HASH' ) {
-+ $opt->{'Passphrase'} = { '' => $opt->{'Passphrase'} };
++ unless (-d $opt->{Keyring} and -r _) {
++ $RT::Logger->info(
++ "RT's SMIME libraries couldn't successfully read your".
++ " configured SMIME keyring directory (".$opt->{Keyring}
++ .").");
++ delete $opt->{Keyring};
++ }
+ }
+ },
+ },
- GnuPG => { Type => 'HASH' },
- GnuPGOptions => {
+ GnuPG => {
Type => 'HASH',
+ PostLoadCheck => sub {
55: d181350 < ---: ------- new functions for testing: smime, temp storang, relocatable
56: 2823fc7 < ---: ------- expect that we get Queue and Actions arguments and pass them further
---: ------- > 79: 2db873e SMIME: Store user keys in a user column
---: ------- > 80: d12d753 SMIME: Message verification
---: ------- > 81: f65e4ac SMIME: Import signing keys after verification
---: ------- > 82: 02f5845 SMIME: Verifying the signing entity of SMIME certificates
---: ------- > 83: 93df8fc SMIME: Allow an insecure mode which accepts untrusted certificates
---: ------- > 84: 03df19e SMIME: Document passphrase loading
57: 19a0900 ! 85: fe3e256 Pass queue and actions into mail plugins, and thence to VerifyDecrypt
@@ -1,6 +1,9 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- pass "queue" and actions into early mail plugins
+ Pass queue and actions into mail plugins, and thence to VerifyDecrypt
+
+ This allows crypt plugins to examine the queue's configured addresses,
+ and use that information when determining which keys to decrypt with.
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
--- a/lib/RT/Interface/Email.pm
@@ -46,3 +49,24 @@
unless ( $SystemTicket->id || $SystemQueueObj->id ) {
return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );
+diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
+--- a/lib/RT/Interface/Email/Auth/Crypt.pm
++++ b/lib/RT/Interface/Email/Auth/Crypt.pm
+@@
+ my %args = (
+ Message => undef,
+ RawMessageRef => undef,
++ Queue => undef,
++ Actions => undef,
+ @_
+ );
+
+@@
+ }
+
+ my (@res) = RT::Crypt->VerifyDecrypt(
++ %args,
+ Entity => $args{'Message'},
+ );
+ if ( !@res ) {
+
58: 37967e7 < ---: ------- s/Incomming/Incoming/
59: 1fd72af < ---: ------- add smime data for testing
60: 5e0cb8c < ---: ------- tests for SMIME
61: 5a2479e < ---: ------- SMIME crypt implementation
62: c742957 < ---: ------- fix getting keys per protocol
63: 09f9358 < ---: ------- debug log in tmp dir may help debug things
64: 6f94710 < ---: ------- SignEncrypt should use enabled outgoing protocol
66: 5a83927 < ---: ------- represent basic GetKeysForEncryption in RT::Crypt::Base
68: 3b23cf1 < ---: ------- per UID expiration dates make sense only for GnuPG
69: bcb936d < ---: ------- mass Crypt::SMIME improvement
70: 38241fb < ---: ------- check proper config option
71: 7e95b4a < ---: ------- switch to new APIs
72: 54b82f2 < ---: ------- update tests
73: df669a4 < ---: ------- rename test files
75: 2db72ac < ---: ------- rename key files for smime
76: e0b42db < ---: ------- tests changes
77: 7845b3b < ---: ------- create our CA and generate keys from scratch
---: ------- > 86: c56f040 SMIME: Message decryption
---: ------- > 87: 7acc2d8 SMIME: Testing keys and certificates
---: ------- > 88: 3c3f958 Factor out find_relocatable_path
---: ------- > 89: c102a7f SMIME: Add a testing module
---: ------- > 90: b46b7d3 SMIME: Test incoming mail verification and encryption
---: ------- > 91: 37f1d2a SMIME: Message signing and encryption
107: 7cd1ced ! 92: f289b2f SMIME: If passphrase is empty, then don't provide -passin
@@ -1,6 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- if passphrase is empty then we shouldn't use -passin
+ SMIME: If passphrase is empty, then don't provide -passin
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
--- a/lib/RT/Crypt/SMIME.pm
@@ -11,8 +11,8 @@
push @command, join ' ', shell_quote(
- $self->OpenSSLPath, qw(smime -sign -passin env:SMIME_PASS),
+ $self->OpenSSLPath, qw(smime -sign),
- -signer => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
- -inkey => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
+ -signer => $file,
+ -inkey => $file,
+ (defined $args{'Passphrase'} && length $args{'Passphrase'})
+ ? (qw(-passin env:SMIME_PASS))
+ : (),
@@ -21,15 +21,15 @@
if ( $args{'Encrypt'} ) {
@@
local $SIG{CHLD} = 'DEFAULT';
- my $cmd = join( ' ', shell_quote(
+ my $cmd = [
$self->OpenSSLPath,
- qw(smime -decrypt -passin env:SMIME_PASS),
-- -recip => $key_file,
-+ qw(smime -decrypt), '-recip' => $key_file,
++ qw(smime -decrypt),
+ -recip => $file,
+ (defined $ENV{'SMIME_PASS'} && length $ENV{'SMIME_PASS'})
+ ? (qw(-passin env:SMIME_PASS))
+ : (),
- ) );
+ ];
safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
- unless ( $? ) {
+ $res{'exit_code'} = $?;
79: 187da04 ! 93: 49fc5c4 SMIME: Test outgoing mail
@@ -1,269 +1,182 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- add and update SMIME tests
+ SMIME: Test outgoing mail
-diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
---- a/t/mail/smime/incoming.t
-+++ b/t/mail/smime/incoming.t
+diff --git a/t/crypt/smime/bad-recipients.t b/t/crypt/smime/bad-recipients.t
+new file mode 100644
+--- /dev/null
++++ b/t/crypt/smime/bad-recipients.t
@@
- );
-
- RT->Config->Set( Crypt =>
-+ Enable => 1,
- Incoming => ['SMIME'],
- Outgoing => 'SMIME',
- );
++use strict;
++use warnings;
++
++use RT::Test::SMIME tests => undef;
++
++use RT::Tickets;
++
++RT::Test::SMIME->import_key('sender at example.com');
++my $queue = RT::Test->load_or_create_queue(
++ Name => 'General',
++ CorrespondAddress => 'sender at example.com',
++);
++ok $queue && $queue->id, 'loaded or created queue';
++
++{
++ my ($status, $msg) = $queue->SetEncrypt(1);
++ ok $status, "turn on encyption by default"
++ or diag "error: $msg";
++}
++
++my $root;
++{
++ $root = RT::User->new($RT::SystemUser);
++ ok($root->LoadByEmail('root at localhost'), "Loaded user 'root'");
++ ok($root->Load('root'), "Loaded user 'root'");
++ is($root->EmailAddress, 'root at localhost');
++
++ RT::Test::SMIME->import_key( 'root at example.com.crt' => $root );
++}
++
++my $bad_user;
++{
++ $bad_user = RT::Test->load_or_create_user(
++ Name => 'bad_user',
++ EmailAddress => 'baduser at example.com',
++ );
++ ok $bad_user && $bad_user->id, 'created a user without key';
++}
++
++RT::Test->clean_caught_mails;
++
++use Test::Warn;
++
++warnings_like {
++ my $ticket = RT::Ticket->new(RT->SystemUser);
++ my ($status, undef, $msg) = $ticket->Create( Queue => $queue->id, Requestor => [$root->id, $bad_user->id] );
++ ok $status, "created a ticket" or diag "error: $msg";
++
++ my @mails = RT::Test->fetch_caught_mails;
++ is scalar @mails, 3, "autoreply, to bad user, to RT owner";
++
++ like $mails[0], qr{To: baduser\@example\.com}, "notification to bad user";
++ like $mails[1], qr{To: root}, "notification to RT owner";
++ like $mails[1], qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'},
++ "notification to owner has error";
++} [qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'}];
++
++done_testing;
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
---- a/t/mail/smime/outgoing.t
+new file mode 100644
+--- /dev/null
+++ b/t/mail/smime/outgoing.t
@@
- ok(eval {
- run3([
- $openssl, qw(smime -decrypt -passin pass:123456),
-- '-inkey', File::Spec->catfile($keys, 'recipient.key'),
-- '-recip', File::Spec->catfile($keys, 'recipient.crt')
-+ '-inkey', File::Spec->catfile($keys, 'root at example.com.key'),
-+ '-recip', File::Spec->catfile($keys, 'root at example.com.crt')
- ], \$mails[0], \$buf, \$err )
- }, 'can decrypt'
- );
-
-diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
-new file mode 100644
---- /dev/null
-+++ b/t/mail/smime/realmail.t
-@@
-+#!/usr/bin/perl
+use strict;
+use warnings;
+
-+use RT::Test tests => 197;
-+
-+my $openssl = RT::Test->find_executable('openssl');
-+plan skip_all => 'openssl executable is required.'
-+ unless $openssl;
-+
-+use Digest::MD5 qw(md5_hex);
-+
-+my $mails = RT::Test::get_abs_relocatable_dir(
-+ (File::Spec->updir()) x 2,
-+ qw(data smime mails),
-+);
-+my $keyring = RT::Test->new_temp_dir(
-+ crypt => smime => 'smime_keyring'
-+);
-+
-+RT->Config->Set( Crypt =>
-+ Enable => 1,
-+ Incoming => ['SMIME'],
-+ Outgoing => 'SMIME',
-+);
-+RT->Config->Set( GnuPG => Enable => 0 );
-+RT->Config->Set( SMIME =>
-+ Enable => 1,
-+ Passphrase => {
-+ 'root at example.com' => '123456',
-+ },
-+ OpenSSL => $openssl,
-+ Keyring => $keyring,
-+);
-+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
-+
-+RT::Test->import_smime_key('root at example.com');
-+RT::Test->import_smime_key('sender at example.com');
-+
-+my ($baseurl, $m) = RT::Test->started_ok;
-+ok $m->login, 'we did log in';
-+$m->get_ok( '/Admin/Queues/');
-+$m->follow_link_ok( {text => 'General'} );
-+$m->submit_form( form_number => 3,
-+ fields => { CorrespondAddress => 'root at example.com' } );
-+
-+diag "load Everyone group" if $ENV{'TEST_VERBOSE'};
-+my $everyone;
-+{
-+ $everyone = RT::Group->new( $RT::SystemUser );
-+ $everyone->LoadSystemInternalGroup('Everyone');
-+ ok $everyone->id, "loaded 'everyone' group";
-+}
-+
-+RT::Test->set_rights(
-+ Principal => $everyone,
-+ Right => ['CreateTicket'],
-+);
-+
-+
-+my $eid = 0;
-+for my $usage (qw/signed encrypted signed&encrypted/) {
-+ for my $attachment (qw/plain text-attachment binary-attachment/) {
-+ ++$eid;
-+ diag "Email $eid: $usage, $attachment email" if $ENV{TEST_VERBOSE};
-+ eval { email_ok($eid, $usage, $attachment) };
-+ }
-+}
-+
-+sub email_ok {
-+ my ($eid, $usage, $attachment) = @_;
-+ diag "email_ok $eid: $usage, $attachment" if $ENV{'TEST_VERBOSE'};
-+
-+ my ($file) = glob("$mails/$eid-*");
-+ my $mail = RT::Test->file_content($file);
-+
-+ my ($status, $id) = RT::Test->send_via_mailgate($mail);
-+ is ($status >> 8, 0, "$eid: The mail gateway exited normally");
-+ ok ($id, "$eid: got id of a newly created ticket - $id");
-+
-+ my $tick = RT::Ticket->new( $RT::SystemUser );
-+ $tick->Load( $id );
-+ ok ($tick->id, "$eid: loaded ticket #$id");
-+
-+ is ($tick->Subject,
-+ "Test Email ID:$eid",
-+ "$eid: Created the ticket"
++use RT::Test::SMIME tests => undef;
++my $test = 'RT::Test::SMIME';
++
++use IPC::Run3 'run3';
++use RT::Interface::Email;
++
++my ($url, $m) = RT::Test->started_ok;
++ok $m->login, "logged in";
++
++my $queue = RT::Test->load_or_create_queue(
++ Name => 'General',
++ CorrespondAddress => 'sender at example.com',
++ CommentAddress => 'sender at example.com',
++);
++ok $queue && $queue->id, 'loaded or created queue';
++
++{
++ my ($status, $msg) = $queue->SetEncrypt(1);
++ ok $status, "turn on encyption by default"
++ or diag "error: $msg";
++}
++
++my $user;
++{
++ $user = RT::User->new($RT::SystemUser);
++ ok($user->LoadByEmail('root at localhost'), "Loaded user 'root'");
++ ok($user->Load('root'), "Loaded user 'root'");
++ is($user->EmailAddress, 'root at localhost');
++
++ RT::Test::SMIME->import_key( 'root at example.com.crt' => $user );
++}
++
++RT::Test->clean_caught_mails;
++
++{
++ my $mail = <<END;
++From: root\@localhost
++To: rt\@example.com
++Subject: This is a test of new ticket creation as an unknown user
++
++Blah!
++Foob!
++
++END
++
++ my ($status, $id) = RT::Test->send_via_mailgate(
++ $mail, queue => $queue->Name,
+ );
-+
-+ my $txn = $tick->Transactions->First;
-+ my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
-+
-+ is( $msg->GetHeader('X-RT-Privacy'),
-+ 'SMIME',
-+ "$eid: recorded incoming mail that is secured"
++ is $status >> 8, 0, "successfuly executed mailgate";
++
++ my $ticket = RT::Ticket->new($RT::SystemUser);
++ $ticket->Load( $id );
++ ok ($ticket->id, "found ticket ". $ticket->id);
++}
++
++{
++ my @mails = RT::Test->fetch_caught_mails;
++ is scalar @mails, 1, "autoreply";
++
++ my ($buf, $err);
++ local $@;
++ ok(eval {
++ run3([
++ qw(openssl smime -decrypt -passin pass:123456),
++ '-inkey', $test->key_path('root at example.com.key'),
++ '-recip', $test->key_path('root at example.com.crt')
++ ], \$mails[0], \$buf, \$err )
++ }, 'can decrypt'
+ );
-+
-+ if ($usage =~ /encrypted/) {
-+ is( $msg->GetHeader('X-RT-Incoming-Encryption'),
-+ 'Success',
-+ "$eid: recorded incoming mail that is encrypted"
-+ );
-+ like( $attachments[0]->Content, qr/ID:$eid/,
-+ "$eid: incoming mail did NOT have original body"
-+ );
-+ }
-+ else {
-+ is( $msg->GetHeader('X-RT-Incoming-Encryption'),
-+ 'Not encrypted',
-+ "$eid: recorded incoming mail that is not encrypted"
-+ );
-+ like( $msg->Content || $attachments[0]->Content, qr/ID:$eid/,
-+ "$eid: got original content"
-+ );
-+ }
-+
-+ if ($usage =~ /signed/) {
-+ is( $msg->GetHeader('X-RT-Incoming-Signature'),
-+ 'RT Test <sender at example.com>',
-+ "$eid: recorded incoming mail that is signed"
-+ );
-+ }
-+ else {
-+ is( $msg->GetHeader('X-RT-Incoming-Signature'),
-+ undef,
-+ "$eid: recorded incoming mail that is not signed"
-+ );
-+ }
-+
-+ if ($attachment =~ /attachment/) {
-+ my ($a) = grep $_->Filename, @attachments;
-+ ok ($a && $a->Id, "$eid: found attachment with filename");
-+
-+ my $acontent = $a->Content;
-+ if ($attachment =~ /binary/)
-+ {
-+ is(md5_hex($acontent), '1e35f1aa90c98ca2bab85c26ae3e1ba7', "$eid: The binary attachment's md5sum matches");
-+ }
-+ else
-+ {
-+ like($acontent, qr/zanzibar/, "$eid: The attachment isn't screwed up in the database.");
-+ }
-+ }
-+
-+ return 0;
-+}
-+
++ diag $@ if $@;
++ diag $err if $err;
++ diag "Error code: $?" if $?;
++ like($buf, qr'This message has been automatically generated in response');
++}
++
++undef $m;
++done_testing;
diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
new file mode 100644
--- /dev/null
+++ b/t/web/smime/outgoing.t
@@
-+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
-+use RT::Test tests => 492;
-+
-+my $openssl = RT::Test->find_executable('openssl');
-+plan skip_all => 'openssl executable is required.'
-+ unless $openssl;
++use RT::Test::SMIME tests => undef;
++my $test = 'RT::Test::SMIME';
+
+use RT::Action::SendEmail;
+use File::Temp qw(tempdir);
+
-+RT::Test->set_mail_catcher;
-+
+use_ok('RT::Crypt::SMIME');
+
-+my $keys = RT::Test::get_abs_relocatable_dir(
-+ (File::Spec->updir()) x 2,
-+ qw(data smime keys),
-+);
-+
-+my $keyring = RT::Test->new_temp_dir(
-+ crypt => smime => 'smime_keyring'
-+);
-+
-+RT->Config->Set( Crypt =>
-+ Enable => 1,
-+ Incoming => ['SMIME'],
-+ Outgoing => 'SMIME',
-+);
-+RT->Config->Set( GnuPG => Enable => 0 );
-+RT->Config->Set( SMIME =>
-+ Enable => 1,
-+ OutgoingMessagesFormat => 'RFC',
-+ Passphrase => {
-+ 'sender at example.com' => '123456',
-+ },
-+ OpenSSL => $openssl,
-+ Keyring => $keyring,
-+);
-+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
-+
-+RT::Test->import_smime_key('sender at example.com');
-+
-+{
-+ my $cf = RT::CustomField->new( $RT::SystemUser );
-+ my ($ret, $msg) = $cf->Create(
-+ Name => 'SMIME Key',
-+ LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
-+ Type => 'TextSingle',
++RT::Test::SMIME->import_key('sender at example.com');
++
++my $user_email = 'root at example.com';
++{
++ my $user = RT::Test->load_or_create_user(
++ Name => $user_email, EmailAddress => $user_email
+ );
-+ ok($ret, "Custom Field created");
-+
-+ my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
-+ $OCF->Create(
-+ CustomField => $cf->id,
-+ ObjectId => 0,
-+ );
-+}
-+
-+my $user_email = 'root at example.com';
-+{
-+ my $user = RT::User->new( $RT::SystemUser );
-+ $user->LoadByEmail( $user_email );
-+ unless ( $user->id ) {
-+ $user->Create(
-+ Name => $user_email,
-+ EmailAddress => $user_email,
-+ Privileged => 1,
-+ );
-+ }
-+ my ($status, $msg) = $user->AddCustomFieldValue(
-+ Field => 'SMIME Key',
-+ Value => RT::Test->file_content([$keys, $user_email .'.crt']),
-+ );
-+ ok $status, "set key for the user" or diag "error: $msg";
++ ok $user && $user->id, 'loaded or created user';
++ RT::Test::SMIME->import_key($user_email, $user);
+}
+
+my $queue = RT::Test->load_or_create_queue(
@@ -373,13 +286,14 @@
+
+# ------------------------------------------------------------------------------
+# now delete all keys from the keyring and put back secret/pub pair for rt-test@
-+# and only public key for rt-recipient@ so we can verify signatures and decrypt
++# and only public key for sender@ so we can verify signatures and decrypt
+# like we are on another side recieving emails
+# ------------------------------------------------------------------------------
+
++my $keyring = $test->keyring_path;
+unlink $_ foreach glob( $keyring ."/*" );
-+RT::Test->import_smime_key('sender at example.com', 'public');
-+RT::Test->import_smime_key($user_email);
++RT::Test::SMIME->import_key('sender at example.com.crt');
++RT::Test::SMIME->import_key($user_email);
+
+$queue = RT::Test->load_or_create_queue(
+ Name => 'Regression',
@@ -406,7 +320,7 @@
+ ok !$msg->GetHeader('X-RT-Incoming-Signature'),
+ "RT's outgoing mail looks not signed";
+
-+ like $msg->Content, qr/Some content/, "RT's mail includes copy of ticket text";
++ like $txn->Content, qr/Some content/, "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed'} } ) {
@@ -426,7 +340,7 @@
+ is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
+ "RT's outgoing mail looks not encrypted";
+ like $msg->GetHeader('X-RT-Incoming-Signature'),
-+ qr/<rt-recipient\@example.com>/,
++ qr/<sender\@example\.com>/,
+ "RT's outgoing mail looks signed";
+
+ like $attachments[0]->Content, qr/Some content/,
@@ -473,7 +387,7 @@
+ is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
+ "RT's outgoing mail looks encrypted";
+ like $msg->GetHeader('X-RT-Incoming-Signature'),
-+ qr/<rt-recipient\@example.com>/,
++ qr/<sender\@example.com>/,
+ "RT's outgoing mail looks signed";
+
+ like $attachments[0]->Content, qr/Some content/,
@@ -543,6 +457,9 @@
+ check_text_emails( \%args, @mail );
+}
+
++undef $m;
++done_testing;
++
+sub check_text_emails {
+ my %args = %{ shift @_ };
+ my @mail = @_;
@@ -558,9 +475,9 @@
+ }
+
+ if ( $args{'Encrypt'} ) {
-+ like $mail, qr/application\/x-pkcs7-mime/, 'outgoing email was processed';
++ like $mail, qr/application\/(?:x-)?pkcs7-mime/, 'outgoing email was processed';
+ } elsif ( $args{'Sign'} ) {
-+ like $mail, qr/x-pkcs7-signature/, 'outgoing email was processed';
++ like $mail, qr/(?:x-)?pkcs7-signature/, 'outgoing email was processed';
+ } else {
+ unlike $mail, qr/smime/, 'outgoing email was not processed';
+ }
@@ -581,7 +498,7 @@
+ # strip id from subject to create new ticket
+ $mail =~ s/^(Subject:)\s*\[.*?\s+#\d+\]\s*/$1 /m;
+ # strip several headers
-+ foreach my $field ( qw(Message-ID X-RT-Original-Encoding RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
++ foreach my $field ( qw(Message-ID RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
+ $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
+ }
+ return $mail;
78: 070a573 ! 94: 62a59a6 SMIME: Test parsing of real mail
@@ -1,6 +1,6 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
-
- add set of emails generated by thunderbird
+Author: Alex Vandiver <alexmv at bestpractical.com>
+
+ SMIME: Test parsing of real mail
diff --git a/t/data/smime/mails/1-signed.eml b/t/data/smime/mails/1-signed.eml
new file mode 100644
@@ -754,3 +754,134 @@
+VJl16vAQxKLzOzQET0xzS5Jic/d6ponxjcBiXLsTxNnS/DHXkPpDWzz+2Fy9p7sz7NScH9+3
+qwQIeNe1tczOd/sAAAAAAAAAAAAA
+diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
+new file mode 100644
+--- /dev/null
++++ b/t/mail/smime/realmail.t
+@@
++use strict;
++use warnings;
++
++use RT::Test::SMIME tests => undef;
++use Digest::MD5 qw(md5_hex);
++
++my $test = 'RT::Test::SMIME';
++my $mails = $test->mail_set_path;
++
++RT->Config->Get('SMIME')->{AcceptUntrustedCAs} = 1;
++
++RT::Test::SMIME->import_key('root at example.com');
++RT::Test::SMIME->import_key('sender at example.com');
++
++my ($baseurl, $m) = RT::Test->started_ok;
++ok $m->login, 'we did log in';
++$m->get_ok( '/Admin/Queues/');
++$m->follow_link_ok( {text => 'General'} );
++$m->submit_form( form_number => 3,
++ fields => { CorrespondAddress => 'root at example.com' } );
++
++diag "load Everyone group" if $ENV{'TEST_VERBOSE'};
++my $everyone;
++{
++ $everyone = RT::Group->new( $RT::SystemUser );
++ $everyone->LoadSystemInternalGroup('Everyone');
++ ok $everyone->id, "loaded 'everyone' group";
++}
++
++RT::Test->set_rights(
++ Principal => $everyone,
++ Right => ['CreateTicket'],
++);
++
++
++my $eid = 0;
++for my $usage (qw/signed encrypted signed&encrypted/) {
++ for my $attachment (qw/plain text-attachment binary-attachment/) {
++ ++$eid;
++ diag "Email $eid: $usage, $attachment email" if $ENV{TEST_VERBOSE};
++ eval { email_ok($eid, $usage, $attachment) };
++ }
++}
++
++undef $m;
++done_testing;
++
++sub email_ok {
++ my ($eid, $usage, $attachment) = @_;
++ diag "email_ok $eid: $usage, $attachment" if $ENV{'TEST_VERBOSE'};
++
++ my ($file) = glob("$mails/$eid-*");
++ my $mail = RT::Test->file_content($file);
++
++ my ($status, $id) = RT::Test->send_via_mailgate($mail);
++ is ($status >> 8, 0, "$eid: The mail gateway exited normally");
++ ok ($id, "$eid: got id of a newly created ticket - $id");
++
++ my $tick = RT::Ticket->new( $RT::SystemUser );
++ $tick->Load( $id );
++ ok ($tick->id, "$eid: loaded ticket #$id");
++
++ is ($tick->Subject,
++ "Test Email ID:$eid",
++ "$eid: Created the ticket"
++ );
++
++ my $txn = $tick->Transactions->First;
++ my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
++
++ is( $msg->GetHeader('X-RT-Privacy'),
++ 'SMIME',
++ "$eid: recorded incoming mail that is secured"
++ );
++
++ if ($usage =~ /encrypted/) {
++ is( $msg->GetHeader('X-RT-Incoming-Encryption'),
++ 'Success',
++ "$eid: recorded incoming mail that is encrypted"
++ );
++ like( $attachments[0]->Content, qr/ID:$eid/,
++ "$eid: incoming mail did NOT have original body"
++ );
++ }
++ else {
++ is( $msg->GetHeader('X-RT-Incoming-Encryption'),
++ 'Not encrypted',
++ "$eid: recorded incoming mail that is not encrypted"
++ );
++ like( $msg->Content || $attachments[0]->Content, qr/ID:$eid/,
++ "$eid: got original content"
++ );
++ }
++
++ if ($usage =~ /signed/) {
++ is( $msg->GetHeader('X-RT-Incoming-Signature'),
++ '"sender" <sender at example.com>',
++ "$eid: recorded incoming mail that is signed"
++ );
++ }
++ else {
++ is( $msg->GetHeader('X-RT-Incoming-Signature'),
++ undef,
++ "$eid: recorded incoming mail that is not signed"
++ );
++ }
++
++ if ($attachment =~ /attachment/) {
++ my ($a) = grep $_->Filename, @attachments;
++ ok ($a && $a->Id, "$eid: found attachment with filename");
++
++ my $acontent = $a->Content;
++ if ($attachment =~ /binary/)
++ {
++ is(md5_hex($acontent), '1e35f1aa90c98ca2bab85c26ae3e1ba7', "$eid: The binary attachment's md5sum matches");
++ }
++ else
++ {
++ like($acontent, qr/zanzibar/, "$eid: The attachment isn't screwed up in the database.");
++ }
++ }
++
++ return 0;
++}
++
+
80: d1e0f7d < ---: ------- move IO::Handler::CRLF
81: 7c61dd5 < ---: ------- refactor SignEncrypt to allow independant actions
82: 5acece4 < ---: ------- both signature formats can be handled in the same way
83: 90aeafa < ---: ------- line ends should be \x0D\x0A
84: 7224999 < ---: ------- clients can use application/x-pkcs7-signature or application/pkcs7-signature
85: 5fceb54 < ---: ------- fix number of tests and signer idententity
87: 6c12fc9 < ---: ------- ParsePKCS7Info - to figure out who signed a message
88: 08194fd < ---: ------- FormatStatus function instead of composing string all the time
89: 56ddf9a < ---: ------- check that key file exists beforehead
90: d0e80c0 < ---: ------- message extracting exact option and postprocessing
91: 9b10374 < ---: ------- figure out signer of the message
92: e9bc260 < ---: ------- fix cofiguration and emails in tests
93: 55e5a84 < ---: ------- we can not test with old files, new set of keys
94: 12bc439 < ---: ------- remove old files,we don't have keys for them
95: 6687eca ! 95: 01d750d Always pass Top entity when we detecting crypto parts
@@ -1,6 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- always pass top entity when we detecting crypto parts
+ Always pass Top entity when we detecting crypto parts
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
--- a/lib/RT/Crypt.pm
@@ -11,10 +11,8 @@
+ $args{'TopEntity'} ||= $entity;
+
- my @protocols = $args{'Protocols'}
- ? @{ $args{'Protocols'} }
- : $self->EnabledOnIncoming;
-
+ my @protocols = $self->EnabledOnIncoming;
+
foreach my $protocol ( @protocols ) {
my $class = $self->LoadImplementation( $protocol );
- my %info = $class->CheckIfProtected( Entity => $entity );
@@ -26,11 +24,17 @@
$args{'Skip'}{ $entity } = 1;
@@
+
foreach my $protocol ( @protocols ) {
my $class = $self->LoadImplementation( $protocol );
- my @list = $class->FindScatteredParts(
+- my @list = $class->FindScatteredParts( Parts => \@parts, Parents => \%parent, Skip => $args{'Skip'} );
++ my @list = $class->FindScatteredParts(
+ Entity => $args{'TopEntity'},
- Parts => \@parts,
- Parents => \%parent,
- Skip => $args{'Skip'}
++ Parts => \@parts,
++ Parents => \%parent,
++ Skip => $args{'Skip'}
++ );
+ next unless @list;
+
+ $_->{'Protocol'} = $protocol foreach @list;
96: b8a3e4f < ---: ------- when key not found, $res{info} is empty
97: 2f4e71a ! 96: a98efb3 SMIME: Improve recipient detection by examining all possibilities
@@ -1,9 +1,10 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- improve recipient detection for SMIME
+ SMIME: Improve recipient detection by examining all possibilities
- check all recipients from message head, as well every address related
- to the queue, use someheuristicto guess most likely candidate
+ Check all recipients from the message's header, as well as every address
+ related to the queue. If none are found, store the failure in the
+ status.
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
--- a/lib/RT/Crypt/SMIME.pm
@@ -12,66 +13,48 @@
my $msg = $args{'Data'}->as_string;
-+ my %addresses;
-+ $addresses{lc $_}++ foreach
-+ map $_->address,
-+ map Email::Address->parse($_),
-+ @{$args{'Recipients'}};
-+
- my $action = 'correspond';
- $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
--
+- my $action = 'correspond';
+- $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
++ push @{ $args{'Recipients'} ||= [] },
++ $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
++ $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
++ ;
+
- my $address = $action eq 'correspond'
- ? $args{'Queue'}->CorrespondAddress || RT->Config->Get('CorrespondAddress')
- : $args{'Queue'}->CommentAddress || RT->Config->Get('CommentAddress');
-- my $key_file = File::Spec->catfile(
-- RT->Config->Get('SMIME')->{'Keyring'}, $address .'.pem'
-- );
-- unless ( -e $key_file && -r _ ) {
-- $res{'exit_code'} = 1;
-- $res{'status'} = $self->FormatStatus({
-- Operation => 'KeyCheck',
-- Status => 'MISSING',
-- Message => "Secret key for '$address' is not available",
+
+- my %res;
+- my $file = $self->CheckKeyring( Key => $address );
+- unless ($file) {
+- $res{'status'} .= $self->FormatStatus({
+- Operation => "KeyCheck", Status => "MISSING",
+- Message => "Secret key for $address is not available",
- Key => $address,
-- KeyType => 'secret',
+- KeyType => "secret",
- });
-- $res{'User'} = {
-- String => $address,
-- SecretKeyMissing => 1,
-- };
+- $res{exit_code} = 1;
- return %res;
-+ if ( $action eq 'correspond' ) {
-+ my $i = 1;
-+ $addresses{lc $_} += $i++ foreach (
-+ $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
-+ $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
-+ );
-+ } else {
-+ my $i = 1;
-+ $addresses{lc $_} += $i++ foreach (
-+ $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
-+ $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress'),
-+ );
- }
-+ my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
+- }
++ my %seen;
++ my @addresses =
++ grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_),
++ grep length && defined, @{$args{'Recipients'}};
++
++ my ($buf, $encrypted_to, %res);
++
++ foreach my $address ( @addresses ) {
++ my $file = $self->CheckKeyring( Key => $address );
++ next unless $file;
- my $buf;
+- my $buf;
- {
-+ my $found_key = 0;
-+ my $encrypted_to;
-+ foreach my $address ( sort { $addresses{$b} <=> $addresses{$a} } grep length, keys %addresses ) {
-+ my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
-+ next unless -e $key_file && -r _;
-+
-+ $found_key = 1;
-+
local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
local $SIG{CHLD} = 'DEFAULT';
- my $cmd = join( ' ', shell_quote(
+ my $cmd = [
@@
- -recip => $key_file,
- ) );
+ : (),
+ ];
safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
+ unless ( $? ) {
+ $encrypted_to = $address;
@@ -83,7 +66,7 @@
$res{'exit_code'} = $?;
- }
- if ( $res{'exit_code'} ) {
- $res{'message'} = "openssl exitted with error code ". ($? >> 8)
+ $res{'message'} = "openssl exited with error code ". ($? >> 8)
." and error: $res{stderr}";
+ $RT::Logger->error( $res{'message'} );
$res{'status'} = $self->FormatStatus({
@@ -93,7 +76,7 @@
});
return %res;
}
-+ unless ( $found_key ) {
++ unless ( $encrypted_to ) {
+ $res{'exit_code'} = 1;
+ $res{'status'} = $self->FormatStatus({
+ Operation => 'KeyCheck',
@@ -104,7 +87,7 @@
+ return %res;
+ }
- my $res_entity = _extract_msg_from_buf( \$buf, 1 );
+ my $res_entity = _extract_msg_from_buf( \$buf );
$res_entity->make_multipart( 'mixed', Force => 1 );
@@
$res{'status'} = $self->FormatStatus({
@@ -116,11 +99,8 @@
return %res;
@@
- }
- }
}
-- return () if !$security_type && $type eq 'application/octet-stream';
-+ return () unless $security_type;
+ return () unless $security_type;
- return (
+ my %res = (
98: 94e4352 < ---: ------- upgrade script
99: 44ecb5c < ---: ------- fix number of tests
100: 9b6cdf8 < ---: ------- make protocol case insensetive
101: 78b2d70 < ---: ------- SMIME documentation
102: 9fd7325 < ---: ------- this check is separate from the gpg check
103: 323ec0e < ---: ------- autocreate a User custom field
104: f8e1deb < ---: ------- extract users' certificates out of signed messages
105: 127ec6b < ---: ------- update description of the %Crypt hash option
106: 72d975c < ---: ------- Don't set GnuPG explicitly as the protocol used for outoging emails
108: 7c68ae0 < ---: ------- users' private keys are only supported for GnuPG
109: 5f8c9bf < ---: ------- variable name typo
110: 7d4b4fd < ---: ------- use Auth::Crypt mail plugin in tests
111: d1ab193 < ---: ------- privacy header was changed to match protocol
112: 1c95713 < ---: ------- some programs use x-pkcs7-... and some use just pkcs7-...
113: c0422d7 < ---: ------- todo file for SMIME integration
114: 57322d9 < ---: ------- delete backup files openssl creates in CA directory
115: a687ed1 < ---: ------- for simplicity allow non-unique subjects
116: 562fb32 < ---: ------- update expiration date on SMIME certs
117: e029cee < ---: ------- ignore a few smime files CA operations create
118: 1f3098b < ---: ------- accept user in RT::Test->import_smime_key
119: 12ed5db < ---: ------- add_mail_catcher was dropped, we always catch mail in tests
120: 7d434a9 < ---: ------- use root at example.com for testing as he has a key
121: aaf357a < ---: ------- use new feature in ->import_smime_key
122: f6e9449 < ---: ------- follow pattern in testing result of send_via_mailgate
123: ee97826 < ---: ------- Create the SMIME key CF
124: a6abdd9 < ---: ------- Correct the test count in the SMIME tests
125: 847d2ea < ---: ------- Fill in SMIME docs in config and mail plugin
126: 75010ea < ---: ------- drop mail catcher call, we always set it
127: 52dfe44 < ---: ------- fix smime web tests
128: 4fce17a < ---: ------- run more tests that are deeper
129: e5a05ba < ---: ------- first pass at generic attachments encrypt/decrypt in DB
130: 294626f < ---: ------- Defend against undefined configurations when checking crypt protocols
---: ------- > 97: b7c47ef SMIME: Be more verbose on how it looks for, and fails to find, private keys
139: a424932 ! 98: 30be6a5 SMIME: Encrypting and decrypting attachments in the database
@@ -1,16 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- encrypting/decrypting attachments in DB with SMIME
-
-diff --git a/TODO.SMIME b/TODO.SMIME
---- a/TODO.SMIME
-+++ b/TODO.SMIME
-@@
--* port RT::Attachment::{Encrypt,Decrypt} over new API with SMIME support
--
- * continue with share//html/Ticket/Elements/ShowGnuPGStatus
-
- * work harder on re-verification
+ SMIME: Encrypting and decrypting attachments in the database
diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
--- a/lib/RT/Attachment.pm
@@ -35,7 +25,7 @@
+ Recipients => \@addresses,
+ );
if ( $res{'exit_code'} ) {
- return (0, $self->loc('GnuPG error. Contact with administrator'));
+ return (0, $self->loc('Decryption error; contact the administrator'));
}
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
@@ -106,11 +96,8 @@
my @keys;
if ( $args{'Encrypt'} ) {
- my @addresses =
-- map $_->address,
-- map Email::Address->parse($_),
-- grep defined && length,
-- map $entity->head->get($_),
-- qw(To Cc Bcc);
+- grep !$seen{$_}++, map $_->address, map Email::Address->parse($_),
+- grep defined && length, map $entity->head->get($_), qw(To Cc Bcc);
+ my @addresses = @{ $args{'Recipients'} };
foreach my $address ( @addresses ) {
@@ -140,7 +127,9 @@
\$buf, \$err
) };
}
- $RT::Logger->debug( "openssl stderr: " . $err ) if length $err;
+@@
+ }) if $args{'Encrypt'};
+ }
- my $tmpdir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 );
- my $parser = MIME::Parser->new();
@@ -151,50 +140,23 @@
- $entity->make_singlepart;
-
- return %res;
+-}
+-
+-sub SignEncryptContent {
+- my $self = shift;
+- return ( exit_code => 1 );
+ return (\$buf, %res);
}
sub VerifyDecrypt {
@@
- my $self = shift;
- my %args = (Data => undef, Queue => undef, @_ );
-
-- my %res;
--
- my $msg = $args{'Data'}->as_string;
-
-- my %addresses;
-- $addresses{lc $_}++ foreach
-- map $_->address,
-- map Email::Address->parse($_),
-- @{$args{'Recipients'}};
--
-- my $action = 'correspond';
-- $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
-- if ( $action eq 'correspond' ) {
-- my $i = 1;
-- $addresses{lc $_} += $i++ foreach (
-- $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
-- $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
-- );
-- } else {
-- my $i = 1;
-- $addresses{lc $_} += $i++ foreach (
-- $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
-- $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress'),
-- );
-- }
-- my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
-+ push @{ $args{'Recipients'} ||= [] },
-+ $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
-+ $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
-+ ;
-
-- my $buf;
+ $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
+ ;
+
+ my ($buf, %res) = $self->_Decrypt( %args, Content => \$args{'Data'}->as_string );
+ return %res unless $buf;
+
-+ my $res_entity = _extract_msg_from_buf( $buf, 1 );
++ my $res_entity = _extract_msg_from_buf( $buf );
+ $res_entity->make_multipart( 'mixed', Force => 1 );
+
+ $args{'Data'}->make_multipart( 'mixed', Force => 1 );
@@ -219,31 +181,18 @@
+sub _Decrypt {
+ my $self = shift;
+ my %args = (Content => undef, @_ );
-+
-+ my %seen;
-+ my @addresses =
-+ grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_),
-+ grep length && defined, @{$args{'Recipients'}};
-+
-+ my ($buf, $encrypted_to, %res);
-+
-+ my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
- my $found_key = 0;
-- my $encrypted_to;
-- foreach my $address ( sort { $addresses{$b} <=> $addresses{$a} } grep length, keys %addresses ) {
-+ foreach my $address ( @addresses ) {
- my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
- next unless -e $key_file && -r _;
-
+
+ my %seen;
+ my @addresses =
@@
? (qw(-passin env:SMIME_PASS))
: (),
- ) );
+ ];
- safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
+ safe_run_child { run3( $cmd, $args{'Content'}, \$buf, \$res{'stderr'} ) };
unless ( $? ) {
$encrypted_to = $address;
- last;
+ $RT::Logger->debug("Message encrypted for $encrypted_to");
@@
Message => 'Decryption failed',
EncryptedTo => $address,
@@ -251,8 +200,8 @@
- return %res;
+ return (undef, %res);
}
- unless ( $found_key ) {
- $res{'exit_code'} = 1;
+ unless ( $encrypted_to ) {
+ $RT::Logger->error("Couldn't find SMIME key for addresses: ". join ', ', @addresses);
@@
Message => "Secret key is not available",
KeyType => 'secret',
@@ -261,7 +210,7 @@
+ return (undef, %res);
}
-- my $res_entity = _extract_msg_from_buf( \$buf, 1 );
+- my $res_entity = _extract_msg_from_buf( \$buf );
- $res_entity->make_multipart( 'mixed', Force => 1 );
-
- $args{'Data'}->make_multipart( 'mixed', Force => 1 );
@@ -275,8 +224,64 @@
});
- return %res;
+-}
+-
+-sub DecryptContent {
+- my $self = shift;
+- return ( exit_code => 1 );
+ return (\$buf, %res);
}
sub FormatStatus {
+diff --git a/t/crypt/smime/attachments-in-db.t b/t/crypt/smime/attachments-in-db.t
+new file mode 100644
+--- /dev/null
++++ b/t/crypt/smime/attachments-in-db.t
+@@
++use strict;
++use warnings;
++
++use RT::Test::SMIME tests => undef;
++
++use IPC::Run3 'run3';
++use String::ShellQuote 'shell_quote';
++use RT::Tickets;
++
++RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} = 1;
++
++RT::Test::SMIME->import_key('sender at example.com');
++my $queue = RT::Test->load_or_create_queue(
++ Name => 'General',
++ CorrespondAddress => 'sender at example.com',
++);
++ok $queue && $queue->id, 'loaded or created queue';
++
++{
++ my $ticket = RT::Test->create_ticket(
++ Queue => $queue->id,
++ Subject => 'test',
++ Content => 'test',
++ );
++
++ my $txn = $ticket->Transactions->First;
++ ok $txn && $txn->id, 'found first transaction';
++ is $txn->Type, 'Create', 'it is Create transaction';
++
++ my $attach = $txn->Attachments->First;
++ ok $attach && $attach->id, 'found attachment';
++ is $attach->Content, 'test', 'correct content';
++
++ my ($status, $msg) = $attach->Encrypt;
++ ok $status, 'encrypted attachment' or diag "error: $msg";
++
++ isnt $attach->Content, 'test', 'correct content';
++
++ ($status, $msg) = $attach->Decrypt;
++ ok $status, 'decrypted attachment' or diag "error: $msg";
++
++ is $attach->Content, 'test', 'correct content';
++}
++
++done_testing;
+
---: ------- > 99: 5186f9a Upgrade script for users of RT::Extension::SMIME and ::Crypt
131: 16c4f4a ! 100: b1ad243 Factor out sending templated errors for convenient future use
@@ -1,6 +1,6 @@
Author: Jason May <jasonmay at bestpractical.com>
- Factor sending templated errors for convenient future use
+ Factor out sending templated errors for convenient future use
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -35,13 +35,12 @@
+ return EmailErrorToSender(
+ %args,
-+ Template => 'Error: bad GnuPG data',
++ Template => 'Error: bad encrypted data',
+ Arguments => { Messages => [ @bad_data_messages ] },
+ );
+}
+
+sub EmailErrorToSender {
-+ my $self = shift;
+ my %args = (@_);
+
+ $args{'Arguments'} ||= {};
@@ -50,7 +49,7 @@
my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
To => $address,
-- Template => 'Error: bad GnuPG data',
+- Template => 'Error: bad encrypted data',
- Arguments => {
- Messages => [ @bad_data_messages ],
- TicketObj => $args{'Ticket'},
@@ -60,4 +59,9 @@
InReplyTo => $args{'Message'},
);
unless ( $status ) {
+- $RT::Logger->error("Couldn't send 'Error: bad encrypted data'");
++ $RT::Logger->error("Couldn't send '$args{'Template'}''");
+ }
+ return 0;
+ }
132: d6b0e3d < ---: ------- Factor sending templated errors for convenient future use
133: 54e3666 < ---: ------- Error and abort if an unencrypted message is received in Strict mode
134: 95fbd5b ! 101: f7e861a Add RejectOnUnencrypted to force all incoming messages to be encrypted
@@ -1,90 +1,119 @@
-Author: Jason May <jasonmay at bestpractical.com>
-
- Test sending (un)encrypted mail with strict auth enabled
-
-diff --git a/t/mail/smime/strict.t b/t/mail/smime/strict.t
+Author: Alex Vandiver <alexmv at bestpractical.com>
+
+ Add RejectOnUnencrypted to force all incoming messages to be encrypted
+
+diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
+--- a/etc/RT_Config.pm.in
++++ b/etc/RT_Config.pm.in
+@@
+ be used in outgoing emails. At this moment, only one protocol can be
+ used to protect outgoing emails.
+
++Set C<RejectOnUnencrypted> to true if all incoming email must be
++properly encrypted. All unencrypted emails will be rejected by RT.
++
+ Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
+ emails encrypted for key RT doesn't have and can not decrypt.
+
+@@
+ Incoming => undef, # ['GnuPG', 'SMIME']
+ Outgoing => undef, # 'SMIME'
+
++ RejectOnUnencrypted => 0,
+ RejectOnMissingPrivateKey => 1,
+ RejectOnBadData => 1,
+
+
+diff --git a/etc/initialdata b/etc/initialdata
+--- a/etc/initialdata
++++ b/etc/initialdata
+@@
+ }
+ },
+ { Queue => 0,
++ Name => "Error: unencrypted message", # loc
++ Description =>
++ "Inform user that their unencrypted mail has been rejected", # loc
++ Content => q{Subject: RT requires that all incoming mail be encrypted
++
++You received this message because RT received mail from you that was not encrypted. As such, it has been rejected.
++}
++ },
++ { Queue => 0,
+ Name => "Error: public key", # loc
+ Description =>
+ "Inform user that he has problems with public key and couldn't recieve encrypted content", # loc
+
+diff --git a/etc/upgrade/4.1.20/content b/etc/upgrade/4.1.20/content
+--- a/etc/upgrade/4.1.20/content
++++ b/etc/upgrade/4.1.20/content
+@@
+ return 1;
+ },
+ );
++
++our @Templates = (
++ { Queue => 0,
++ Name => "Error: unencrypted message", # loc
++ Description =>
++ "Inform user that their unencrypted mail has been rejected", # loc
++ Content => q{Subject: RT requires that all incoming mail be encrypted
++
++You received this message because RT received mail from you that was not encrypted. As such, it has been rejected.
++}
++ },
++);
+
+diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
+--- a/lib/RT/Interface/Email/Auth/Crypt.pm
++++ b/lib/RT/Interface/Email/Auth/Crypt.pm
+@@
+ Entity => $args{'Message'},
+ );
+ if ( !@res ) {
+- $args{'Message'}->head->replace(
+- 'X-RT-Incoming-Encryption' => 'Not encrypted'
+- );
+-
++ if (RT->Config->Get('Crypt')->{'RejectOnUnencrypted'}) {
++ EmailErrorToSender(
++ %args,
++ Template => 'Error: unencrypted message',
++ Arguments => { Message => $args{'Message'} },
++ );
++ return (-1, 'rejected because the message is unencrypted with RejectOnUnencrypted enabled');
++ }
++ else {
++ $args{'Message'}->head->replace(
++ 'X-RT-Incoming-Encryption' => 'Not encrypted'
++ );
++ }
+ return 1;
+ }
+
+
+diff --git a/t/mail/smime/reject_on_unencrypted.t b/t/mail/smime/reject_on_unencrypted.t
new file mode 100644
--- /dev/null
-+++ b/t/mail/smime/strict.t
-@@
-+#!/usr/bin/perl
++++ b/t/mail/smime/reject_on_unencrypted.t
+@@
+use strict;
+use warnings;
+
-+use RT::Test tests => 22;
-+
-+my $openssl = RT::Test->find_executable('openssl');
-+plan skip_all => 'openssl executable is required.'
-+ unless $openssl;
++use RT::Test::SMIME tests => undef;
++my $test = 'RT::Test::SMIME';
+
+use IPC::Run3 'run3';
+use String::ShellQuote 'shell_quote';
+use RT::Tickets;
+
-+my $keys = RT::Test::get_abs_relocatable_dir(
-+ (File::Spec->updir()) x 2,
-+ qw(data smime keys),
-+);
-+
-+my $keyring = RT::Test->new_temp_dir(
-+ crypt => smime => 'smime_keyring'
-+);
-+
-+RT->Config->Set( Crypt =>
-+ Enable => 1,
-+ Strict => 1,
-+ Incoming => ['SMIME'],
-+ Outgoing => 'SMIME',
-+);
-+RT->Config->Set( GnuPG => Enable => 0 );
-+RT->Config->Set( SMIME =>
-+ Enable => 1,
-+ Strict => 1,
-+ OutgoingMessagesFormat => 'RFC',
-+ Passphrase => {
-+ 'sender at example.com' => '123456',
-+ },
-+ OpenSSL => $openssl,
-+ Keyring => $keyring,
-+);
-+
-+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
-+
-+{
-+ my $cf = RT::CustomField->new( $RT::SystemUser );
-+ my ($ret, $msg) = $cf->Create(
-+ Name => 'SMIME Key',
-+ LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
-+ Type => 'TextSingle',
-+ );
-+ ok($ret, "Custom Field created");
-+
-+ my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
-+ $OCF->Create(
-+ CustomField => $cf->id,
-+ ObjectId => 0,
-+ );
-+}
-+
-+{
-+ my $template = RT::Template->new($RT::SystemUser);
-+ $template->Create(
-+ Name => 'NotEncryptedMessage',
-+ Queue => 0,
-+ Content => <<EOF,
-+
-+Subject: Failed to send unencrypted message
-+
-+This message was not sent since it is unencrypted:
-+EOF
-+ );
-+}
++RT->Config->Get('Crypt')->{'RejectOnUnencrypted'} = 1;
+
+my ($url, $m) = RT::Test->started_ok;
+ok $m->login, "logged in";
+
+# configure key for General queue
-+RT::Test->import_smime_key('sender at example.com');
++RT::Test::SMIME->import_key('sender at example.com');
+my $queue = RT::Test->load_or_create_queue(
+ Name => 'General',
+ CorrespondAddress => 'sender at example.com',
@@ -96,7 +125,7 @@
+ Name => 'root at example.com',
+ EmailAddress => 'root at example.com',
+);
-+RT::Test->import_smime_key('root at example.com.crt', $user);
++RT::Test::SMIME->import_key('root at example.com.crt', $user);
+RT::Test->add_rights( Principal => $user, Right => 'SuperUser', Object => RT->System );
+
+my $mail = RT::Test->open_mailgate_ok($url);
@@ -115,11 +144,11 @@
+ my ($mail) = RT::Test->fetch_caught_mails;
+ like(
+ $mail,
-+ qr/^Subject: Failed to send unencrypted message/m,
-+ 'recorded incoming mail that is not encrypted'
++ qr/^Subject: RT requires that all incoming mail be encrypted/m,
++ 'rejected mail that is not encrypted'
+ );
+ my ($warning) = $m->get_warnings;
-+ like($warning, qr/rejected because the message is unencrypted with Strict mode enabled/);
++ like($warning, qr/rejected because the message is unencrypted/);
+}
+
+{
@@ -129,9 +158,9 @@
+ shell_quote(
+ qw(openssl smime -encrypt -des3),
+ -from => 'root at example.com',
-+ -to => 'rt@' . $RT::rtname,
++ -to => 'sender at example.com',
+ -subject => "Encrypted message for queue",
-+ File::Spec->catfile( $keys, 'sender at example.com.crt' ),
++ $test->key_path('sender at example.com.crt' ),
+ ),
+ \"Subject: test\n\norzzzzzz",
+ \$buf,
@@ -171,16 +200,16 @@
+ shell_quote(
+ RT->Config->Get('SMIME')->{'OpenSSL'},
+ qw( smime -sign -nodetach -passin pass:123456),
-+ -signer => File::Spec->catfile( $keys, 'root at example.com.crt' ),
-+ -inkey => File::Spec->catfile( $keys, 'root at example.com.key' ),
++ -signer => $test->key_path('root at example.com.crt' ),
++ -inkey => $test->key_path('root at example.com.key' ),
+ ),
+ '|',
+ shell_quote(
+ qw(openssl smime -encrypt -des3),
+ -from => 'root at example.com',
-+ -to => 'rt@' . RT->Config->Get('rtname'),
++ -to => 'sender at example.com',
+ -subject => "Encrypted and signed message for queue",
-+ File::Spec->catfile( $keys, 'sender at example.com.crt' ),
++ $test->key_path('sender at example.com.crt' ),
+ )),
+ \"Subject: test\n\norzzzzzz",
+ \$buf,
@@ -205,4 +234,6 @@
+ like( $attach->Content, qr'orzzzz');
+}
+
-
++undef $m;
++done_testing;
+
168: cb77896 ! 102: 70a3f5a Allow encryption/signing of dashboards
@@ -1,8 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- make it possible to encrypt/sign dashboards
-
- an option in %Crypt config
+ Allow encryption/signing of dashboards
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
--- a/etc/RT_Config.pm.in
136: caa078b ! 103: 8982b44 Refactor common delegation code
@@ -1,14 +1,22 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- refactor common code
+ Refactor common delegation code
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@
- return $class;
- } }
+ }
+ }
++=head2 SimpleImplementationCall Protocol => NAME, [...]
++
++Examines the caller of this method, and dispatches to the method of the
++same name on the correct L<RT::Crypt::Role> class based on the provided
++C<Protocol>.
++
++=cut
++
+sub SimpleImplementationCall {
+ my $self = shift;
+ my %args = (@_);
@@ -22,9 +30,9 @@
+ return %res;
+}
+
- # encryption and signatures can be nested one over another, for example:
- # GPG inline signed text can be signed with SMIME
+ =head2 FindProtectedParts Entity => MIME::Entity
+ Looks for encrypted or signed parts of the given C<Entity>, using all
@@
qw(To Cc Bcc)
];
@@ -37,19 +45,21 @@
+ return $self->SimpleImplementationCall( %args );
}
- sub SignEncryptContent {
+ =head2 SignEncryptContent Content => STRINGREF, [Sign => 1], [Encrypt => 1],
@@
$args{'Recipients'} = [ RT->Config->Get('CorrespondAddress') ];
}
- my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-- my %res = $self->LoadImplementation( $protocol )->SignEncryptContent( %args );
+- my $class = $self->LoadImplementation( $protocol );
+-
+- my %res = $class->SignEncryptContent( %args );
- $res{'Protocol'} = $protocol;
- return %res;
+ return $self->SimpleImplementationCall( %args );
}
- sub DrySign {
+ =head2 DrySign Signer => KEY
@@
my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} );
@@ -58,30 +68,30 @@
- my $class = $self->LoadImplementation( $protocol );
- my %res = $class->VerifyDecrypt( %args, Info => $protected );
- $res{'Protocol'} = $protocol;
-- push @res, \%res;
-+ push @res, { $self->SimpleImplementationCall(
++ my %res = $self->SimpleImplementationCall(
+ %args, Protocol => $protected->{'Protocol'}, Info => $protected
-+ ) };
- }
- return @res;
- }
++ );
+
+ # Let the header be modified so continuations are handled
+ my $modify = $res{status_on}->head->modify;
+@@
+ =cut
sub DecryptContent {
- my $self = shift;
-- my %args = (@_);
--
-- my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-- my %res = $self->LoadImplementation( $protocol )->DecryptContent( %args );
-- $res{'Protocol'} = $protocol;
-- return %res;
+- my %args = (
+- Protocol => undef,
+- @_
+- );
+- return $self->LoadImplementation( $args{'Protocol'} )->DecryptContent( @_ );
+ return shift->SimpleImplementationCall( @_ );
}
- sub ParseStatus {
+ =head2 ParseStatus Protocol => NAME, Status => STRING
@@
sub GetKeysForEncryption {
my $self = shift;
- my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
+ my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ );
- my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
- my %res = $self->LoadImplementation( $protocol )->GetKeysForEncryption( %args );
- $res{'Protocol'} = $protocol;
@@ -89,6 +99,8 @@
+ return $self->SimpleImplementationCall( %args );
}
+ =head2 GetKeysForSigning Signer => EMAIL, Protocol => NAME
+@@
sub GetKeysForSigning {
my $self = shift;
my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_);
@@ -99,7 +111,7 @@
+ return $self->SimpleImplementationCall( %args );
}
- sub GetPublicKeyInfo {
+ =head2 GetPublicKeyInfo Protocol => NAME, KEY => EMAIL
@@
sub GetKeysInfo {
my $self = shift;
137: 98eba5e < ---: ------- use x-application-rt\/$protocol-encrypted content type
138: e1bc3e0 < ---: ------- Move AllowEncryptDataInDB into %Crypt option
140: 8830d2f < ---: ------- extract common code into RT::Test::SMIME
141: baac570 < ---: ------- test encrypting attachments in DB with SMIME
142: a6662f2 < ---: ------- reference proper argument in loc string
143: c12781e < ---: ------- adjust number of tests
144: ae59e43 < ---: ------- return back some wording to make patching old RT easier
145: d3c8ace < ---: ------- move SMIME upgrade script from 3.8.8 to 4.1.0
---: ------- > 104: 278b990 Only GnuPG supports multiple private keys per user; restrict PrivateKey
---: ------- > 105: 7a25437 SMIME: Admin interface for updating SMIME keys
---: ------- > 106: 4bdeb9d /Admin/Users/GnuPG.html is no longer just GPG, but all secret keys
146: d3ba497 ! 107: a100e31 Rename GnuPG mason components to Crypt
@@ -1,6 +1,280 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- rename *GnuPG* components to *Crypt*
+ Rename GnuPG mason components to Crypt
+
+diff --git a/share/html/Admin/Users/GnuPG.html b/share/html/Admin/Users/GnuPG.html
+deleted file mode 100644
+--- a/share/html/Admin/Users/GnuPG.html
++++ /dev/null
+@@
+-%# BEGIN BPS TAGGED BLOCK {{{
+-%#
+-%# COPYRIGHT:
+-%#
+-%# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
+-%# <sales at bestpractical.com>
+-%#
+-%# (Except where explicitly superseded by other copyright notices)
+-%#
+-%#
+-%# LICENSE:
+-%#
+-%# This work is made available to you under the terms of Version 2 of
+-%# the GNU General Public License. A copy of that license should have
+-%# been provided with this software, but in any event can be snarfed
+-%# from www.gnu.org.
+-%#
+-%# This work is distributed in the hope that it will be useful, but
+-%# WITHOUT ANY WARRANTY; without even the implied warranty of
+-%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+-%# General Public License for more details.
+-%#
+-%# You should have received a copy of the GNU General Public License
+-%# along with this program; if not, write to the Free Software
+-%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+-%# 02110-1301 or visit their web page on the internet at
+-%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+-%#
+-%#
+-%# CONTRIBUTION SUBMISSION POLICY:
+-%#
+-%# (The following paragraph is not intended to limit the rights granted
+-%# to you to modify and distribute this software under the terms of
+-%# the GNU General Public License and is only of importance to you if
+-%# you choose to contribute your changes and enhancements to the
+-%# community by submitting them to Best Practical Solutions, LLC.)
+-%#
+-%# By intentionally submitting any modifications, corrections or
+-%# derivatives to this work, or any other work intended for use with
+-%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+-%# you are the copyright holder for those contributions and you grant
+-%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+-%# royalty-free, perpetual, license to use, copy, create derivative
+-%# works based on those contributions, and sublicense and distribute
+-%# those contributions and any derivatives thereof.
+-%#
+-%# END BPS TAGGED BLOCK }}}
+-<& /Admin/Elements/Header, Title => loc("[_1]'s encryption keys",$UserObj->Name) &>
+-<& /Elements/Tabs &>
+-
+-<& /Elements/ListActions, actions => \@results &>
+-
+-% if ( $email ) {
+-<& /Admin/Elements/ShowKeyInfo, EmailAddress => $email &>
+-% } else {
+-<h2><% loc("User has empty email address") %></h2>
+-% }
+-
+-<form action="<%RT->Config->Get('WebPath')%>/Admin/Users/GnuPG.html" method="post" enctype="multipart/form-data">
+-<input type="hidden" class="hidden" name="id" value="<% $UserObj->Id %>" />
+-
+-% if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
+-<&|/Widgets/TitleBox, title => 'GnuPG private key'&>
+-<& /Widgets/Form/Select,
+- Name => 'PrivateKey',
+- Description => loc('Private Key'),
+- Values => \@potential_keys,
+- CurrentValue => $UserObj->PrivateKey,
+- DefaultLabel => loc('No private key'),
+-&>
+-</&>
+-% }
+-
+-% if (RT::Crypt->UseForOutgoing eq 'SMIME') {
+-<&|/Widgets/TitleBox, title => 'SMIME Certificate' &>
+-<textarea name="SMIMECertificate"><% $UserObj->SMIMECertificate || '' %></textarea>
+-</&>
+-% }
+-
+-<& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
+-</form>
+-
+-<%ARGS>
+-$id => undef
+-$Update => undef
+-</%ARGS>
+-<%INIT>
+-return unless RT->Config->Get('Crypt')->{'Enable'};
+-
+-my @results;
+-
+-my $UserObj = RT::User->new( $session{'CurrentUser'} );
+-$UserObj->Load( $id );
+-unless ( $UserObj->id ) {
+- Abort( loc("Couldn't load user #[_1]", $id) );
+-}
+-$id = $ARGS{'id'} = $UserObj->id;
+-
+-my @potential_keys;
+-my $email = $UserObj->EmailAddress;
+-
+-if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
+- my %keys_meta = RT::Crypt->GetKeysForSigning( Signer => $email, Protocol => 'GnuPG' );
+- @potential_keys = map $_->{'Key'}, @{ $keys_meta{'info'} || [] };
+-
+- $ARGS{'PrivateKey'} = $m->comp('/Widgets/Form/Select:Process',
+- Name => 'PrivateKey',
+- Arguments => \%ARGS,
+- Default => 1,
+- );
+-
+- if ( $Update ) {
+- if (not $ARGS{'PrivateKey'} or grep {$_ eq $ARGS{'PrivateKey'}} @potential_keys) {
+- if (($ARGS{'PrivateKey'}||'') ne ($UserObj->PrivateKey||'')) {
+- my ($status, $msg) = $UserObj->SetPrivateKey( $ARGS{'PrivateKey'} );
+- push @results, $msg;
+- }
+- } else {
+- push @results, loc("Invalid key [_1] for address '[_2]'", $ARGS{'PrivateKey'}, $email);
+- }
+- }
+-}
+-
+-if (RT::Crypt->UseForOutgoing eq 'SMIME') {
+- if ( $Update and ($ARGS{'SMIMECertificate'}||'') ne ($UserObj->SMIMECertificate||'') ) {
+- my ($status, $msg) = $UserObj->SetSMIMECertificate( $ARGS{'SMIMECertificate'} );
+- push @results, $msg;
+- }
+-}
+-
+-</%INIT>
+
+diff --git a/share/html/Admin/Users/Keys.html b/share/html/Admin/Users/Keys.html
+new file mode 100644
+--- /dev/null
++++ b/share/html/Admin/Users/Keys.html
+@@
++%# BEGIN BPS TAGGED BLOCK {{{
++%#
++%# COPYRIGHT:
++%#
++%# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
++%# <sales at bestpractical.com>
++%#
++%# (Except where explicitly superseded by other copyright notices)
++%#
++%#
++%# LICENSE:
++%#
++%# This work is made available to you under the terms of Version 2 of
++%# the GNU General Public License. A copy of that license should have
++%# been provided with this software, but in any event can be snarfed
++%# from www.gnu.org.
++%#
++%# This work is distributed in the hope that it will be useful, but
++%# WITHOUT ANY WARRANTY; without even the implied warranty of
++%# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++%# General Public License for more details.
++%#
++%# You should have received a copy of the GNU General Public License
++%# along with this program; if not, write to the Free Software
++%# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
++%# 02110-1301 or visit their web page on the internet at
++%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
++%#
++%#
++%# CONTRIBUTION SUBMISSION POLICY:
++%#
++%# (The following paragraph is not intended to limit the rights granted
++%# to you to modify and distribute this software under the terms of
++%# the GNU General Public License and is only of importance to you if
++%# you choose to contribute your changes and enhancements to the
++%# community by submitting them to Best Practical Solutions, LLC.)
++%#
++%# By intentionally submitting any modifications, corrections or
++%# derivatives to this work, or any other work intended for use with
++%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
++%# you are the copyright holder for those contributions and you grant
++%# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
++%# royalty-free, perpetual, license to use, copy, create derivative
++%# works based on those contributions, and sublicense and distribute
++%# those contributions and any derivatives thereof.
++%#
++%# END BPS TAGGED BLOCK }}}
++<& /Admin/Elements/Header, Title => loc("[_1]'s encryption keys",$UserObj->Name) &>
++<& /Elements/Tabs &>
++
++<& /Elements/ListActions, actions => \@results &>
++
++% if ( $email ) {
++<& /Admin/Elements/ShowKeyInfo, EmailAddress => $email &>
++% } else {
++<h2><% loc("User has empty email address") %></h2>
++% }
++
++<form action="<%RT->Config->Get('WebPath')%>/Admin/Users/Keys.html" method="post" enctype="multipart/form-data">
++<input type="hidden" class="hidden" name="id" value="<% $UserObj->Id %>" />
++
++% if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
++<&|/Widgets/TitleBox, title => 'GnuPG private key'&>
++<& /Widgets/Form/Select,
++ Name => 'PrivateKey',
++ Description => loc('Private Key'),
++ Values => \@potential_keys,
++ CurrentValue => $UserObj->PrivateKey,
++ DefaultLabel => loc('No private key'),
++&>
++</&>
++% }
++
++% if (RT::Crypt->UseForOutgoing eq 'SMIME') {
++<&|/Widgets/TitleBox, title => 'SMIME Certificate' &>
++<textarea name="SMIMECertificate"><% $UserObj->SMIMECertificate || '' %></textarea>
++</&>
++% }
++
++<& /Elements/Submit, Name => 'Update', Label => loc('Save Changes') &>
++</form>
++
++<%ARGS>
++$id => undef
++$Update => undef
++</%ARGS>
++<%INIT>
++return unless RT->Config->Get('Crypt')->{'Enable'};
++
++my @results;
++
++my $UserObj = RT::User->new( $session{'CurrentUser'} );
++$UserObj->Load( $id );
++unless ( $UserObj->id ) {
++ Abort( loc("Couldn't load user #[_1]", $id) );
++}
++$id = $ARGS{'id'} = $UserObj->id;
++
++my @potential_keys;
++my $email = $UserObj->EmailAddress;
++
++if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
++ my %keys_meta = RT::Crypt->GetKeysForSigning( Signer => $email, Protocol => 'GnuPG' );
++ @potential_keys = map $_->{'Key'}, @{ $keys_meta{'info'} || [] };
++
++ $ARGS{'PrivateKey'} = $m->comp('/Widgets/Form/Select:Process',
++ Name => 'PrivateKey',
++ Arguments => \%ARGS,
++ Default => 1,
++ );
++
++ if ( $Update ) {
++ if (not $ARGS{'PrivateKey'} or grep {$_ eq $ARGS{'PrivateKey'}} @potential_keys) {
++ if (($ARGS{'PrivateKey'}||'') ne ($UserObj->PrivateKey||'')) {
++ my ($status, $msg) = $UserObj->SetPrivateKey( $ARGS{'PrivateKey'} );
++ push @results, $msg;
++ }
++ } else {
++ push @results, loc("Invalid key [_1] for address '[_2]'", $ARGS{'PrivateKey'}, $email);
++ }
++ }
++}
++
++if (RT::Crypt->UseForOutgoing eq 'SMIME') {
++ if ( $Update and ($ARGS{'SMIMECertificate'}||'') ne ($UserObj->SMIMECertificate||'') ) {
++ my ($status, $msg) = $UserObj->SetSMIMECertificate( $ARGS{'SMIMECertificate'} );
++ push @results, $msg;
++ }
++}
++
++</%INIT>
diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
new file mode 100644
@@ -163,7 +437,6 @@
+% }
+
+<%INIT>
-+require RT::Crypt::GnuPG;
+my $d;
+
+my %res = RT::Crypt->GetKeysForEncryption($EmailAddress);
@@ -185,6 +458,7 @@
+$EmailAddress => undef
+$Default => undef
+</%ARGS>
++
diff --git a/share/html/Elements/Crypt/SelectKeyForSigning b/share/html/Elements/Crypt/SelectKeyForSigning
new file mode 100644
@@ -256,7 +530,7 @@
+# XXX: Only GnuPG at this moment supports user's private keys
+my $user_key;
+$user_key = $User->PrivateKey
-+ if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';;
++ if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';
+</%INIT>
diff --git a/share/html/Elements/Crypt/SignEncryptWidget b/share/html/Elements/Crypt/SignEncryptWidget
@@ -614,7 +888,6 @@
-% }
-
-<%INIT>
--require RT::Crypt::GnuPG;
-my $d;
-
-my %res = RT::Crypt->GetKeysForEncryption($EmailAddress);
@@ -636,6 +909,7 @@
-$EmailAddress => undef
-$Default => undef
-</%ARGS>
+-
diff --git a/share/html/Elements/GnuPG/SelectKeyForSigning b/share/html/Elements/GnuPG/SelectKeyForSigning
deleted file mode 100644
@@ -707,7 +981,7 @@
-# XXX: Only GnuPG at this moment supports user's private keys
-my $user_key;
-$user_key = $User->PrivateKey
-- if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';;
+- if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';
-</%INIT>
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -973,7 +1247,7 @@
+my @runs;
+my $needs_unsigned_warning = $WarnUnsigned;
+
-+my @protocols = RT::Crypt->Protocols;
++my @protocols = RT::Crypt->EnabledProtocols;
+my $re_protocols = join '|', map "\Q$_\E", @protocols;
+
+foreach ( $Attachment->SplitHeaders ) {
@@ -1016,29 +1290,25 @@
+ return (0, "Couldn't parse content of attachment #". $original->id);
+ }
+
-+ use RT::Interface::Email::Auth::Crypt;
-+ my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
-+ if ( $status && !@res ) {
-+ # imposible in this situation
-+ return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
-+ }
-+ elsif ( @res ) {
-+ require RT::Crypt;
-+
-+ $top->DelHeader('X-RT-GnuPG-Status');
-+ $top->AddHeader(map { ('X-RT-GnuPG-Status' => $_->{'status'} ) } @res);
-+ $top->SetHeader('X-RT-Privacy' => 'GnuPG' );
-+ $top->DelHeader('X-RT-Incoming-Signature');
-+
-+ my @status = RT::Crypt->ParseStatus(
-+ Protocol => $res[0]{'Protocol'},
-+ Status => $res[0]{'status'},
-+ );
-+ for ( @status ) {
-+ if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) {
-+ $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} );
-+ $needs_unsigned_warning = 0;
-+ }
++ my @res = RT::Crypt->VerifyDecrypt( Entity => $entity );
++ return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted")
++ unless @res;
++
++ $top->DelHeader("X-RT-$_-Status") for RT::Crypt->Protocols;
++ $top->AddHeader(map { ("X-RT-". $_->{Protocol} ."-Status" => $_->{'status'} ) } @res);
++ $top->DelHeader("X-RT-Privacy");
++ my %protocols; $protocols{$_->{Protocol}}++ for @res;
++ $top->AddHeader('X-RT-Privacy' => $_ ) for sort keys %protocols;
++
++ $top->DelHeader('X-RT-Incoming-Signature');
++ my @status = RT::Crypt->ParseStatus(
++ Protocol => $res[0]{'Protocol'},
++ Status => $res[0]{'status'},
++ );
++ for ( @status ) {
++ if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) {
++ $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} );
++ $needs_unsigned_warning = 0;
+ }
+ }
+ return (1, "Reverified original message");
@@ -1164,7 +1434,7 @@
-my @runs;
-my $needs_unsigned_warning = $WarnUnsigned;
-
--my @protocols = RT::Crypt->Protocols;
+-my @protocols = RT::Crypt->EnabledProtocols;
-my $re_protocols = join '|', map "\Q$_\E", @protocols;
-
-foreach ( $Attachment->SplitHeaders ) {
@@ -1207,29 +1477,25 @@
- return (0, "Couldn't parse content of attachment #". $original->id);
- }
-
-- use RT::Interface::Email::Auth::Crypt;
-- my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
-- if ( $status && !@res ) {
-- # imposible in this situation
-- return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
-- }
-- elsif ( @res ) {
-- require RT::Crypt;
--
-- $top->DelHeader('X-RT-GnuPG-Status');
-- $top->AddHeader(map { ('X-RT-GnuPG-Status' => $_->{'status'} ) } @res);
-- $top->SetHeader('X-RT-Privacy' => 'GnuPG' );
-- $top->DelHeader('X-RT-Incoming-Signature');
--
-- my @status = RT::Crypt->ParseStatus(
-- Protocol => $res[0]{'Protocol'},
-- Status => $res[0]{'status'},
-- );
-- for ( @status ) {
-- if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) {
-- $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} );
-- $needs_unsigned_warning = 0;
-- }
+- my @res = RT::Crypt->VerifyDecrypt( Entity => $entity );
+- return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted")
+- unless @res;
+-
+- $top->DelHeader("X-RT-$_-Status") for RT::Crypt->Protocols;
+- $top->AddHeader(map { ("X-RT-". $_->{Protocol} ."-Status" => $_->{'status'} ) } @res);
+- $top->DelHeader("X-RT-Privacy");
+- my %protocols; $protocols{$_->{Protocol}}++ for @res;
+- $top->AddHeader('X-RT-Privacy' => $_ ) for sort keys %protocols;
+-
+- $top->DelHeader('X-RT-Incoming-Signature');
+- my @status = RT::Crypt->ParseStatus(
+- Protocol => $res[0]{'Protocol'},
+- Status => $res[0]{'status'},
+- );
+- for ( @status ) {
+- if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) {
+- $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} );
+- $needs_unsigned_warning = 0;
- }
- }
- return (1, "Reverified original message");
@@ -1286,25 +1552,51 @@
-
-</%INIT>
+diff --git a/share/html/Elements/ShowHistory b/share/html/Elements/ShowHistory
+--- a/share/html/Elements/ShowHistory
++++ b/share/html/Elements/ShowHistory
+@@
+ UpdatePath => 'Update.html',
+ ForwardPath => 'Forward.html',
+ EmailRecordPath => 'ShowEmailRecord.html',
+- EncryptionPath => 'GnuPG.html',
++ EncryptionPath => 'Crypt.html',
+ );
+
+ while ( my ($arg, $path) = each %tmp ) {
+
diff --git a/share/html/Elements/ShowTransactionAttachments b/share/html/Elements/ShowTransactionAttachments
--- a/share/html/Elements/ShowTransactionAttachments
+++ b/share/html/Elements/ShowTransactionAttachments
@@
foreach my $message ( @{ $Attachments->{ $Parent || 0 } || [] } ) {
- if (RT->Config->Get('GnuPG')->{'Enable'}) {
+ if (RT->Config->Get('Crypt')->{'Enable'}) {
- $m->comp( 'ShowGnuPGStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
+ $m->comp( 'ShowCryptStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
}
$m->comp( 'ShowMessageHeaders',
+diff --git a/share/html/Elements/Tabs b/share/html/Elements/Tabs
+--- a/share/html/Elements/Tabs
++++ b/share/html/Elements/Tabs
+@@
+ path => '/Admin/Users/DashboardsInMenu.html?id=' . $id,
+ );
+ if ( RT->Config->Get('Crypt')->{'Enable'} ) {
+- $tabs->child( keys => title => loc('Private keys'), path => "/Admin/Users/GnuPG.html?id=" . $id );
++ $tabs->child( keys => title => loc('Private keys'), path => "/Admin/Users/Keys.html?id=" . $id );
+ }
+ $tabs->child( 'summary' => title => loc('User Summary'), path => "/User/Summary.html?id=" . $id );
+ }
+
diff --git a/share/html/Prefs/Other.html b/share/html/Prefs/Other.html
--- a/share/html/Prefs/Other.html
+++ b/share/html/Prefs/Other.html
@@
- % if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+ % if ( RT->Config->Get('Crypt')->{'Enable'} ) {
<&|/Widgets/TitleBox, title => loc( 'Cryptography' ) &>
-<&|/l&>Preferred key</&>: <& /Elements/GnuPG/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
+<&|/l&>Preferred key</&>: <& /Elements/Crypt/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
@@ -1438,7 +1730,7 @@
+
+my $attachments = $txn->Attachments;
+while ( my $attachment = $attachments->Next ) {
-+ next unless $attachment->ContentType =~ m{^x-application-rt/gpg-encrypted\b};
++ next unless $attachment->ContentType =~ m{^x-application-rt/[^-]+-encrypted\b};
+ $encrypted = 1;
+ last;
+}
@@ -1544,7 +1836,7 @@
-
-my $attachments = $txn->Attachments;
-while ( my $attachment = $attachments->Next ) {
-- next unless $attachment->ContentType =~ m{^x-application-rt/gpg-encrypted\b};
+- next unless $attachment->ContentType =~ m{^x-application-rt/[^-]+-encrypted\b};
- $encrypted = 1;
- last;
-}
147: ff6ec4b < ---: ------- return more of master's RT::Test
148: 89cf615 < ---: ------- we do the same in bootstrap_logging
149: 59808f4 < ---: ------- separate smime keyring dir from gpg's
150: 1a9914a < ---: ------- AllowEncryptDataInDB is in %Crypt now, not %GnuPG
151: d128eb2 < ---: ------- Switch to using the FormatStatus method
152: e611e98 < ---: ------- Show status if you're only using S/MIME
153: ce9b99a < ---: ------- Switch the label because there can be S/MIME messages
155: ea52566 < ---: ------- more protection, make sure crypt modules are laoded
156: ec225ff < ---: ------- drop shift() call, shouldn't be there
157: 5ef4c37 < ---: ------- be double sure openssl path is set to something
158: 1b9e57b < ---: ------- special case of how openssl prints SMIME certs
159: 3278e04 < ---: ------- special case of how openssl prints SMIME certs
160: 0a70506 < ---: ------- newlines don't survive in header fields
163: 956f749 < ---: ------- format status string so multiple can be appended together
164: 4f41cea < ---: ------- fix detecting of bad recipients during SMIME encryption
165: e64d80e < ---: ------- test how we parse certificate information
166: 69c555f ! 108: da8523b Reword UI messages implying the GnuPG is the only form of encryption
@@ -1,6 +1,36 @@
-Author: Ruslan Zakirov <ruz at bestpractical.com>
+Author: Alex Vandiver <alexmv at bestpractical.com>
- adjust wording in KeyIssues to suite GnuPG and SMIME
+ Reword UI messages implying the GnuPG is the only form of encryption
+
+diff --git a/share/html/Admin/Queues/Modify.html b/share/html/Admin/Queues/Modify.html
+--- a/share/html/Admin/Queues/Modify.html
++++ b/share/html/Admin/Queues/Modify.html
+@@
+ % if ( my $email = $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') ) {
+ <& /Admin/Elements/ShowKeyInfo, Type => 'private', EmailAddress => $email &>
+ % } else {
+-<&|/Widgets/TitleBox, title => loc( 'GnuPG private keys') &>
+-<i><&|/l&>You have enabled GnuPG support but have not set a correspondence address for this queue.</&>
+-<&|/l&>You must set a correspondence address for this queue in order to configure a GnuPG private key.</&></i>
++<&|/Widgets/TitleBox, title => loc( 'Private keys') &>
++<i><&|/l&>You have enabled encryption support but have not set a correspondence address for this queue.</&>
++<&|/l&>You must set a correspondence address for this queue in order to configure a private key.</&></i>
+ </&>
+ % }
+ </td></tr>
+@@
+ % if ( my $email = $QueueObj->CommentAddress || RT->Config->Get('CommentAddress') ) {
+ <& /Admin/Elements/ShowKeyInfo, Type => 'private', EmailAddress => $email &>
+ % } else {
+-<&|/Widgets/TitleBox, title => loc( 'GnuPG private keys') &>
+-<i><&|/l&>You have enabled GnuPG support but have not set a comment address for this queue.</&>
+-<&|/l&>You must set a comment address for this queue in order to configure a GnuPG private key.</&></i>
++<&|/Widgets/TitleBox, title => loc( 'Private keys') &>
++<i><&|/l&>You have enabled encryption support but have not set a comment address for this queue.</&>
++<&|/l&>You must set a comment address for this queue in order to configure a private key.</&></i>
+ </&>
+ %}
+ </td></tr>
diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
--- a/share/html/Elements/Crypt/KeyIssues
@@ -41,3 +71,29 @@
Name => 'UseKey-'. $issue->{'EmailAddress'},
EmailAddress => $issue->{'EmailAddress'},
+diff --git a/share/html/Elements/ShowCryptStatus b/share/html/Elements/ShowCryptStatus
+--- a/share/html/Elements/ShowCryptStatus
++++ b/share/html/Elements/ShowCryptStatus
+@@
+ %#
+ %# END BPS TAGGED BLOCK }}}
+ <table class="crypt-runs">
+-<tr><td align="right" class="labeltop" rowspan="<% scalar @messages %>">GnuPG:</td>
++<tr><td align="right" class="labeltop" rowspan="<% scalar @messages %>">Encryption:</td>
+ <td><% shift @messages %></td></tr>
+
+ % foreach my $msg( @messages ) {
+
+diff --git a/share/html/Elements/ShowTransaction b/share/html/Elements/ShowTransaction
+--- a/share/html/Elements/ShowTransaction
++++ b/share/html/Elements/ShowTransaction
+@@
+ && RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'}
+ ) {
+ push @actions, {
+- class => "gpg-link",
++ class => "encryption-link",
+ title => loc('Encrypt/Decrypt'),
+ path => $EncryptionPath
+ .'?id='. $Transaction->id
+
---: ------- > 109: d79a53c Display Created and Expire dates in the user's preferred format by setting CurrentUser
---: ------- > 110: bc4b1b1 On UIDs with neither expiration nor created dates (SMIME), skip the dates
167: 925e346 ! 111: ff42449 Display GnuPG/SMIME issues box in yellow, much like results
@@ -1,6 +1,6 @@
Author: Ruslan Zakirov <ruz at bestpractical.com>
- show GnuPG/SMIME issues box in yellow like results
+ Display GnuPG/SMIME issues box in yellow, much like results
diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
--- a/share/html/Elements/Crypt/KeyIssues
169: 8f3de9d < ---: ------- test that notification to RT owner has correct info
170: a99b5b1 < ---: ------- Adapt merged 4.2/gnupg-refactor for changes on the branch
171: 64b0f76 < ---: ------- Fix a typo, preventing emails from setting internal encryption header
172: bc87c56 < ---: ------- Remove internal signing and encryption hints from incoming mail
173: 178a31c < ---: ------- Refactor shared code controlling if a message will be encrypted or signed (cherry picked from commit 8f242072bb196e155ec9a5fbbf06bc8a2d3c2813)
174: 35d74e1 < ---: ------- The "Always Sign, even redistributed email" option is now called SignAuto
175: 6c2ef43 < ---: ------- Stop looking for things in the GnuPG configuration
176: 62adec8 < ---: ------- Let MIME::Head modify the crypt status headers to handle continuations
177: 9c479ba < ---: ------- make SMIME more verbose on how it looks for keys
178: f20eced < ---: ------- test fixup, handle warning in t/crypt/no-signer-address.t
179: a958339 < ---: ------- test fixup, handle expected warnings
180: 6c53063 < ---: ------- test fixup, handle expected warning
181: 97898dc < ---: ------- fixup, we changed privacy from PGP to GnuPG
182: b6ad583 < ---: ------- test expects SignAuto
183: 03f80d8 < ---: ------- some expected warnings in the test
184: 6341237 < ---: ------- fixup, misplaced code
185: f8f8410 < ---: ------- fixup, typo on conflict resolution
186: e23e280 < ---: ------- License tag new files
187: fb0de06 < ---: ------- Fix tests to pass updated policy
188: a0951a3 < ---: ------- GetKeyContent should return a scalar value
189: bc35471 < ---: ------- Don't generate warnings if there is no 'SMIME Key' CF
190: ae49844 < ---: ------- Adjust test for new title as of 3ed0f64
191: 69f2731 < ---: ------- Adjust security signing test for new GetKeysInfo API
192: e702e73 < ---: ------- The top-level $msg is a multipart/alternative; use $txn->content for the body
---: ------- > 112: 5750602 Resolve SMIME/GnuPG inconsistency when asking for non-existent keys
---: ------- > 113: ead85be Visualize trust level of signing entity
More information about the Rt-commit
mailing list