[Rt-commit] rt branch, 4.4-trunk, updated. rt-4.4.4-182-g2e3c228f33

? sunnavy sunnavy at bestpractical.com
Tue Nov 24 13:53:18 EST 2020


The branch, 4.4-trunk has been updated
       via  2e3c228f330ae0048a6674bdb7302d8afa87d3c9 (commit)
       via  c38437afba84a38bc48049f77fd07f6ae2da7293 (commit)
       via  f0d5babe23d74e12e771a130eae782d2e1069738 (commit)
       via  ee1985edc64409d8d7ecca5cbecb0ece483b165a (commit)
       via  c3f994533048bf11e1d9c0db8a6d895d5672280b (commit)
       via  ca1cee922ea1c084449d442158a62cd4ddc9eed1 (commit)
      from  fed93849071b9460953c36d7f2a6d74f2588c953 (commit)

Summary of changes:
 lib/RT/Attachment.pm                               |   5 +-
 lib/RT/Config.pm                                   |  29 +++-
 lib/RT/Crypt.pm                                    | 116 +++++++++++----
 lib/RT/Interface/Email.pm                          |   3 +
 lib/RT/Test/{GnuPG.pm => Crypt.pm}                 | 157 ++++++++++++++++-----
 lib/RT/Test/GnuPG.pm                               |   7 +
 lib/RT/Test/SMIME.pm                               |   9 ++
 lib/RT/User.pm                                     |   2 +-
 share/html/Admin/Users/Keys.html                   |   8 +-
 share/html/Elements/Crypt/KeyIssues                |   4 +
 share/html/Elements/Crypt/SelectKeyForEncryption   |   6 +-
 share/html/Elements/Crypt/SignEncryptWidget        |   9 +-
 share/html/Elements/RT__Ticket/ColumnMap           |  10 +-
 share/html/Prefs/Other.html                        |   2 +-
 share/html/Ticket/Create.html                      |   2 +-
 share/html/Ticket/Forward.html                     |   2 +-
 share/html/Ticket/Update.html                      |   2 +-
 share/html/m/ticket/create                         |   2 +-
 share/html/m/ticket/reply                          |   2 +-
 t/crypt/gnupg/attachments-in-db.t                  |   3 +-
 t/crypt/no-signer-address.t                        |   3 +-
 t/crypt/smime/attachments-in-db.t                  |   4 +-
 t/crypt/smime/bad-recipients.t                     |   6 +-
 t/crypt/smime/extract-email-address.t              |   2 +-
 t/crypt/smime/other-certs.t                        |  10 +-
 t/mail/crypt-gnupg.t                               |  29 ++--
 ...going.t => crypt-per-queue-outgoing-protocol.t} |  37 ++++-
 t/mail/gnupg-bad.t                                 |   5 +-
 t/mail/gnupg-incoming.t                            |   7 +-
 t/mail/gnupg-outgoing-encrypted-plaintext.t        |   3 +-
 t/mail/gnupg-outgoing-encrypted.t                  |   3 +-
 t/mail/gnupg-outgoing-plain-plaintext.t            |   3 +-
 t/mail/gnupg-outgoing-plain.t                      |   3 +-
 t/mail/gnupg-outgoing-signed-plaintext.t           |   3 +-
 t/mail/gnupg-outgoing-signed.t                     |   3 +-
 t/mail/gnupg-outgoing-signed_encrypted-plaintext.t |   3 +-
 t/mail/gnupg-outgoing-signed_encrypted.t           |   3 +-
 t/mail/gnupg-realmail.t                            |   2 +-
 t/mail/gnupg-reverification.t                      |   2 +-
 t/mail/gnupg-special.t                             |   2 +-
 t/mail/smime/incoming.t                            |  20 +--
 t/mail/smime/other-signed.t                        |  18 +--
 t/mail/smime/outgoing.t                            |  10 +-
 t/mail/smime/realmail.t                            |  10 +-
 t/mail/smime/reject_on_unencrypted.t               |  16 +--
 .../CVE-2012-4735-incoming-encryption-header.t     |   2 +-
 t/security/CVE-2012-4735-sign-any-key.t            |   5 +-
 t/security/CVE-2012-4735-sign-encrypt-header.t     |   3 +-
 t/web/admin_user.t                                 |   3 +-
 t/web/crypt-gnupg.t                                |   7 +-
 t/web/gnupg-headers.t                              |   3 +-
 t/web/gnupg-select-keys-on-create.t                |  10 +-
 t/web/gnupg-select-keys-on-update.t                |  10 +-
 t/web/gnupg-tickyboxes.t                           |   2 +-
 t/web/smime/outgoing.t                             |  51 +++----
 55 files changed, 466 insertions(+), 217 deletions(-)
 copy lib/RT/Test/{GnuPG.pm => Crypt.pm} (75%)
 copy t/mail/{smime/outgoing.t => crypt-per-queue-outgoing-protocol.t} (58%)

- Log -----------------------------------------------------------------
commit ca1cee922ea1c084449d442158a62cd4ddc9eed1
Author: Dianne Skoll <dianne at bestpractical.com>
Date:   Fri Nov 6 11:22:57 2020 -0500

    Merge RT::Test::GnuPG and RT::Test::SMIME into a single package RT::Test::Crypt
    
    Supply at least one of GnuPG => 1 or SMIME => 1 when you use
    RT::Test::Crypt to enable tests for that encryption protocol.

diff --git a/lib/RT/Test/Crypt.pm b/lib/RT/Test/Crypt.pm
new file mode 100644
index 0000000000..19517a6225
--- /dev/null
+++ b/lib/RT/Test/Crypt.pm
@@ -0,0 +1,505 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2020 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::Test::Crypt;
+use strict;
+use warnings;
+use Test::More;
+use RT::Crypt;
+use base qw(RT::Test);
+use File::Temp qw(tempdir);
+use IPC::Run3 'run3';
+use File::Copy;
+use 5.010;
+
+our @EXPORT =
+  qw(create_a_ticket update_ticket cleanup_headers set_queue_crypt_options 
+          check_text_emails send_email_and_check_transaction
+          create_and_test_outgoing_emails
+          );
+
+our $UsingGnuPG = 0;
+sub import {
+    my $class = shift;
+    my %args  = @_;
+    my $t     = $class->builder;
+
+    if ($args{GnuPG}) {
+        $UsingGnuPG = 1;
+        RT::Test::plan( skip_all => 'ENV SKIP_GPG_TESTS is set to true.' )
+            if $ENV{'SKIP_GPG_TESTS'};
+        RT::Test::plan( skip_all => 'GnuPG required.' )
+            unless GnuPG::Interface->require;
+        RT::Test::plan( skip_all => 'gpg executable is required.' )
+            unless RT::Test->find_executable('gpg');
+    }
+
+    if ($args{SMIME}) {
+        RT::Test::plan( skip_all => 'openssl executable is required.' )
+            unless RT::Test->find_executable('openssl');
+    }
+    $class->SUPER::import(%args);
+    return $class->export_to_level(1)
+        if $^C;
+
+    RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'};
+
+    $class->set_rights(
+        Principal => 'Everyone',
+        Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+    );
+
+    $class->export_to_level(1);
+}
+
+sub bootstrap_more_config {
+    my $self = shift;
+    my $handle = shift;
+    my $args = shift;
+
+    $self->SUPER::bootstrap_more_config($handle, $args, @_);
+
+    if ($args->{GnuPG}) {
+        my %gnupg_options = (
+            'no-permission-warning' => undef,
+            $args->{gnupg_options} ? %{ $args->{gnupg_options} } : (),
+            );
+        $gnupg_options{homedir} ||= new_homedir();
+
+        my $conf = File::Spec->catfile( $gnupg_options{homedir}, 'gpg.conf' );
+        if ( gnupg_version() >= 2 ) {
+            open my $fh, '>', $conf or die $!;
+            print $fh "pinentry-mode loopback\n";
+            close $fh;
+        }
+        else {
+            unlink $conf if -e $conf;
+        }
+
+        use Data::Dumper;
+        local $Data::Dumper::Terse = 1; # "{...}" instead of "$VAR1 = {...};"
+        my $dumped_gnupg_options = Dumper(\%gnupg_options);
+
+        print $handle qq{
+        Set(\%GnuPG, (
+    Enable                 => 1,
+    OutgoingMessagesFormat => 'RFC',
+            ));
+        Set(\%GnuPGOptions => \%{ $dumped_gnupg_options });
+        };
+    }
+
+    if ($args->{SMIME}) {
+        my $openssl = $self->find_executable('openssl');
+
+        my $keyring = $self->smime_keyring_path;
+        mkdir($keyring);
+
+        my $ca = $self->smime_key_path("demoCA", "cacert.pem");
+
+        if (!$args->{GnuPG}) {
+            print $handle qq{ Set(\%GnuPG, Enable => 0); };
+        }
+        print $handle qq{
+        Set(\%SMIME =>
+            Enable => 1,
+            Passphrase => {
+        'root\@example.com' => '123456',
+            'sender\@example.com' => '123456',
+        },
+            OpenSSL => q{$openssl},
+            Keyring => q{$keyring},
+            CAPath  => q{$ca},
+            );
+        };
+
+    }
+}
+
+sub smime_keyring_path {
+    return File::Spec->catfile( RT::Test->temp_directory, "smime" );
+}
+
+sub smime_key_path {
+    my $self = shift;
+    my $keys = RT::Test::find_relocatable_path(
+        qw(data smime keys),
+    );
+    return File::Spec->catfile( $keys => @_ ),
+}
+
+sub smime_mail_set_path {
+    my $self = shift;
+    return RT::Test::find_relocatable_path(
+        qw(data smime mails),
+    );
+}
+
+sub smime_import_key {
+    my $self = shift;
+    my $key  = shift;
+    my $user = shift;
+
+    my $path = RT::Test::find_relocatable_path( 'data', 'smime', 'keys' );
+    die "can't find the dir where smime keys are stored"
+        unless $path;
+
+    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
+    die "SMIME keyring '$keyring' doesn't exist"
+        unless $keyring && -e $keyring;
+
+    $key .= ".pem" unless $key =~ /\.(pem|crt|key)$/;
+
+    my $content = RT::Test->file_content( [ $path, $key ] );
+
+    if ( $user ) {
+        my ($status, $msg) = $user->SetSMIMECertificate( $content );
+        die "Couldn't set CF: $msg" unless $status;
+    } else {
+        my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
+        die "SMIME keyring '$keyring' doesn't exist"
+            unless $keyring && -e $keyring;
+
+        open my $fh, '>:raw', File::Spec->catfile($keyring, $key)
+            or die "can't open file: $!";
+        print $fh $content;
+        close $fh;
+    }
+
+    return;
+}
+
+sub create_a_ticket {
+    my $queue = shift;
+    my $mail = shift;
+    my $m = shift;
+    my %args = (@_);
+
+    RT::Test->clean_caught_mails;
+
+    $m->goto_create_ticket( $queue );
+    $m->form_name('TicketCreate');
+    $m->field( Subject    => 'test' );
+    $m->field( Requestors => 'rt-test at example.com' );
+    $m->field( Content    => 'Some content' );
+
+    foreach ( qw(Sign Encrypt) ) {
+        if ( $args{ $_ } ) {
+            $m->tick( $_ => 1 );
+        } else {
+            $m->untick( $_ => 1 );
+        }
+    }
+
+    $m->submit;
+    is $m->status, 200, "request successful";
+
+    $m->content_lacks("unable to sign outgoing email messages");
+
+
+    my @mail = RT::Test->fetch_caught_mails;
+    check_text_emails(\%args, @mail );
+    categorize_emails($mail, \%args, @mail );
+}
+
+sub update_ticket {
+    my $tid = shift;
+    my $mail = shift;
+    my $m = shift;
+    my %args = (@_);
+
+    RT::Test->clean_caught_mails;
+
+    $m->get( $m->rt_base_url . "/Ticket/Update.html?Action=Respond&id=$tid" );
+    $m->form_number(3);
+    $m->field( UpdateContent => 'Some content' );
+
+    foreach ( qw(Sign Encrypt) ) {
+        if ( $args{ $_ } ) {
+            $m->tick( $_ => 1 );
+        } else {
+            $m->untick( $_ => 1 );
+        }
+    }
+
+    $m->click('SubmitTicket');
+    is $m->status, 200, "request successful";
+    $m->content_contains("Correspondence added", 'Correspondence added') or diag $m->content;
+
+
+    my @mail = RT::Test->fetch_caught_mails;
+    check_text_emails(\%args, @mail );
+    categorize_emails($mail, \%args, @mail );
+}
+
+sub categorize_emails {
+    my $mail = shift;
+    my $args = shift;
+    my @mail = @_;
+
+    if ( $args->{'Sign'} && $args->{'Encrypt'} ) {
+        push @{ $mail->{'signed_encrypted'} }, @mail;
+    }
+    elsif ( $args->{'Sign'} ) {
+        push @{ $mail->{'signed'} }, @mail;
+    }
+    elsif ( $args->{'Encrypt'} ) {
+        push @{ $mail->{'encrypted'} }, @mail;
+    }
+    else {
+        push @{ $mail->{'plain'} }, @mail;
+    }
+}
+
+sub check_text_emails {
+    my %args = %{ shift @_ };
+    my @mail = @_;
+
+    ok scalar @mail, "got some mail";
+    for my $mail (@mail) {
+        for my $type ('email', 'attachment') {
+            next if $type eq 'attachment' && !$args{'Attachment'};
+
+            my $content = $type eq 'email'
+                        ? "Some content"
+                        : $args{Attachment};
+
+            if ( $args{'Encrypt'} ) {
+                unlike $mail, qr/$content/, "outgoing $type is not in plaintext";
+                my $entity = RT::Test::parse_mail($mail);
+                my @res = RT::Crypt->VerifyDecrypt(Entity => $entity);
+                like $res[0]{'status'}, qr/DECRYPTION_OKAY/, "Decrypts OK";
+                like $entity->as_string, qr/$content/, "outgoing decrypts to contain $type content";
+            } else {
+                like $mail, qr/$content/, "outgoing $type was not encrypted";
+            }
+
+            next unless $type eq 'email';
+
+            if ( $args{'Sign'} && $args{'Encrypt'} ) {
+                like $mail, qr/BEGIN PGP MESSAGE/, 'outgoing email was signed';
+            } elsif ( $args{'Sign'} ) {
+                like $mail, qr/SIGNATURE/, 'outgoing email was signed';
+            } else {
+                unlike $mail, qr/SIGNATURE/, 'outgoing email was not signed';
+            }
+        }
+    }
+}
+
+sub cleanup_headers {
+    my $mail = shift;
+    # 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 RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
+        $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
+    }
+    return $mail;
+}
+
+sub set_queue_crypt_options {
+    my $queue = shift;
+    my %args = @_;
+    $queue->SetEncrypt($args{'Encrypt'});
+    $queue->SetSign($args{'Sign'});
+}
+
+sub send_email_and_check_transaction {
+    my $mail = shift;
+    my $type = shift;
+
+    my ( $status, $id ) = RT::Test->send_via_mailgate($mail);
+    is( $status >> 8, 0, "The mail gateway exited normally" );
+    ok( $id, "got id of a newly created ticket - $id" );
+
+    my $tick = RT::Ticket->new( RT->SystemUser );
+    $tick->Load($id);
+    ok( $tick->id, "loaded ticket #$id" );
+
+    my $txn = $tick->Transactions->First;
+    my ( $msg, @attachments ) = @{ $txn->Attachments->ItemsArrayRef };
+
+    if ( $attachments[0] ) {
+        like $attachments[0]->Content, qr/Some content/,
+          "RT's mail includes copy of ticket text";
+    }
+    else {
+        like $msg->Content, qr/Some content/,
+          "RT's mail includes copy of ticket text";
+    }
+
+    if ( $type eq 'plain' ) {
+        ok !$msg->GetHeader('X-RT-Privacy'), "RT's outgoing mail has no crypto";
+        is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
+          "RT's outgoing mail looks not encrypted";
+        ok !$msg->GetHeader('X-RT-Incoming-Signature'),
+          "RT's outgoing mail looks not signed";
+    }
+    elsif ( $type eq 'signed' ) {
+        is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
+          "RT's outgoing mail has crypto";
+        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>/,
+          "RT's outgoing mail looks signed";
+    }
+    elsif ( $type eq 'encrypted' ) {
+        is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
+          "RT's outgoing mail has crypto";
+        is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
+          "RT's outgoing mail looks encrypted";
+        ok !$msg->GetHeader('X-RT-Incoming-Signature'),
+          "RT's outgoing mail looks not signed";
+
+    }
+    elsif ( $type eq 'signed_encrypted' ) {
+        is $msg->GetHeader('X-RT-Privacy'), 'GnuPG',
+          "RT's outgoing mail has crypto";
+        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>/,
+          "RT's outgoing mail looks signed";
+    }
+    else {
+        die "unknown type: $type";
+    }
+}
+
+sub create_and_test_outgoing_emails {
+    my $queue = shift;
+    my $m     = shift;
+    my @variants =
+      ( {}, { Sign => 1 }, { Encrypt => 1 }, { Sign => 1, Encrypt => 1 }, );
+
+    # collect emails
+    my %mail;
+
+    # create a ticket for each combination
+    foreach my $ticket_set (@variants) {
+        create_a_ticket( $queue, \%mail, $m, %$ticket_set );
+    }
+
+    my $tid;
+    {
+        my $ticket = RT::Ticket->new( RT->SystemUser );
+        ($tid) = $ticket->Create(
+            Subject   => 'test',
+            Queue     => $queue->id,
+            Requestor => 'rt-test at example.com',
+        );
+        ok $tid, 'ticket created';
+    }
+
+    # again for each combination add a reply message
+    foreach my $ticket_set (@variants) {
+        update_ticket( $tid, \%mail, $m, %$ticket_set );
+    }
+
+# ------------------------------------------------------------------------------
+# 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
+# like we are on another side recieve emails
+# ------------------------------------------------------------------------------
+
+    unlink $_
+      foreach glob( RT->Config->Get('GnuPGOptions')->{'homedir'} . "/*" );
+    RT::Test->import_gnupg_key( 'rt-recipient at example.com', 'public' );
+    RT::Test->import_gnupg_key('rt-test at example.com');
+
+    $queue = RT::Test->load_or_create_queue(
+        Name              => 'Regression',
+        CorrespondAddress => 'rt-test at example.com',
+        CommentAddress    => 'rt-test at example.com',
+    );
+    ok $queue && $queue->id, 'changed props of the queue';
+
+    for my $type ( keys %mail ) {
+        for my $mail ( map cleanup_headers($_), @{ $mail{$type} } ) {
+            send_email_and_check_transaction( $mail, $type );
+        }
+    }
+}
+
+sub gnupg_version {
+    GnuPG::Interface->require or return;
+    require version;
+    state $gnupg_version = version->parse(GnuPG::Interface->new->version);
+}
+
+sub new_homedir {
+    my $source = shift;
+    my $dir = tempdir();
+
+    if ($source) {
+        opendir my $dh, $source or die $!;
+        for my $file ( grep {/\.gpg$/} readdir $dh ) {
+            copy( File::Spec->catfile( $source, $file ), File::Spec->catfile( $dir, $file ) ) or die $!;
+        }
+        closedir $dh;
+        if ( gnupg_version() >= 2 ) {
+            # Do the data migration
+            run3( [ 'gpg', '--homedir', $dir, '--list-secret-keys' ], \undef, \undef, \undef );
+        }
+    }
+
+    return $dir;
+}
+
+END {
+    if ($UsingGnuPG) {
+        if ( gnupg_version() >= 2 ) {
+            system( 'gpgconf', '--homedir', RT->Config->Get('GnuPGOptions')->{homedir}, '--quiet', '--kill', 'gpg-agent' )
+                && warn $!;
+        }
+    }
+}
+
+1;
diff --git a/t/crypt/gnupg/attachments-in-db.t b/t/crypt/gnupg/attachments-in-db.t
index 1a377c341a..90caa32fbb 100644
--- a/t/crypt/gnupg/attachments-in-db.t
+++ b/t/crypt/gnupg/attachments-in-db.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+    GnuPG         => 1,
     tests         => 12,
     gnupg_options => {
         passphrase    => 'recipient',
diff --git a/t/crypt/no-signer-address.t b/t/crypt/no-signer-address.t
index 4c459c06a8..489b5e280f 100644
--- a/t/crypt/no-signer-address.t
+++ b/t/crypt/no-signer-address.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
diff --git a/t/crypt/smime/attachments-in-db.t b/t/crypt/smime/attachments-in-db.t
index 5230938ccd..94ecf868f1 100644
--- a/t/crypt/smime/attachments-in-db.t
+++ b/t/crypt/smime/attachments-in-db.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
+use RT::Test::Crypt SMIME => 1, tests => undef;
 
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
@@ -9,7 +9,7 @@ use RT::Tickets;
 
 RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} = 1;
 
-RT::Test::SMIME->import_key('sender at example.com');
+RT::Test::Crypt->smime_import_key('sender at example.com');
 my $queue = RT::Test->load_or_create_queue(
     Name              => 'General',
     CorrespondAddress => 'sender at example.com',
diff --git a/t/crypt/smime/bad-recipients.t b/t/crypt/smime/bad-recipients.t
index 1dc097adec..ca2461c33a 100644
--- a/t/crypt/smime/bad-recipients.t
+++ b/t/crypt/smime/bad-recipients.t
@@ -1,11 +1,11 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
+use RT::Test::Crypt SMIME => 1, tests => undef;
 
 use RT::Tickets;
 
-RT::Test::SMIME->import_key('sender at example.com');
+RT::Test::Crypt->smime_import_key('sender at example.com');
 my $queue = RT::Test->load_or_create_queue(
     Name              => 'General',
     CorrespondAddress => 'sender at example.com',
@@ -25,7 +25,7 @@ my $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 );
+    RT::Test::Crypt->smime_import_key( 'root at example.com.crt' => $root );
 }
 
 my $bad_user;
diff --git a/t/crypt/smime/extract-email-address.t b/t/crypt/smime/extract-email-address.t
index ab709c58c7..20e58c0e15 100644
--- a/t/crypt/smime/extract-email-address.t
+++ b/t/crypt/smime/extract-email-address.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
+use RT::Test::Crypt SMIME => 1, tests => undef;
 
 sub extract_email_address
 {
diff --git a/t/crypt/smime/other-certs.t b/t/crypt/smime/other-certs.t
index ee067192d1..818c5e6f93 100644
--- a/t/crypt/smime/other-certs.t
+++ b/t/crypt/smime/other-certs.t
@@ -1,10 +1,10 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
+use RT::Test::Crypt SMIME => 1, tests => undef;
 use IPC::Run3 'run3';
 
-RT::Test::SMIME->import_key( 'sender at example.com' );
+RT::Test::Crypt->smime_import_key( 'sender at example.com' );
 
 diag "No OtherCertificatesToSend";
 
@@ -26,7 +26,7 @@ ok( $cert, 'got cert' );
 ok( !$err, 'no errors' );
 
 chomp $cert;
-open my $fh, '<', RT::Test::SMIME->key_path( 'sender at example.com.crt' ) or die $!;
+open my $fh, '<', RT::Test::Crypt->smime_key_path( 'sender at example.com.crt' ) or die $!;
 my $sender_cert = do { local $/; <$fh> };
 
 # Variations in how different versions of OpenSSL print certificates
@@ -39,7 +39,7 @@ is( $cert, $sender_cert, 'cert is the same one' );
 
 diag "Has OtherCertificatesToSend";
 
-RT->Config->Get( 'SMIME' )->{OtherCertificatesToSend} = RT::Test::SMIME->key_path( 'demoCA', 'cacert.pem' );
+RT->Config->Get( 'SMIME' )->{OtherCertificatesToSend} = RT::Test::Crypt->smime_key_path( 'demoCA', 'cacert.pem' );
 
 $mime = MIME::Entity->build(
     From => 'sender at example.com',
@@ -61,7 +61,7 @@ chomp $cert;
 my @certs = split /\n(?=Certificate:)/, $cert;
 is( scalar @certs, 2, 'found 2 certs' );
 
-open $fh, '<', RT::Test::SMIME->key_path( 'demoCA', 'cacert.pem' ) or die $!;
+open $fh, '<', RT::Test::Crypt->smime_key_path( 'demoCA', 'cacert.pem' ) or die $!;
 my $ca_cert = do { local $/; <$fh> };
 
 # Variations in how different versions of OpenSSL print certificates
diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
index 70c7447831..36d2ff1b44 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -4,15 +4,15 @@ use warnings;
 
 my $homedir;
 BEGIN {
-    require RT::Test::GnuPG;
-    $homedir = RT::Test::GnuPG::new_homedir(
+    require RT::Test::Crypt;
+    $homedir = RT::Test::Crypt::new_homedir(
         RT::Test::get_abs_relocatable_dir( File::Spec->updir(), qw/data gnupg keyrings/ ) );
 }
 
-use RT::Test::GnuPG tests => undef, gnupg_options => { homedir => $homedir, quiet => 1 };
+use RT::Test::Crypt GnuPG => 1, tests => undef, gnupg_options => { homedir => $homedir, quiet => 1 };
 use Test::Warn;
 
-my $gnupg_version = RT::Test::GnuPG::gnupg_version;
+my $gnupg_version = RT::Test::Crypt::gnupg_version;
 
 use_ok('RT::Crypt');
 use_ok('MIME::Entity');
@@ -24,7 +24,7 @@ diag 'only signing. correct passphrase';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test', Protocol => 'GnuPG' );
     ok( $entity, 'signed entity');
     ok( !$res{'logger'}, "log is here as well" ) or diag $res{'logger'};
     my @status = RT::Crypt->ParseStatus(
@@ -81,6 +81,7 @@ SKIP: {
     my %res;
     warning_like {
         %res = RT::Crypt->SignEncrypt(
+            Protocol   => 'GnuPG',
             Entity     => $entity,
             Encrypt    => 0,
             Passphrase => ''
@@ -108,6 +109,7 @@ diag 'only signing. wrong passphrase';
     my %res;
     warning_like {
         %res = RT::Crypt->SignEncrypt(
+            Protocol   => 'GnuPG',
             Entity     => $entity,
             Encrypt    => 0,
             Passphrase => 'wrong',
@@ -133,7 +135,7 @@ diag 'encryption only';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0, Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, "successful encryption" );
     ok( !$res{'logger'}, "no records in logger" );
 
@@ -165,6 +167,7 @@ diag 'encryption only, bad recipient';
     my %res;
     warning_like {
         %res = RT::Crypt->SignEncrypt(
+            Protocol   => 'GnuPG',
             Entity => $entity,
             Sign   => 0,
         );
@@ -195,7 +198,7 @@ diag 'encryption and signing with combined method';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Passphrase => 'test' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Passphrase => 'test', Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, "successful encryption with signing" );
     ok( !$res{'logger'}, "log is here as well" ) or diag $res{'logger'};
 
@@ -236,10 +239,10 @@ diag 'encryption and signing with cascading, sign on encrypted';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0, Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, 'successful encryption' );
     ok( !$res{'logger'}, "no records in logger" );
-    %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+    %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test', Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, 'successful signing' );
     ok( !$res{'logger'}, "no records in logger" );
 
@@ -258,7 +261,7 @@ diag 'find signed/encrypted part deep inside';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0, Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, "success" );
     $entity->make_multipart( 'mixed', Force => 1 );
     $entity->attach(
@@ -283,6 +286,7 @@ diag 'wrong signed/encrypted parts: no protocol';
     );
 
     my %res = RT::Crypt->SignEncrypt(
+        Protocol   => 'GnuPG',
         Entity => $entity,
         Sign   => 0,
     );
@@ -309,6 +313,7 @@ diag 'wrong signed/encrypted parts: not enought parts';
     );
 
     my %res = RT::Crypt->SignEncrypt(
+        Protocol   => 'GnuPG',
         Entity => $entity,
         Sign   => 0,
     );
@@ -331,7 +336,7 @@ diag 'wrong signed/encrypted parts: wrong proto';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0, Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => 'application/bad-proto' );
 
@@ -347,7 +352,7 @@ diag 'wrong signed/encrypted parts: wrong proto';
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test', Protocol => 'GnuPG' );
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => 'application/bad-proto' );
 
diff --git a/t/mail/gnupg-bad.t b/t/mail/gnupg-bad.t
index 3ecbdb9bf0..ea90e3a4cd 100644
--- a/t/mail/gnupg-bad.t
+++ b/t/mail/gnupg-bad.t
@@ -1,11 +1,12 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => 7,
   gnupg_options => {
     passphrase => 'rt-test',
-    homedir => RT::Test::GnuPG::new_homedir(RT::Test::get_abs_relocatable_dir(
+    homedir => RT::Test::Crypt::new_homedir(RT::Test::get_abs_relocatable_dir(
         File::Spec->updir(), qw/data gnupg keyrings/
     )),
   };
diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
index 7b373e54bd..2a583adb99 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -3,12 +3,13 @@ use warnings;
 
 my $homedir;
 BEGIN {
-    require RT::Test::GnuPG;
-    $homedir = RT::Test::GnuPG::new_homedir(
+    require RT::Test::Crypt;
+    $homedir = RT::Test::Crypt::new_homedir(
         RT::Test::get_abs_relocatable_dir( File::Spec->updir(), qw/data gnupg keyrings/ ) ),
 }
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => 53,
   actual_server => 1,
   gnupg_options => {
diff --git a/t/mail/gnupg-outgoing-encrypted-plaintext.t b/t/mail/gnupg-outgoing-encrypted-plaintext.t
index 9507ff4767..1325ded56a 100644
--- a/t/mail/gnupg-outgoing-encrypted-plaintext.t
+++ b/t/mail/gnupg-outgoing-encrypted-plaintext.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG          => 1,
   tests          => undef,
   text_templates => 1,
   gnupg_options  => {
diff --git a/t/mail/gnupg-outgoing-encrypted.t b/t/mail/gnupg-outgoing-encrypted.t
index 802830fcd5..eab4ab8601 100644
--- a/t/mail/gnupg-outgoing-encrypted.t
+++ b/t/mail/gnupg-outgoing-encrypted.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
diff --git a/t/mail/gnupg-outgoing-plain-plaintext.t b/t/mail/gnupg-outgoing-plain-plaintext.t
index a9a68cee9d..8cad32acd0 100644
--- a/t/mail/gnupg-outgoing-plain-plaintext.t
+++ b/t/mail/gnupg-outgoing-plain-plaintext.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG          => 1,
   tests          => undef,
   text_templates => 1,
   gnupg_options  => {
diff --git a/t/mail/gnupg-outgoing-plain.t b/t/mail/gnupg-outgoing-plain.t
index d1cd301a71..d092e934b7 100644
--- a/t/mail/gnupg-outgoing-plain.t
+++ b/t/mail/gnupg-outgoing-plain.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
diff --git a/t/mail/gnupg-outgoing-signed-plaintext.t b/t/mail/gnupg-outgoing-signed-plaintext.t
index e51676243d..a9840117c8 100644
--- a/t/mail/gnupg-outgoing-signed-plaintext.t
+++ b/t/mail/gnupg-outgoing-signed-plaintext.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG          => 1,
   tests          => undef,
   text_templates => 1,
   gnupg_options  => {
diff --git a/t/mail/gnupg-outgoing-signed.t b/t/mail/gnupg-outgoing-signed.t
index b7119a176f..153f4a64cd 100644
--- a/t/mail/gnupg-outgoing-signed.t
+++ b/t/mail/gnupg-outgoing-signed.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
diff --git a/t/mail/gnupg-outgoing-signed_encrypted-plaintext.t b/t/mail/gnupg-outgoing-signed_encrypted-plaintext.t
index 946fa76a80..fc4173d9c1 100644
--- a/t/mail/gnupg-outgoing-signed_encrypted-plaintext.t
+++ b/t/mail/gnupg-outgoing-signed_encrypted-plaintext.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG          => 1,
   tests          => undef,
   text_templates => 1,
   gnupg_options  => {
diff --git a/t/mail/gnupg-outgoing-signed_encrypted.t b/t/mail/gnupg-outgoing-signed_encrypted.t
index 61cf6b20f0..399f20f67c 100644
--- a/t/mail/gnupg-outgoing-signed_encrypted.t
+++ b/t/mail/gnupg-outgoing-signed_encrypted.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
diff --git a/t/mail/gnupg-realmail.t b/t/mail/gnupg-realmail.t
index 1609cffbba..eab0425187 100644
--- a/t/mail/gnupg-realmail.t
+++ b/t/mail/gnupg-realmail.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => 198, gnupg_options => { passphrase => 'rt-test' };
+use RT::Test::Crypt GnuPG => 1, tests => 198, gnupg_options => { passphrase => 'rt-test' };
 
 use Digest::MD5 qw(md5_hex);
 
diff --git a/t/mail/gnupg-reverification.t b/t/mail/gnupg-reverification.t
index 1ec9aa19e8..ec3b5a01fb 100644
--- a/t/mail/gnupg-reverification.t
+++ b/t/mail/gnupg-reverification.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' };
+use RT::Test::Crypt GnuPG => 1, tests => undef, gnupg_options => { passphrase => 'rt-test' };
 
 diag "load Everyone group";
 my $everyone;
diff --git a/t/mail/gnupg-special.t b/t/mail/gnupg-special.t
index 15aad3489c..c95e5bfda5 100644
--- a/t/mail/gnupg-special.t
+++ b/t/mail/gnupg-special.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => 25, gnupg_options => { passphrase => 'rt-test' };
+use RT::Test::Crypt GnuPG => 1, tests => 25, gnupg_options => { passphrase => 'rt-test' };
 
 use Digest::MD5 qw(md5_hex);
 
diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index 07897ee53d..4442c5744d 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -1,8 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef, actual_server => 1;
-my $test = 'RT::Test::SMIME';
+use RT::Test::Crypt SMIME => 1, tests => undef, actual_server => 1;
+my $test = 'RT::Test::Crypt';
 
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
@@ -13,7 +13,7 @@ my ($url, $m) = RT::Test->started_ok;
 ok $m->login, "logged in";
 
 # configure key for General queue
-RT::Test::SMIME->import_key('sender at example.com');
+$test->smime_import_key('sender at example.com');
 my $queue = RT::Test->load_or_create_queue(
     Name              => 'General',
     CorrespondAddress => 'sender at example.com',
@@ -25,7 +25,7 @@ my $user = RT::Test->load_or_create_user(
     Name => 'root at example.com',
     EmailAddress => 'root at example.com',
 );
-RT::Test::SMIME->import_key('root at example.com.crt', $user);
+$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);
@@ -63,7 +63,7 @@ RT::Test->close_mailgate_ok($mail);
             -from    => 'root at example.com',
             -to      => 'sender at example.com',
             -subject => "Encrypted message for queue",
-            $test->key_path('sender at example.com.crt'),
+            $test->smime_key_path('sender at example.com.crt'),
         ),
         \"Subject: test\n\norzzzzzz",
         \$buf,
@@ -103,8 +103,8 @@ RT::Test->close_mailgate_ok($mail);
             shell_quote(
                 RT->Config->Get('SMIME')->{'OpenSSL'},
                 qw( smime -sign -nodetach -passin pass:123456),
-                -signer => $test->key_path('root at example.com.crt'),
-                -inkey  => $test->key_path('root at example.com.key'),
+                -signer => $test->smime_key_path('root at example.com.crt'),
+                -inkey  => $test->smime_key_path('root at example.com.key'),
             ),
             '|',
             shell_quote(
@@ -112,7 +112,7 @@ RT::Test->close_mailgate_ok($mail);
                 -from    => 'root at example.com',
                 -to      => 'sender at example.com',
                 -subject => "Encrypted and signed message for queue",
-                $test->key_path('sender at example.com.crt'),
+                $test->smime_key_path('sender at example.com.crt'),
             )),
             \"Subject: test\n\norzzzzzz",
             \$buf,
@@ -144,8 +144,8 @@ RT::Test->close_mailgate_ok($mail);
         shell_quote(
             RT->Config->Get('SMIME')->{'OpenSSL'},
             qw( smime -sign -passin pass:123456),
-            -signer => $test->key_path('root at example.com.crt'),
-            -inkey  => $test->key_path('root at example.com.key'),
+            -signer => $test->smime_key_path('root at example.com.crt'),
+            -inkey  => $test->smime_key_path('root at example.com.key'),
         ),
         \"Content-type: text/plain\n\nThis is the body",
         \$buf,
diff --git a/t/mail/smime/other-signed.t b/t/mail/smime/other-signed.t
index 4e97e711f5..f4fc3d59d1 100644
--- a/t/mail/smime/other-signed.t
+++ b/t/mail/smime/other-signed.t
@@ -1,8 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
-my $test = 'RT::Test::SMIME';
+use RT::Test::Crypt SMIME=>1, tests => undef;
+my $test = 'RT::Test::Crypt';
 
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
@@ -10,7 +10,7 @@ use RT::Tickets;
 use Test::Warn;
 
 # configure key for General queue
-RT::Test::SMIME->import_key('sender at example.com');
+$test->smime_import_key('sender at example.com');
 my $queue = RT::Test->load_or_create_queue(
     Name              => 'General',
     CorrespondAddress => 'sender at example.com',
@@ -22,7 +22,7 @@ my $user = RT::Test->load_or_create_user(
     Name => 'root at example.com',
     EmailAddress => 'root at example.com',
 );
-RT::Test::SMIME->import_key('root at example.com.crt', $user);
+$test->smime_import_key('root at example.com.crt', $user);
 RT::Test->add_rights( Principal => $user, Right => 'SuperUser', Object => RT->System );
 
 my $buf = '';
@@ -31,8 +31,8 @@ run3(
     shell_quote(
         RT->Config->Get('SMIME')->{'OpenSSL'},
         qw( smime -sign -passin pass:123456),
-        -signer => $test->key_path('root at example.com.crt'),
-        -inkey  => $test->key_path('root at example.com.key'),
+        -signer => $test->smime_key_path('root at example.com.crt'),
+        -inkey  => $test->smime_key_path('root at example.com.key'),
     ),
     \"Content-type: text/plain\n\nThis is the body",
     \$buf,
@@ -83,7 +83,7 @@ warning_like {
 
 # Test with the correct CA path; marked as signed, trusted
 {
-    my ($msg, $status) = $send_mail->( CAPath => $test->key_path . "/demoCA/cacert.pem" );
+    my ($msg, $status) = $send_mail->( CAPath => $test->smime_key_path . "/demoCA/cacert.pem" );
     is( $msg->GetHeader('X-RT-Incoming-Signature'),
         '"Enoch Root" <root at example.com>', "Message is signed" );
 
@@ -94,7 +94,7 @@ warning_like {
 
 # Test with the other CA
 warning_like {
-    my ($msg, $status) = $send_mail->( CAPath => $test->key_path . "/otherCA/cacert.pem" );
+    my ($msg, $status) = $send_mail->( CAPath => $test->smime_key_path . "/otherCA/cacert.pem" );
     is( $msg->GetHeader('X-RT-Incoming-Signature'),
         undef,
         "Message was not marked as signed"
@@ -108,7 +108,7 @@ warning_like {
 
 # Other CA, but allow all CAs
 {
-    my ($msg, $status) = $send_mail->( CAPath => $test->key_path . "/otherCA/cacert.pem", AcceptUntrustedCAs => 1 );
+    my ($msg, $status) = $send_mail->( CAPath => $test->smime_key_path . "/otherCA/cacert.pem", AcceptUntrustedCAs => 1 );
     is( $msg->GetHeader('X-RT-Incoming-Signature'),
         '"Enoch Root" <root at example.com>',
         "Message was marked as signed"
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index a90c4cb2bc..7e611756be 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -1,8 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
-my $test = 'RT::Test::SMIME';
+use RT::Test::Crypt SMIME => 1, tests => undef;
+my $test = 'RT::Test::Crypt';
 
 use IPC::Run3 'run3';
 use RT::Interface::Email;
@@ -30,7 +30,7 @@ my $user;
     ok($user->Load('root'), "Loaded user 'root'");
     is($user->EmailAddress, 'root at localhost');
 
-    RT::Test::SMIME->import_key( 'root at example.com.crt' => $user );
+    $test->smime_import_key( 'root at example.com.crt' => $user );
 }
 
 RT::Test->clean_caught_mails;
@@ -65,8 +65,8 @@ END
     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')
+            '-inkey', $test->smime_key_path('root at example.com.key'),
+            '-recip', $test->smime_key_path('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
index 13c9a51521..6676de5f2e 100644
--- a/t/mail/smime/realmail.t
+++ b/t/mail/smime/realmail.t
@@ -1,16 +1,16 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
+use RT::Test::Crypt SMIME => 1, tests => undef;
 use Digest::MD5 qw(md5_hex);
 
-my $test = 'RT::Test::SMIME';
-my $mails = $test->mail_set_path;
+my $test = 'RT::Test::Crypt';
+my $mails = $test->smime_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');
+$test->smime_import_key('root at example.com');
+$test->smime_import_key('sender at example.com');
 
 my ($baseurl, $m) = RT::Test->started_ok;
 ok $m->login, 'we did log in';
diff --git a/t/mail/smime/reject_on_unencrypted.t b/t/mail/smime/reject_on_unencrypted.t
index 3d35e4f94e..0e853acb64 100644
--- a/t/mail/smime/reject_on_unencrypted.t
+++ b/t/mail/smime/reject_on_unencrypted.t
@@ -1,8 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef, actual_server => 1, config => 'Set( @MailPlugins, "Authz::RequireEncrypted" );';
-my $test = 'RT::Test::SMIME';
+use RT::Test::Crypt SMIME=>1, tests => undef, actual_server => 1, config => 'Set( @MailPlugins, "Authz::RequireEncrypted" );';
+my $test = 'RT::Test::Crypt';
 
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
@@ -12,7 +12,7 @@ my ($url, $m) = RT::Test->started_ok;
 ok $m->login, "logged in";
 
 # configure key for General queue
-RT::Test::SMIME->import_key('sender at example.com');
+$test->smime_import_key('sender at example.com');
 my $queue = RT::Test->load_or_create_queue(
     Name              => 'General',
     CorrespondAddress => 'sender at example.com',
@@ -24,7 +24,7 @@ my $user = RT::Test->load_or_create_user(
     Name => 'root at example.com',
     EmailAddress => 'root at example.com',
 );
-RT::Test::SMIME->import_key('root at example.com.crt', $user);
+$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);
@@ -59,7 +59,7 @@ RT::Test->close_mailgate_ok($mail);
             -from    => 'root at example.com',
             -to      => 'sender at example.com',
             -subject => "Encrypted message for queue",
-            $test->key_path('sender at example.com.crt' ),
+            $test->smime_key_path('sender at example.com.crt' ),
         ),
         \"Subject: test\n\norzzzzzz",
         \$buf,
@@ -99,8 +99,8 @@ RT::Test->close_mailgate_ok($mail);
             shell_quote(
                 RT->Config->Get('SMIME')->{'OpenSSL'},
                 qw( smime -sign -nodetach -passin pass:123456),
-                -signer => $test->key_path('root at example.com.crt' ),
-                -inkey  => $test->key_path('root at example.com.key' ),
+                -signer => $test->smime_key_path('root at example.com.crt' ),
+                -inkey  => $test->smime_key_path('root at example.com.key' ),
             ),
             '|',
             shell_quote(
@@ -108,7 +108,7 @@ RT::Test->close_mailgate_ok($mail);
                 -from    => 'root at example.com',
                 -to      => 'sender at example.com',
                 -subject => "Encrypted and signed message for queue",
-                $test->key_path('sender at example.com.crt' ),
+                $test->smime_key_path('sender at example.com.crt' ),
             )),
             \"Subject: test\n\norzzzzzz",
             \$buf,
diff --git a/t/security/CVE-2012-4735-incoming-encryption-header.t b/t/security/CVE-2012-4735-incoming-encryption-header.t
index bd89e67566..e067ff1136 100644
--- a/t/security/CVE-2012-4735-incoming-encryption-header.t
+++ b/t/security/CVE-2012-4735-incoming-encryption-header.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => undef;
+use RT::Test::Crypt GnuPG => 1, tests => undef;
 use Test::Warn;
 
 {
diff --git a/t/security/CVE-2012-4735-sign-any-key.t b/t/security/CVE-2012-4735-sign-any-key.t
index 248df9c55e..78e9d1bace 100644
--- a/t/security/CVE-2012-4735-sign-any-key.t
+++ b/t/security/CVE-2012-4735-sign-any-key.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
@@ -14,7 +15,7 @@ RT::Test->import_gnupg_key('general at example.com');
 # Determine the key IDs of the newly-loaded keys
 my %secret_keys;
 {
-    my %info = RT::Crypt->GetKeysInfo( Type => 'private', Force => 1 );
+    my %info = RT::Crypt->GetKeysInfo( Protocol => 'GnuPG', Type => 'private', Force => 1 );
     for my $key (@{$info{info}}) {
         my $user = $key->{User}[0]{String};
         $user = (Email::Address->parse( $user ))[0]->address;
diff --git a/t/security/CVE-2012-4735-sign-encrypt-header.t b/t/security/CVE-2012-4735-sign-encrypt-header.t
index 4cf8e00d70..de42d60668 100644
--- a/t/security/CVE-2012-4735-sign-encrypt-header.t
+++ b/t/security/CVE-2012-4735-sign-encrypt-header.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG => 1,
   tests => undef,
   gnupg_options => {
     passphrase    => 'rt-test',
diff --git a/t/web/admin_user.t b/t/web/admin_user.t
index 552543e6e7..2504bc8ff1 100644
--- a/t/web/admin_user.t
+++ b/t/web/admin_user.t
@@ -2,7 +2,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'recipient',
diff --git a/t/web/crypt-gnupg.t b/t/web/crypt-gnupg.t
index 119bd91350..550d52e11c 100644
--- a/t/web/crypt-gnupg.t
+++ b/t/web/crypt-gnupg.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => undef,
   gnupg_options => {
     passphrase    => 'recipient',
@@ -423,7 +424,7 @@ ok($user = RT::User->new(RT->SystemUser));
 ok($user->Load('root'), "Loaded user 'root'");
 is($user->PreferredKey, $key1, "preferred key is set correctly");
 $m->get("$baseurl/Prefs/Other.html");
-like($m->content, qr/Preferred key/, "preferred key option shows up in preference");
+like($m->content, qr/Preferred GnuPG key/, "preferred key option shows up in preference");
 
 # XXX: mech doesn't let us see the current value of the select, apparently
 like($m->content, qr/$key1/, "first key shows up in preferences");
@@ -439,7 +440,7 @@ ok($user->Load('root'), "Loaded user 'root'");
 is($user->PreferredKey, $key2, "preferred key is set correctly to the new value");
 
 $m->get("$baseurl/Prefs/Other.html");
-like($m->content, qr/Preferred key/, "preferred key option shows up in preference");
+like($m->content, qr/Preferred GnuPG key/, "preferred key option shows up in preference");
 
 # XXX: mech doesn't let us see the current value of the select, apparently
 like($m->content, qr/$key2/, "second key shows up in preferences");
diff --git a/t/web/gnupg-headers.t b/t/web/gnupg-headers.t
index 03e60901ea..d7155bd3f2 100644
--- a/t/web/gnupg-headers.t
+++ b/t/web/gnupg-headers.t
@@ -1,7 +1,8 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG
+use RT::Test::Crypt
+  GnuPG         => 1,
   tests         => 15,
   gnupg_options => {
     passphrase    => 'recipient',
diff --git a/t/web/gnupg-select-keys-on-create.t b/t/web/gnupg-select-keys-on-create.t
index ee8357d59f..6438c02be0 100644
--- a/t/web/gnupg-select-keys-on-create.t
+++ b/t/web/gnupg-select-keys-on-create.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' };
+use RT::Test::Crypt GnuPG => 1, tests => undef, gnupg_options => { passphrase => 'rt-test' };
 use RT::Action::SendEmail;
 
 my $queue = RT::Test->load_or_create_queue(
@@ -37,7 +37,7 @@ diag "check that signing doesn't work if there is no key";
 {
     RT::Test->import_gnupg_key('rt-recipient at example.com');
     RT::Test->trust_gnupg_key('rt-recipient at example.com');
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com', Protocol => 'GnuPG' );
     is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key';
 }
 
@@ -74,7 +74,7 @@ diag "import first key of rt-test\@example.com";
 my $fpr1 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com', 'secret');
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com', Protocol => 'GnuPG' );
     is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr1 = $res{'info'}[0]{'Fingerprint'};
 }
@@ -123,7 +123,7 @@ diag "import a second key of rt-test\@example.com";
 my $fpr2 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com.2', 'secret');
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com', Protocol => 'GnuPG' );
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr2 = $res{'info'}[2]{'Fingerprint'};
 }
@@ -170,7 +170,7 @@ diag "check that things still doesn't work if two keys are not trusted";
 
 {
     RT::Test->lsign_gnupg_key( $fpr1 );
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com', Protocol => 'GnuPG' );
     ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
 }
diff --git a/t/web/gnupg-select-keys-on-update.t b/t/web/gnupg-select-keys-on-update.t
index 2f31a0f0d2..5d8419b93a 100644
--- a/t/web/gnupg-select-keys-on-update.t
+++ b/t/web/gnupg-select-keys-on-update.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => undef, gnupg_options => { passphrase => 'rt-test' };
+use RT::Test::Crypt GnuPG => 1, tests => undef, gnupg_options => { passphrase => 'rt-test' };
 
 use RT::Action::SendEmail;
 
@@ -52,7 +52,7 @@ diag "check that signing doesn't work if there is no key";
 {
     RT::Test->import_gnupg_key('rt-recipient at example.com');
     RT::Test->trust_gnupg_key('rt-recipient at example.com');
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com', Protocol => 'GnuPG' );
     is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key';
 }
 
@@ -91,7 +91,7 @@ diag "import first key of rt-test\@example.com";
 my $fpr1 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com', 'secret');
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com', Protocol => 'GnuPG' );
     is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr1 = $res{'info'}[0]{'Fingerprint'};
 }
@@ -141,7 +141,7 @@ diag "import a second key of rt-test\@example.com";
 my $fpr2 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com.2', 'secret');
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com', Protocol => 'GnuPG' );
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr2 = $res{'info'}[2]{'Fingerprint'};
 }
@@ -189,7 +189,7 @@ diag "check that things still doesn't work if two keys are not trusted";
 
 {
     RT::Test->lsign_gnupg_key( $fpr1 );
-    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com' );
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com', Protocol => 'GnuPG' );
     ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
 }
diff --git a/t/web/gnupg-tickyboxes.t b/t/web/gnupg-tickyboxes.t
index eb4388a2c4..4629f8e459 100644
--- a/t/web/gnupg-tickyboxes.t
+++ b/t/web/gnupg-tickyboxes.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Test::GnuPG tests => 22, gnupg_options => { passphrase => 'rt-test' };
+use RT::Test::Crypt GnuPG => 1, tests => 22, gnupg_options => { passphrase => 'rt-test' };
 
 use RT::Action::SendEmail;
 
diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index d85c2ca111..fd85917516 100644
--- a/t/web/smime/outgoing.t
+++ b/t/web/smime/outgoing.t
@@ -1,15 +1,15 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => undef;
-my $test = 'RT::Test::SMIME';
+use RT::Test::Crypt SMIME=>1, tests => undef;
+my $test = 'RT::Test::Crypt';
 
 use RT::Action::SendEmail;
 use File::Temp qw(tempdir);
 
 use_ok('RT::Crypt::SMIME');
 
-RT::Test::SMIME->import_key('sender at example.com');
+$test->smime_import_key('sender at example.com');
 
 my $user_email = 'root at example.com';
 {
@@ -17,7 +17,7 @@ my $user_email = 'root at example.com';
         Name => $user_email, EmailAddress => $user_email
     );
     ok $user && $user->id, 'loaded or created user';
-    RT::Test::SMIME->import_key($user_email, $user);
+    $test->smime_import_key($user_email, $user);
 }
 
 my $queue = RT::Test->load_or_create_queue(
@@ -53,7 +53,7 @@ my %mail = (
 diag "check in read-only mode that queue's props influence create/update ticket pages" if $ENV{TEST_VERBOSE};
 {
     foreach my $variant ( @variants ) {
-        set_queue_crypt_options( %$variant );
+        t_set_queue_crypt_options( %$variant );
         $m->goto_create_ticket( $queue );
         $m->form_name('TicketCreate');
         if ( $variant->{'Encrypt'} ) {
@@ -69,7 +69,7 @@ diag "check in read-only mode that queue's props influence create/update ticket
     }
 
     # to avoid encryption/signing during create
-    set_queue_crypt_options();
+    t_set_queue_crypt_options();
 
     my $ticket = RT::Ticket->new( $RT::SystemUser );
     my ($id) = $ticket->Create(
@@ -80,7 +80,7 @@ diag "check in read-only mode that queue's props influence create/update ticket
     ok $id, 'ticket created';
 
     foreach my $variant ( @variants ) {
-        set_queue_crypt_options( %$variant );
+        t_set_queue_crypt_options( %$variant );
         $m->goto_ticket( $id );
         $m->follow_link_ok({text => 'Reply'}, '-> reply');
         $m->form_number(3);
@@ -99,9 +99,9 @@ diag "check in read-only mode that queue's props influence create/update ticket
 
 # create a ticket for each combination
 foreach my $queue_set ( @variants ) {
-    set_queue_crypt_options( %$queue_set );
+    t_set_queue_crypt_options( %$queue_set );
     foreach my $ticket_set ( @variants ) {
-        create_a_ticket( %$ticket_set );
+        t_create_a_ticket( %$ticket_set );
     }
 }
 
@@ -118,9 +118,9 @@ my $tid;
 
 # again for each combination add a reply message
 foreach my $queue_set ( @variants ) {
-    set_queue_crypt_options( %$queue_set );
+    t_set_queue_crypt_options( %$queue_set );
     foreach my $ticket_set ( @variants ) {
-        update_ticket( $tid, %$ticket_set );
+        t_update_ticket( $tid, %$ticket_set );
     }
 }
 
@@ -131,10 +131,10 @@ foreach my $queue_set ( @variants ) {
 # like we are on another side recieving emails
 # ------------------------------------------------------------------------------
 
-my $keyring = $test->keyring_path;
+my $keyring = $test->smime_keyring_path;
 unlink $_ foreach glob( $keyring ."/*" );
-RT::Test::SMIME->import_key('sender at example.com.crt');
-RT::Test::SMIME->import_key($user_email);
+$test->smime_import_key('sender at example.com.crt');
+$test->smime_import_key($user_email);
 
 $queue = RT::Test->load_or_create_queue(
     Name              => 'Regression',
@@ -245,7 +245,7 @@ foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed_encrypted'} } ) {
         "RT's mail includes copy of ticket text";
 }
 
-sub create_a_ticket {
+sub t_create_a_ticket {
     my %args = (@_);
 
     RT::Test->clean_caught_mails;
@@ -274,10 +274,10 @@ sub create_a_ticket {
     $m->get_ok('/'); # ensure that the mail has been processed
 
     my @mail = RT::Test->fetch_caught_mails;
-    check_text_emails( \%args, @mail );
+    t_check_text_emails( \%args, @mail );
 }
 
-sub update_ticket {
+sub t_update_ticket {
     my $tid = shift;
     my %args = (@_);
 
@@ -305,12 +305,12 @@ sub update_ticket {
     $m->get_ok('/'); # ensure that the mail has been processed
 
     my @mail = RT::Test->fetch_caught_mails;
-    check_text_emails( \%args, @mail );
+    t_check_text_emails( \%args, @mail );
 }
 
 done_testing;
 
-sub check_text_emails {
+sub t_check_text_emails {
     my %args = %{ shift @_ };
     my @mail = @_;
 
@@ -343,18 +343,7 @@ sub check_text_emails {
     }
 }
 
-sub cleanup_headers {
-    my $mail = shift;
-    # 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 RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
-        $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
-    }
-    return $mail;
-}
-
-sub set_queue_crypt_options {
+sub t_set_queue_crypt_options {
     my %args = @_;
 
     describe_options('setting queue options: ', %args);

commit c3f994533048bf11e1d9c0db8a6d895d5672280b
Author: Dianne Skoll <dianne at bestpractical.com>
Date:   Thu Nov 19 17:07:16 2020 -0500

    Add deprecation warnings to RT::Test::GnuPG and RT::Test::SMIME.

diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
index 7720a5c10e..993d354cc5 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -1,3 +1,8 @@
+# NOTE: This file is deprecated.  Instead of:
+#    use RT::Test::GnuPG ARGS
+# please do:
+#    use RT::Test::Crypt GnuPG => 1, ARGS
+
 # BEGIN BPS TAGGED BLOCK {{{
 #
 # COPYRIGHT:
@@ -409,6 +414,8 @@ END {
         system( 'gpgconf', '--homedir', RT->Config->Get('GnuPGOptions')->{homedir}, '--quiet', '--kill', 'gpg-agent' )
             && warn $!;
     }
+
+    diag "RT::Test::GnuPG is deprecated; please use RT::Test::Crypt GnuPG => 1 instead";
 }
 
 1;
diff --git a/lib/RT/Test/SMIME.pm b/lib/RT/Test/SMIME.pm
index 25b3a43a2f..5d03a22461 100644
--- a/lib/RT/Test/SMIME.pm
+++ b/lib/RT/Test/SMIME.pm
@@ -1,3 +1,8 @@
+# NOTE: This file is deprecated.  Instead of:
+#    use RT::Test::SMIME ARGS
+# please do:
+#    use RT::Test::Crypt SMIME => 1, ARGS
+
 # BEGIN BPS TAGGED BLOCK {{{
 #
 # COPYRIGHT:
@@ -160,4 +165,8 @@ sub import_key {
     return;
 }
 
+END {
+    diag('RT::Test::SMIME is deprecated; please use RT::Test::Crypt SMIME => 1 instead');
+}
+
 1;

commit ee1985edc64409d8d7ecca5cbecb0ece483b165a
Author: Dianne Skoll <dianne at bestpractical.com>
Date:   Fri Nov 6 12:06:26 2020 -0500

    Allow specification of outbound signing/encryption protocol on a per-queue basis

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index 45bd479073..3c2ef6cc4a 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -931,9 +931,9 @@ sub Encrypt {
         RT->Config->Get('CorrespondAddress'),
         RT->Config->Get('CommentAddress'),
     ) {
-        my %res = RT::Crypt->GetKeysInfo( Key => $address, Type => 'private' );
+        my %res = RT::Crypt->GetKeysInfo( Queue => $queue, Key => $address, Type => 'private' );
         next if $res{'exit_code'} || !$res{'info'};
-        %res = RT::Crypt->GetKeysForEncryption( $address );
+        %res = RT::Crypt->GetKeysForEncryption( Queue => $queue, Recipient => $address );
         next if $res{'exit_code'} || !$res{'info'};
         $encrypt_for = $address;
     }
@@ -943,6 +943,7 @@ sub Encrypt {
 
     my $content = $self->Content;
     my %res = RT::Crypt->SignEncryptContent(
+        Queue => $queue,
         Content => \$content,
         Sign => 0,
         Encrypt => 1,
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index a22ec1a366..dbd4c55b94 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -796,12 +796,35 @@ our %META;
                 $opt->{'Incoming'} = \@enabled;
             }
             if ( $opt->{'Outgoing'} ) {
-                if (not $enabled{$opt->{'Outgoing'}}) {
-                    $RT::Logger->warning($opt->{'Outgoing'}.
+                if (ref($opt->{'Outgoing'}) eq 'HASH') {
+                    # Check each entry in the hash
+                    foreach my $q (keys(%{$opt->{'Outgoing'}})) {
+                        if (not $enabled{$opt->{'Outgoing'}->{$q}}) {
+                            if ($q ne '') {
+                                $RT::Logger->warning($opt->{'Outgoing'}->{$q}.
+                                                     " explicitly set as outgoing Crypt plugin for queue $q, but not marked Enabled; "
+                                                     . (@enabled ? "using $enabled[0]" : "removing"));
+                            } else {
+                                $RT::Logger->warning($opt->{'Outgoing'}->{$q}.
+                                                     " explicitly set as default outgoing Crypt plugin, but not marked Enabled; "
+                                                     . (@enabled ? "using $enabled[0]" : "removing"));
+                            }
+                            $opt->{'Outgoing'}->{$q} = $enabled[0];
+                        }
+                    }
+                    # If there's no entry for the default queue, set one
+                    if (!$opt->{'Outgoing'}->{''} && scalar(@enabled)) {
+                        $RT::Logger->warning("No default outgoing Crypt plugin set; using $enabled[0]");
+                        $opt->{'Outgoing'}->{''} = $enabled[0];
+                    }
+                } else {
+                    if (not $enabled{$opt->{'Outgoing'}}) {
+                        $RT::Logger->warning($opt->{'Outgoing'}.
                                              " explicitly set as outgoing Crypt plugin, but not marked Enabled; "
                                              . (@enabled ? "using $enabled[0]" : "removing"));
+                    }
+                    $opt->{'Outgoing'} = $enabled[0] unless $enabled{$opt->{'Outgoing'}};
                 }
-                $opt->{'Outgoing'} = $enabled[0] unless $enabled{$opt->{'Outgoing'}};
             } else {
                 $opt->{'Outgoing'} = $enabled[0];
             }
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 6283ae5538..6b8ce22163 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -98,9 +98,9 @@ to limit this to a subset, you may, via:
         ...
     );
 
-RT can currently only use one protocol to encrypt and sign outgoing
-email; this defaults to the first enabled protocol.  You many specify it
-explicitly via:
+RT can currently only use one protocol per queue to encrypt and sign
+outgoing email; this defaults to the first enabled protocol.  You may
+specify it explicitly via:
 
     Set( %Crypt,
         ...
@@ -108,6 +108,17 @@ explicitly via:
         ...
     );
 
+which sets the protocol for all queues.  Alternatively, you can use:
+
+    Set( %Crypt,
+        ...
+        Outgoing => { Queue1 => 'GnuPG', Queue2 => 'SMIME', '' => 'GnuPG' },
+        ...
+    );
+
+which sets the outgoing protocol for Queue1 to GnuPG, for Queue2 to
+SMIME, and for all other queues to GnuPG (the '' key.)
+
 You can allow users to encrypt data in the database by setting the
 C<AllowEncryptDataInDB> key to a true value; by default, this is
 disabled.  Be aware that users must have rights to see and modify
@@ -226,15 +237,38 @@ sub EnabledProtocols {
     return grep RT->Config->Get($_)->{'Enable'}, $self->Protocols;
 }
 
-=head2 UseForOutgoing
+=head2 UseForOutgoing (Queue => QUEUE)
 
-Returns the configured outgoing encryption protocol; see
-L<RT_Config/Crypt>.
+Returns the configured outgoing encryption protocol for the given
+queue; see L<RT_Config/Crypt>.  The Queue argument can be either a
+queue name or an RT::Queue object.
 
 =cut
 
 sub UseForOutgoing {
-    return RT->Config->Get('Crypt')->{'Outgoing'};
+    my $self = shift;
+    my %args = (
+        Queue => undef,
+        @_
+    );
+    my $outgoing = RT->Config->Get('Crypt')->{'Outgoing'};
+
+    # Old-style config: Crypt->{'Outgoing'} is a scalar to use for all queues
+    if ( ( ref($outgoing) || '' ) ne 'HASH' ) {
+        return $outgoing;
+    }
+
+    # New-style config: Crypt->{'Outgoing'} is a hash keyed by queue name
+    my $qname = $args{Queue} || '';
+
+    # We accept either a queue name or an RT::Queue object;
+    # either way, boil it down to the name.
+    $qname = $qname->Name if ref($qname);
+
+    # Look for per-queue method; default to '' method in outgoing hash;
+    # the '' entry is guaranteed to exist because it's added if necessary
+    # by the PostLoadCheck routine in RT::Config.
+    return $outgoing->{$qname} || $outgoing->{''};
 }
 
 =head2 EnabledOnIncoming
@@ -270,18 +304,22 @@ sub LoadImplementation {
     }
 }
 
-=head2 SimpleImplementationCall Protocol => NAME, [...]
+=head2 SimpleImplementationCall Protocol => NAME, Queue => QUEUE, [...]
 
 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>.
 
+If C<Protocol> is not supplied, then C<Queue> will be used to figure out
+which outgoing protocol to use.  C<Queue> can be a queue name or an
+C<RT::Queue> object.
+
 =cut
 
 sub SimpleImplementationCall {
     my $self = shift;
     my %args = (@_);
-    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing(@_);
 
     my $method = (caller(1))[3];
     $method =~ s/.*:://;
@@ -387,7 +425,7 @@ sub FindProtectedParts {
 
 =head2 SignEncrypt Entity => ENTITY, [Sign => 1], [Encrypt => 1],
 [Recipients => ARRAYREF], [Signer => NAME], [Protocol => NAME],
-[Passphrase => VALUE]
+[Passphrase => VALUE], [Queue => QUEUE]
 
 Takes a L<MIME::Entity> object, and signs and/or encrypts it using the
 given C<Protocol>.  If not set, C<Recipients> for encryption will be set
@@ -418,6 +456,7 @@ sub SignEncrypt {
     my %args = (
         Sign => 1,
         Encrypt => 1,
+        Queue => undef,
         @_,
     );
 
@@ -468,7 +507,7 @@ sub SignEncryptContent {
     return $self->SimpleImplementationCall( %args );
 }
 
-=head2 DrySign Signer => KEY
+=head2 DrySign Queue => QUEUE, 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
@@ -640,31 +679,42 @@ sub UseKeyForEncryption {
     return ();
 }
 
-=head2 GetKeysForEncryption Recipient => EMAIL, Protocol => NAME
+=head2 CheckRecipients Queue => QUEUE, Recipients => [ @RECIPIENTS ]
 
-Returns the list of keys which are suitable for encrypting mail to the
-given C<Recipient>.  Generally this is equivalent to L</GetKeysInfo>
-with a C<Type> of <private>, but encryption protocols may further limit
-which keys can be used for encryption, as opposed to signing.
+Check a list of recipients to make sure we have trustable public
+keys for all of them.  Returns (1, ()) if everything is OK, or
+(0, @list_of_problem_messages) if there are errors.
 
 =cut
 
 sub CheckRecipients {
     my $self = shift;
-    my @recipients = (@_);
+    my %args = (
+        Queue      => undef,
+        Recipients => undef,
+    );
+
+    if ( grep { $_ && $_ =~ /^(Queue|Recipients)$/ } @_ ) {
+        %args = ( %args, @_ );
+    }
+    else {
+        $args{Recipients} = \@_;
+    }
+    my @recipients = @{$args{Recipients} || []};
 
     my ($status, @issues) = (1, ());
 
     my $trust = sub { 1 };
-    if ( $self->UseForOutgoing eq 'SMIME' ) {
+    my $proto = $self->UseForOutgoing( Queue => $args{Queue} );
+    if ( $proto eq 'SMIME' ) {
         $trust = sub { $_[0]->{'TrustLevel'} > 0 or RT->Config->Get('SMIME')->{AcceptUntrustedCAs} };
-    } elsif ( $self->UseForOutgoing eq 'GnuPG' ) {
+    } elsif ( $proto eq 'GnuPG' ) {
         $trust = sub { $_[0]->{'TrustLevel'} > 0 };
     }
 
     my %seen;
     foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
-        my %res = $self->GetKeysForEncryption( Recipient => $address );
+        my %res = $self->GetKeysForEncryption( Queue => $args{Queue}, Recipient => $address );
         if ( $res{'info'} && @{ $res{'info'} } == 1 and $trust->($res{'info'}[0]) ) {
             # One key, which is trusted, or we can sign with an
             # untrusted key (aka SMIME with AcceptUntrustedCAs)
@@ -723,13 +773,23 @@ sub CheckRecipients {
     return ($status, @issues);
 }
 
+=head2 GetKeysForEncryption Recipient => EMAIL, Protocol => NAME, Queue => QUEUE
+
+Returns the list of keys which are suitable for encrypting mail to the
+given C<Recipient>.  Generally this is equivalent to L</GetKeysInfo>
+with a C<Type> of <private>, but encryption protocols may further limit
+which keys can be used for encryption, as opposed to signing.
+
+=cut
+
+
 sub GetKeysForEncryption {
     my $self = shift;
-    my %args = @_%2? (Recipient => @_) : (Protocol => undef, Recipient => undef, @_ );
+    my %args = @_%2? (Recipient => @_) : (Protocol => undef, Queue => undef, Recipient => undef, @_ );
     return $self->SimpleImplementationCall( %args );
 }
 
-=head2 GetKeysForSigning Signer => EMAIL, Protocol => NAME
+=head2 GetKeysForSigning Signer => EMAIL, Protocol => NAME, Queue => QUEUE
 
 Returns the list of keys which are suitable for signing mail from the
 given C<Signer>.  Generally this is equivalent to L</GetKeysInfo>
@@ -740,7 +800,7 @@ which keys can be used for signing, as opposed to encryption.
 
 sub GetKeysForSigning {
     my $self = shift;
-    my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_);
+    my %args = @_%2? (Signer => @_) : (Protocol => undef, Queue => undef, Signer => undef, @_);
     return $self->SimpleImplementationCall( %args );
 }
 
@@ -778,7 +838,7 @@ sub GetKeyInfo {
     return %res;
 }
 
-=head2 GetKeysInfo Protocol => NAME, Type => ('public'|'private'), Key => EMAIL
+=head2 GetKeysInfo Protocol => NAME, Queue => QUEUE, Type => ('public'|'private'), Key => EMAIL
 
 Looks up information about the public or private keys (as determined by
 C<Type>) for the email address C<Key>.  As each protocol has its own key
@@ -796,6 +856,12 @@ protocol-dependent, but will at least contain:
 
 The name of the protocol of this key
 
+=item Queue
+
+An L<RT::Queue> or queue name. This is to infer the protocol to use from
+C<Outgoing> in L</%Crypt> configuration, only needed if Protocol is not
+provided.
+
 =item Created
 
 An L<RT::Date> of the date the key was created; undef if unset.
@@ -825,7 +891,7 @@ C<Created> and C<Expire> keys, which are L<RT::Date> objects.
 
 sub GetKeysInfo {
     my $self = shift;
-    my %args = @_%2 ? (Key => @_) : ( Protocol => undef, Key => undef, @_ );
+    my %args = @_%2 ? (Key => @_) : ( Protocol => undef, Key => undef, Queue => undef, @_ );
     return $self->SimpleImplementationCall( %args );
 }
 
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index e88248e5b2..78ff123d3d 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -887,6 +887,9 @@ sub SendEmail {
             Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef,
             Ticket     => $TicketObj,
         );
+        if ($TicketObj) {
+            $args{'Queue'} = $TicketObj->QueueObj;
+        }
         my $res = SignEncrypt( %args );
         return $res unless $res > 0;
     }
diff --git a/lib/RT/User.pm b/lib/RT/User.pm
index 5184eb3fa4..85db5c30b5 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -2060,7 +2060,7 @@ sub PreferredKey
     return $prefkey->Content if $prefkey;
 
     # we don't have a preferred key for this user, so now we must query GPG
-    my %res = RT::Crypt->GetKeysForEncryption($self->EmailAddress);
+    my %res = RT::Crypt->GetKeysForEncryption(Recipient => $self->EmailAddress, Protocol => 'GnuPG');
     return undef unless defined $res{'info'};
     my @keys = @{ $res{'info'} };
     return undef if @keys == 0;
diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
index 0317537409..26d355d69c 100644
--- a/share/html/Elements/Crypt/KeyIssues
+++ b/share/html/Elements/Crypt/KeyIssues
@@ -76,6 +76,8 @@ There is a problem with key/certificate(s) for address <% $issue->{'EmailAddress
 <br />
 Select a key/certificate you want to use for encryption:
 <& /Elements/Crypt/SelectKeyForEncryption,
+    Protocol     => $Protocol,
+    Queue        => $Queue,
     Name         => 'UseKey-'. $issue->{'EmailAddress'},
     EmailAddress => $issue->{'EmailAddress'},
     Default      => ( $issue->{'User'}? $issue->{'User'}->PreferredKey : undef ),
@@ -90,5 +92,7 @@ Select a key/certificate you want to use for encryption:
 <%ARGS>
 $Issues => []
 $SignAddresses => []
+$Protocol => undef
+$Queue => undef
 </%ARGS>
 
diff --git a/share/html/Elements/Crypt/SelectKeyForEncryption b/share/html/Elements/Crypt/SelectKeyForEncryption
index 5ccb2d812b..07891b666e 100644
--- a/share/html/Elements/Crypt/SelectKeyForEncryption
+++ b/share/html/Elements/Crypt/SelectKeyForEncryption
@@ -58,7 +58,9 @@
 <%INIT>
 my $d;
 
-my %res = RT::Crypt->GetKeysForEncryption($EmailAddress);
+my %res = RT::Crypt->GetKeysForEncryption(Queue => $Queue,
+                                          Protocol => $Protocol,
+                                          Recipient => $EmailAddress);
 # move the preferred key to the top of the list
 my @keys = map {
                $_->{'Fingerprint'} eq ( $Default || '' )
@@ -76,5 +78,7 @@ unshift @keys, $d if defined $d;
 $Name         => 'PreferredKey'
 $EmailAddress => undef
 $Default      => undef
+$Queue        => undef
+$Protocol     => undef
 </%ARGS>
 
diff --git a/share/html/Elements/Crypt/SignEncryptWidget b/share/html/Elements/Crypt/SignEncryptWidget
index 815234c13b..fe97d8f158 100644
--- a/share/html/Elements/Crypt/SignEncryptWidget
+++ b/share/html/Elements/Crypt/SignEncryptWidget
@@ -87,11 +87,15 @@ return { %$Arguments };
 <%METHOD ShowIssues>
 <%ARGS>
 $self => undef,
+$Protocol => undef
+$Queue => undef
 </%ARGS>
 <%INIT>
 return unless $self;
 
 return $m->comp( '/Elements/Crypt/KeyIssues',
+    Protocol => $Protocol,
+    Queue => $Queue,
     Issues => $self->{'GnuPGRecipientsKeyIssues'} || [],
     SignAddresses => $self->{'GnuPGCanNotSignAs'} || [],
 );
@@ -145,7 +149,7 @@ if ( $self->{'Sign'} ) {
     if ($address ne $private and $address ne $queue) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
-    } elsif ( not RT::Crypt->DrySign( Signer => $address ) ) {
+    } elsif ( not RT::Crypt->DrySign( Queue => $QueueObj, Signer => $address ) ) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
     } else {
@@ -183,7 +187,8 @@ if ( $self->{'Encrypt'} ) {
         keys %$self
     );
 
-    my ($status, @issues) = RT::Crypt->CheckRecipients( @recipients );
+    $QueueObj ||= $TicketObj->QueueObj if $TicketObj;
+    my ($status, @issues) = RT::Crypt->CheckRecipients( Queue => $QueueObj, Recipients => \@recipients );
     push @{ $self->{'GnuPGRecipientsKeyIssues'} ||= [] }, @issues;
     $checks_failure = 1 unless $status;
 }
diff --git a/share/html/Elements/RT__Ticket/ColumnMap b/share/html/Elements/RT__Ticket/ColumnMap
index ae425272e1..c0f95fc09f 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -82,8 +82,9 @@ my $LinkCallback = sub {
 };
 
 my $trustSub = sub {
+    my $queue = shift;
     my $user = shift;
-    my %key = RT::Crypt->GetKeyInfo( Key => $user->EmailAddress );
+    my %key = RT::Crypt->GetKeyInfo( Key => $user->EmailAddress, Queue => $queue );
     if (!defined $key{'info'}) {
         return $m->interp->apply_escapes(' ' . loc("(no pubkey!)"), "h");
     } elsif ($key{'info'}{'TrustLevel'} == 0) {
@@ -275,7 +276,7 @@ $COLUMN_MAP = {
     KeyRequestors => {
         title     => 'Requestors', # loc
         attribute => 'Requestor.EmailAddress',
-        value     => sub { return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->Requestor, PostUser => $trustSub ) ) }
+        value     => sub { my $ticket = $_[0]; return \($m->scomp("/Elements/ShowPrincipal", Object => $ticket->Requestor, PostUser => sub { my $user = shift; return $trustSub->($ticket->QueueObj, $user); }))}
     },
     KeyOwnerName => {
         title     => 'Owner', # loc
@@ -283,7 +284,7 @@ $COLUMN_MAP = {
         value     => sub {
             my $t = shift;
             my $name = $t->OwnerObj->Name;
-            my %key = RT::Crypt->GetKeyInfo( Key => $t->OwnerObj->EmailAddress );
+            my %key = RT::Crypt->GetKeyInfo( Key => $t->OwnerObj->EmailAddress, Queue => $t->QueueObj );
             if (!defined $key{'info'}) {
                 $name .= ' '. loc("(no pubkey!)");
             }
@@ -297,7 +298,8 @@ $COLUMN_MAP = {
     KeyOwner => {
         title     => 'Owner', # loc
         attribute => 'Owner',
-        value     => sub { return \($m->scomp("/Elements/ShowPrincipal", Object => $_[0]->OwnerObj, PostUser => $trustSub ) ) }
+        value     => sub {
+            my $ticket = $_[0]; return \($m->scomp("/Elements/ShowPrincipal", Object => $ticket->OwnerObj, PostUser => sub { my $user = shift; return $trustSub->($ticket->QueueObj, $user); }))},
     },
 
     # Everything from LINKTYPEMAP
diff --git a/share/html/Prefs/Other.html b/share/html/Prefs/Other.html
index cf9fe4abab..e2526dbd91 100644
--- a/share/html/Prefs/Other.html
+++ b/share/html/Prefs/Other.html
@@ -69,7 +69,7 @@
 
 % if ( RT->Config->Get('Crypt')->{'Enable'} ) {
 <&|/Widgets/TitleBox, title => loc( 'Cryptography' ) &>
-<&|/l&>Preferred key</&>: <& /Elements/Crypt/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
+<&|/l&>Preferred GnuPG key</&>: <& /Elements/Crypt/SelectKeyForEncryption, Name => 'PreferredKey', EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey, Protocol => 'GnuPG' &>
 </&>
 % }
 
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index bbe1ccf0d7..da35ee9b3d 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -60,7 +60,7 @@
 % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 
 % if ($gnupg_widget) {
-  <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+  <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget, Queue => $QueueObj &>
 % }
 
 <div id="Ticket-Create-basics">
diff --git a/share/html/Ticket/Forward.html b/share/html/Ticket/Forward.html
index 7e81b0c376..b4493b2cfe 100644
--- a/share/html/Ticket/Forward.html
+++ b/share/html/Ticket/Forward.html
@@ -56,7 +56,7 @@
 <input type="hidden" class="hidden" name="id" value="<% $id %>" /><br />
 <input type="hidden" class="hidden" name="QuoteTransaction" value="<% $ARGS{'QuoteTransaction'} || '' %>" />
 
-<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget, Queue => $TicketObj->QueueObj &>
 
 <table border="0">
 <tr><td align="right"><&|/l&>From</&>:</td>
diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index 0439b74ace..70f5e7d605 100644
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -59,7 +59,7 @@
 <input type="hidden" class="hidden" name="Action" value="<% $Action %>" />
 <input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />
 
-<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget, Queue => $TicketObj->QueueObj &>
 
 <div id="ticket-update-metadata">
   <&|/Widgets/TitleBox, title => loc('Ticket and Transaction') &>
diff --git a/share/html/m/ticket/create b/share/html/m/ticket/create
index 347810826c..912dde9268 100644
--- a/share/html/m/ticket/create
+++ b/share/html/m/ticket/create
@@ -217,7 +217,7 @@ if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} e
 <input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />
 % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 % if ($gnupg_widget) {
-<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget, Queue => $QueueObj &>
 % }
 
 
diff --git a/share/html/m/ticket/reply b/share/html/m/ticket/reply
index 4382b9e645..4d7cbb541c 100644
--- a/share/html/m/ticket/reply
+++ b/share/html/m/ticket/reply
@@ -57,7 +57,7 @@
 <input type="hidden" class="hidden" name="Token" value="<% $ARGS{'Token'} %>" />
 
 % if ($gnupg_widget) {
-<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget, Queue => $t->QueueObj &>
 % }
 
 <div class="entry"><span class="label"><&|/l&>Status</&>:</span>

commit f0d5babe23d74e12e771a130eae782d2e1069738
Author: Dianne Skoll <dianne at bestpractical.com>
Date:   Thu Nov 19 12:21:02 2020 -0500

    In Admin/Users/Keys.html, do not call "UseForOutgoing" when we have no $Queue object.
    
    Instead, just check if the corresponding encryption protocol is enabled.

diff --git a/share/html/Admin/Users/Keys.html b/share/html/Admin/Users/Keys.html
index cee08c869e..36b9a7e353 100644
--- a/share/html/Admin/Users/Keys.html
+++ b/share/html/Admin/Users/Keys.html
@@ -59,7 +59,7 @@
 <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') {
+% if (RT::Config->Get('GnuPG')->{Enable}) {
 <&|/Widgets/TitleBox, title => loc('GnuPG private key') &>
 <& /Widgets/Form/Select,
     Name         => 'PrivateKey',
@@ -71,7 +71,7 @@
 </&>
 % }
 
-% if (RT::Crypt->UseForOutgoing eq 'SMIME') {
+% if (RT::Config->Get('SMIME')->{Enable}) {
 <&|/Widgets/TitleBox, title => loc('SMIME Certificate') &>
 <textarea name="SMIMECertificate"><% $UserObj->SMIMECertificate || '' %></textarea>
 </&>
@@ -99,7 +99,7 @@ $id = $ARGS{'id'} = $UserObj->id;
 my @potential_keys;
 my $email = $UserObj->EmailAddress;
 
-if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
+if (RT::Config->Get('GnuPG')->{Enable}) {
     my %keys_meta = RT::Crypt->GetKeysForSigning( Signer => $email, Protocol => 'GnuPG' );
     @potential_keys = map $_->{'Key'}, @{ $keys_meta{'info'} || [] };
 
@@ -121,7 +121,7 @@ if (RT::Crypt->UseForOutgoing eq 'GnuPG') {
     }
 }
 
-if (RT::Crypt->UseForOutgoing eq 'SMIME') {
+if (RT::Config->Get('SMIME')->{Enable}) {
     if ( $Update and ($ARGS{'SMIMECertificate'}||'') ne ($UserObj->SMIMECertificate||'') ) {
         my ($status, $msg) = $UserObj->SetSMIMECertificate( $ARGS{'SMIMECertificate'} );
         push @results, $msg;

commit c38437afba84a38bc48049f77fd07f6ae2da7293
Author: Dianne Skoll <dianne at bestpractical.com>
Date:   Thu Nov 19 12:08:35 2020 -0500

    Add unit test to make sure that per-queue-crypt-protocol setting actually works.

diff --git a/t/mail/crypt-per-queue-outgoing-protocol.t b/t/mail/crypt-per-queue-outgoing-protocol.t
new file mode 100644
index 0000000000..1568e25413
--- /dev/null
+++ b/t/mail/crypt-per-queue-outgoing-protocol.t
@@ -0,0 +1,102 @@
+use strict;
+use warnings;
+
+use RT::Test::Crypt
+    GnuPG      => 1,
+    SMIME      => 1,
+    tests      => undef,
+    gnupg_options => {
+        passphrase    => 'rt-test',
+        'trust-model' => 'always'
+    },
+    config => 'Set( %Crypt, Incoming => ["GnuPG", "SMIME"], Outgoing => {"" => "GnuPG", Special => "SMIME" } );';
+
+my $test = 'RT::Test::Crypt';
+
+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              => 'Special',
+    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 encryption 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');
+
+    $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,
+    );
+    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->smime_key_path('root at example.com.key'),
+            '-recip', $test->smime_key_path('root at example.com.crt')
+        ], \$mails[0], \$buf, \$err )
+        }, 'can decrypt'
+    );
+    diag $@ if $@;
+    diag $err if $err;
+    diag "Error code: $?" if $?;
+    like($buf, qr'This message has been automatically generated in response');
+}
+
+# non-"Special" queue should use GnuPG, not S/MIME.
+RT::Test->import_gnupg_key('rt-recipient at example.com');
+RT::Test->import_gnupg_key( 'rt-test at example.com' );
+
+$queue = RT::Test->load_or_create_queue(
+    Name              => 'Regression',
+    CorrespondAddress => 'rt-recipient at example.com',
+    CommentAddress    => 'rt-recipient at example.com',
+    Encrypt           => 1,
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+create_and_test_outgoing_emails( $queue, $m );
+
+done_testing;

commit 2e3c228f330ae0048a6674bdb7302d8afa87d3c9
Merge: fed9384907 c38437afba
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Nov 25 02:38:42 2020 +0800

    Merge branch '4.4/per-queue-outgoing-encryption-protocol' into 4.4-trunk


-----------------------------------------------------------------------


More information about the rt-commit mailing list