[Rt-commit] rt branch, 4.4/smime-separate-encrypt-and-sign-certs, created. rt-4.4.4-58-ge9202dbcb

? sunnavy sunnavy at bestpractical.com
Thu Aug 15 15:53:52 EDT 2019


The branch, 4.4/smime-separate-encrypt-and-sign-certs has been created
        at  e9202dbcbff9f0a31098a4ea65d831e2aba3c1ea (commit)

- Log -----------------------------------------------------------------
commit 0e0e89fd95b77459d637875eaf96da590dc4801a
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Tue Sep 25 03:32:28 2018 +0800

    Support separate certificates for SMIME encryption and signing
    
    PEM files need to be named like "email.address at example.com.encryption.pem"
    and "email.address at example.com.signing.pem", respectively.
    
    If passphrases are also different, they can be specified in config like:
    
        'email.address at example.com' => {
            Encryption => 'passphrase for encryption certificate',
            Signing    => 'passphrase for signing certificate',
        }

diff --git a/lib/RT/Crypt/Role.pm b/lib/RT/Crypt/Role.pm
index 9836a3bf3..593e3bc7d 100644
--- a/lib/RT/Crypt/Role.pm
+++ b/lib/RT/Crypt/Role.pm
@@ -69,7 +69,7 @@ encounters.
 
 requires 'Probe';
 
-=head2 GetPassphrase Address => ADDRESS
+=head2 GetPassphrase Address => ADDRESS, For => Encryption|Signing
 
 Returns the passphrase for the given address.  It looks at the relevant
 configuration option for the encryption protocol
@@ -82,7 +82,7 @@ it is a hash, it looks up the address (using '' as a fallback key).
 
 sub GetPassphrase {
     my $self = shift;
-    my %args = ( Address => undef, @_ );
+    my %args = ( Address => undef, For => undef, @_ );
 
     my $class = ref($self) || $self;
     $class =~ s/^RT::Crypt:://;
@@ -94,6 +94,9 @@ sub GetPassphrase {
     if (not ref $config) {
         return $config;
     } elsif (ref $config eq "HASH") {
+        if ( ref $config->{$args{Address}} eq 'HASH' ) {
+            return $config->{$args{Address}}{$args{For} // ''} || $config->{$args{Address}}{''};
+        }
         return $config->{$args{Address}}
             || $config->{''};
     } elsif (ref $config eq "CODE") {
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 4075b8f95..f891940b8 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -79,6 +79,10 @@ You should start from reading L<RT::Crypt>.
         CAPath  => '/opt/rt4/var/data/smime/signing-ca.pem',
         Passphrase => {
             'queue.address at example.com' => 'passphrase',
+            'another.queue.address at example.com' => {
+                Encryption => 'passphrase for encryption certificate',
+                Signing    => 'passphrase for signing certificate',
+            },
             '' => 'fallback',
         },
     );
@@ -127,6 +131,11 @@ on users, private SSL keys are only loaded from disk.  Keys and
 certificates should be concatenated, in in PEM format, in files named
 C<email.address at example.com.pem>, for example.
 
+For addresses that have separate certificates for encryption/decryption
+and signing, the PEM files need to be named like
+C<email.address at example.com.encryption.pem> and
+C<email.address at example.com.signing.pem>, respectively.
+
 These files need be readable by the web server user which is running
 RT's web interface; however, if you are running cronjobs or other
 utilities that access RT directly via API, and may generate
@@ -296,7 +305,7 @@ sub _SignEncrypt {
         foreach my $address ( @addresses ) {
             $RT::Logger->debug( "Considering encrypting message to " . $address );
 
-            my %key_info = $self->GetKeysInfo( Key => $address );
+            my %key_info = $self->GetKeysInfo( Key => $address, For => 'Encryption' );
             unless ( defined $key_info{'info'} ) {
                 $res{'exit_code'} = 1;
                 my $reason = 'Key not found';
@@ -337,7 +346,7 @@ sub _SignEncrypt {
 
     my @commands;
     if ( $args{'Sign'} ) {
-        my $file = $self->CheckKeyring( Key => $args{'Signer'} );
+        my $file = $self->CheckKeyring( Key => $args{'Signer'}, For => 'Signing' );
         unless ($file) {
             $res{'status'} .= $self->FormatStatus({
                 Operation => "KeyCheck", Status => "MISSING",
@@ -348,7 +357,7 @@ sub _SignEncrypt {
             $res{exit_code} = 1;
             return (undef, %res);
         }
-        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} )
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'}, For => 'Signing' )
             unless defined $args{'Passphrase'};
 
         push @commands, [
@@ -582,14 +591,14 @@ sub _Decrypt {
     my ($buf, $encrypted_to, %res);
 
     foreach my $address ( @addresses ) {
-        my $file = $self->CheckKeyring( Key => $address );
+        my $file = $self->CheckKeyring( Key => $address, For => 'Encryption' );
         unless ( $file ) {
             my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
             $RT::Logger->debug("No key found for $address in $keyring directory");
             next;
         }
 
-        local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
+        local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address, For => 'Encryption' );
         local $SIG{CHLD} = 'DEFAULT';
         my $cmd = [
             $self->OpenSSLPath,
@@ -803,7 +812,7 @@ sub GetKeysForEncryption {
     my $self = shift;
     my %args = (Recipient => undef, @_);
     my $recipient = delete $args{'Recipient'};
-    my %res = $self->GetKeysInfo( Key => $recipient, %args, Type => 'public' );
+    my %res = $self->GetKeysInfo( Key => $recipient, %args, Type => 'public', For => 'Encryption' );
     return %res unless $res{'info'};
 
     foreach my $key ( splice @{ $res{'info'} } ) {
@@ -832,7 +841,7 @@ sub GetKeysForEncryption {
 sub GetKeysForSigning {
     my $self = shift;
     my %args = (Signer => undef, @_);
-    return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' );
+    return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private', For => 'Signing' );
 }
 
 sub GetKeysInfo {
@@ -841,6 +850,7 @@ sub GetKeysInfo {
         Key   => undef,
         Type  => 'public',
         Force => 0,
+        For   => undef,
         @_
     );
 
@@ -857,7 +867,7 @@ sub GetKeysInfo {
 
 sub GetKeyContent {
     my $self = shift;
-    my %args = ( Key => undef, @_ );
+    my %args = ( Key => undef, For => undef, @_ );
 
     my $key;
     if ( my $file = $self->CheckKeyring( %args ) ) {
@@ -878,11 +888,17 @@ sub CheckKeyring {
     my $self = shift;
     my %args = (
         Key => undef,
+        For => undef,
         @_,
     );
     my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
     return undef unless $keyring;
 
+    if ( $args{For} ) {
+        my $file = File::Spec->catfile( $keyring, $args{'Key'} . '.' . lc( $args{For} ) . '.pem' );
+        return $file if -f $file;
+    }
+
     my $file = File::Spec->catfile( $keyring, $args{'Key'} .'.pem' );
     return undef unless -f $file;
 

commit e9202dbcbff9f0a31098a4ea65d831e2aba3c1ea
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Oct 17 21:43:43 2018 +0800

    Test separate smime certificates for signing and encryption
    
    For signing, it looks for EMAIL.signing.pem and then EMAIL.pem.
    For encryption, it looks for EMAIL.encryption.pem and then EMAIL.pem.

diff --git a/t/mail/smime/separate_certs.t b/t/mail/smime/separate_certs.t
new file mode 100644
index 000000000..9e11df60b
--- /dev/null
+++ b/t/mail/smime/separate_certs.t
@@ -0,0 +1,137 @@
+use strict;
+use warnings;
+
+use RT::Test::SMIME tests => undef;
+
+use IPC::Run3 'run3';
+use Test::Warn;
+
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'General',
+    CorrespondAddress => 'sender at example.com',
+    CommentAddress    => 'sender at example.com',
+);
+
+my ( $ret, $msg ) = $queue->SetSignAuto(1);
+ok( $ret, 'Enabled SignAuto' );
+
+my %signing = (
+    'sender at example.com.pem'            => 1,
+    'sender at example.com.signing.pem'    => 1,
+    'sender at example.com.encryption.pem' => 0,
+);
+
+my $key_ring = RT->Config->Get('SMIME')->{'Keyring'};
+for my $key ( keys %signing ) {
+    diag "Testing signing with $key";
+
+    RT::Test::SMIME->import_key('sender at example.com');
+    if ( $key ne 'sender at example.com' ) {
+        rename File::Spec->catfile( $key_ring, 'sender at example.com.pem' ), File::Spec->catfile( $key_ring, $key )
+          or die $!;
+    }
+
+    my $mail = <<END;
+From: root\@localhost
+Subject: test signing
+
+Hello
+END
+
+    my ( $ret, $id ) = RT::Test->send_via_mailgate( $mail, queue => $queue->Name, );
+    is $ret >> 8, 0, "Successfuly executed mailgate";
+
+    my @mails = RT::Test->fetch_caught_mails;
+    if ( $signing{$key} ) {
+        is scalar @mails, 1, "autoreply";
+        like( $mails[0], qr'Content-Type: application/x-pkcs7-signature', 'Sent message contains signature' );
+
+        my ( $buf, $err );
+        run3( [ qw(openssl smime -verify), '-CAfile', RT::Test::SMIME->key_path . "/demoCA/cacert.pem", ],
+            \$mails[0], \$buf, \$err );
+
+        like( $err, qr'Verification successful', 'Verification output' );
+        like( $buf, qr'This message has been automatically generated in response', 'Verified message' );
+        unlike( $buf, qr'Content-Type: application/x-pkcs7-signature', 'Verified message does not contain signature' );
+    }
+    else {
+        is scalar @mails, 0, "Couldn't send autoreply";
+    }
+
+    unlink File::Spec->catfile( $key_ring, $key );
+}
+
+( $ret, $msg ) = $queue->SetSignAuto(0);
+ok( $ret, 'Disabled SignAuto' );
+
+my %encryption = (
+    'sender at example.com.pem'            => 1,
+    'sender at example.com.signing.pem'    => 0,
+    'sender at example.com.encryption.pem' => 1,
+);
+
+my $root = RT::Test->load_or_create_user( Name => 'root' );
+( $ret, $msg ) = $root->SetEmailAddress('root at example.com');
+ok( $ret, 'set root email to root at example.com' );
+RT::Test::SMIME->import_key( 'root at example.com', $root );
+
+for my $key ( keys %encryption ) {
+    diag "Testing decryption with $key";
+
+    RT::Test::SMIME->import_key('sender at example.com');
+    if ( $key ne 'sender at example.com' ) {
+        rename File::Spec->catfile( $key_ring, 'sender at example.com.pem' ), File::Spec->catfile( $key_ring, $key )
+          or die $!;
+    }
+
+    my ( $buf, $err );
+    run3(
+        [   qw(openssl smime -encrypt  -des3),
+            -from    => 'root at example.com',
+            -to      => 'sender at example.com',
+            -subject => "Encrypted message for queue",
+            RT::Test::SMIME->key_path('sender at example.com.crt'),
+        ],
+        \"\nthis is content",
+        \$buf,
+        \$err,
+    );
+
+    my ( $ret, $id );
+    if ( $encryption{$key} ) {
+        ( $ret, $id ) = RT::Test->send_via_mailgate($buf);
+    }
+    else {
+        warning_like {
+            ( $ret, $id ) = RT::Test->send_via_mailgate($buf);
+        }
+        [   qr!Couldn't find SMIME key for addresses: sender\@example.com!,
+            qr!Failure during SMIME keycheck: Secret key is not available!
+        ],
+          "Got missing key warning";
+    }
+
+    is( $ret >> 8, 0, "The mail gateway exited normally" );
+
+    my $ticket = RT::Ticket->new($RT::SystemUser);
+    $ticket->Load($id);
+    is( $ticket->Subject, 'Encrypted message for queue', "Created the ticket" );
+    my $txn = $ticket->Transactions->First;
+    my ( $msg, $attach, $orig ) = @{ $txn->Attachments->ItemsArrayRef };
+
+    is( $msg->GetHeader('X-RT-Privacy'), 'SMIME', 'X-RT-Privacy is SMIME' );
+    is( $orig->GetHeader('Content-Type'), 'application/x-rt-original-message', 'Original message is recorded' );
+
+    if ( $encryption{$key} ) {
+        is( $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success', 'X-RT-Incoming-Encryption is success' );
+        is( $attach->Content, 'this is content', 'Content is decrypted' );
+    }
+    else {
+        is( $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted', 'X-RT-Incoming-Encryption is not encrypted' );
+        unlike( $attach->Content, qr/this is content/, 'Content is not decrypted' );
+    }
+
+    unlink File::Spec->catfile( $key_ring, $key );
+}
+
+done_testing;

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


More information about the rt-commit mailing list