Mailing List Archive

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

- Log -----------------------------------------------------------------
commit 0e0e89fd95b77459d637875eaf96da590dc4801a
Author: sunnavy <sunnavy@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@example.com.encryption.pem"
and "email.address@example.com.signing.pem", respectively.

If passphrases are also different, they can be specified in config like:

'email.address@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@example.com' => 'passphrase',
+ 'another.queue.address@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@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@example.com.encryption.pem> and
+C<email.address@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@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@example.com',
+ CommentAddress => 'sender@example.com',
+);
+
+my ( $ret, $msg ) = $queue->SetSignAuto(1);
+ok( $ret, 'Enabled SignAuto' );
+
+my %signing = (
+ 'sender@example.com.pem' => 1,
+ 'sender@example.com.signing.pem' => 1,
+ 'sender@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@example.com');
+ if ( $key ne 'sender@example.com' ) {
+ rename File::Spec->catfile( $key_ring, 'sender@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@example.com.pem' => 1,
+ 'sender@example.com.signing.pem' => 0,
+ 'sender@example.com.encryption.pem' => 1,
+);
+
+my $root = RT::Test->load_or_create_user( Name => 'root' );
+( $ret, $msg ) = $root->SetEmailAddress('root@example.com');
+ok( $ret, 'set root email to root@example.com' );
+RT::Test::SMIME->import_key( 'root@example.com', $root );
+
+for my $key ( keys %encryption ) {
+ diag "Testing decryption with $key";
+
+ RT::Test::SMIME->import_key('sender@example.com');
+ if ( $key ne 'sender@example.com' ) {
+ rename File::Spec->catfile( $key_ring, 'sender@example.com.pem' ), File::Spec->catfile( $key_ring, $key )
+ or die $!;
+ }
+
+ my ( $buf, $err );
+ run3(
+ [. qw(openssl smime -encrypt -des3),
+ -from => 'root@example.com',
+ -to => 'sender@example.com',
+ -subject => "Encrypted message for queue",
+ RT::Test::SMIME->key_path('sender@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;

-----------------------------------------------------------------------
_______________________________________________
rt-commit mailing list
rt-commit@lists.bestpractical.com
http://lists.bestpractical.com/cgi-bin/mailman/listinfo/rt-commit