[Rt-commit] rt branch, smime, updated. rt-3.8.7-175-g3416f00

Ruslan Zakirov ruz at bestpractical.com
Tue Feb 9 19:18:51 EST 2010


The branch, smime has been updated
       via  3416f000dea20885ee2816f7ec320d9f91b613b9 (commit)
       via  61e51f51aa4d3d923a8d2a21af6f9eba5e643e76 (commit)
       via  d29f81a0191ecfa3d87898e31b4cbf03e34d3348 (commit)
       via  66b0cfc384fb107172a314746db239195716e5e8 (commit)
       via  3e293e66602e9695a56d7582a10eda3a6c4ddd6d (commit)
       via  2f2caa898166c76cfed3961dd0e403d57f6c685c (commit)
       via  9ce9b9472d5598e1d7d4d0d29483bd8c6498b6ee (commit)
      from  cf1b8997220d5efe1e07e8592b5903225febf0b3 (commit)

Summary of changes:
 lib/RT/Crypt/SMIME.pm                |  164 +++++++++++++++++++++++++++++-----
 lib/RT/Interface/Email/Auth/Crypt.pm |    7 +-
 t/mail/smime/realmail.t              |    4 +-
 3 files changed, 148 insertions(+), 27 deletions(-)

- Log -----------------------------------------------------------------
commit 9ce9b9472d5598e1d7d4d0d29483bd8c6498b6ee
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:06:09 2010 +0300

    fix number of tests and signer idententity

diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
index 65c6f23..1d710c4 100644
--- a/t/mail/smime/realmail.t
+++ b/t/mail/smime/realmail.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 197;
+use RT::Test tests => 89;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
@@ -116,7 +116,7 @@ sub email_ok {
 
     if ($usage =~ /signed/) {
         is( $msg->GetHeader('X-RT-Incoming-Signature'),
-            'RT Test <sender at example.com>',
+            '"sender" <sender at example.com>',
             "$eid: recorded incoming mail that is signed"
         );
     }

commit 2f2caa898166c76cfed3961dd0e403d57f6c685c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:08:18 2010 +0300

    one entity may have information about multiple crypt runs

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index b716f82..7d87147 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -144,12 +144,13 @@ sub GetCurrentUser {
         my $decrypted;
 
         foreach my $protocol ( @check_protocols ) {
-            my $status = $part->head->get( "X-RT-$protocol-Status" );
-            next unless $status;
+            my @status = grep defined && length,
+                $part->head->get( "X-RT-$protocol-Status" );
+            next unless @status;
 
             push @found, $protocol;
 
-            for ( RT::Crypt->ParseStatus( Protocol => $protocol, Status => $status ) ) {
+            for ( map RT::Crypt->ParseStatus( Protocol => $protocol, Status => "$_" ), @status ) {
                 if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
                     $decrypted = 1;
                 }

commit 3e293e66602e9695a56d7582a10eda3a6c4ddd6d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:11:21 2010 +0300

    ParsePKCS7Info - to figure out who signed a message

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index f9f49e8..0739117 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -522,6 +522,22 @@ sub GetCertificateInfo {
     return %res;
 }
 
+my %SHORT_NAMES = (
+    C => 'Country',
+    ST => 'StateOrProvince',
+    O  => 'Organization',
+    OU => 'OrganizationUnit',
+    CN => 'Name',
+);
+my %LONG_NAMES = (
+    countryName => 'Country',
+    stateOrProvinceName => 'StateOrProvince',
+    organizationName => 'Organization',
+    organizationalUnitName => 'OrganizationUnit',
+    commonName => 'Name',
+    emailAddress => 'EmailAddress',
+);
+
 sub CanonicalizeInfo {
     my $self = shift;
     my %info = @_;
@@ -530,19 +546,17 @@ sub CanonicalizeInfo {
         # XXX: trust is not implmented for SMIME
         TrustLevel => 1,
     );
-    {
-        my $subject = delete $info{'Certificate'}{'Data'}{'Subject'};
-        $res{'User'} = [{
-            Country => $subject->{'countryName'},
-            StateOrProvince  => $subject->{'stateOrProvinceName'},
-            Organization     => $subject->{'organizationName'},
-            OrganizationUnit => $subject->{'organizationalUnitName'},
-        }];
-        my $email = Email::Address->new( @{$subject}{'commonName', 'emailAddress'} );
-        $res{'User'}[-1]{'String'} = $email->format;
+    if ( my $subject = delete $info{'Certificate'}{'Data'}{'Subject'} ) {
+        $res{'User'} = [
+            { $self->CanonicalizeUserInfo( %$subject ) },
+        ];
     }
-    {
-        my $validity = delete $info{'Certificate'}{'Data'}{'Validity'};
+    if ( my $issuer = delete $info{'Certificate'}{'Data'}{'Issuer'} ) {
+        $res{'Issuer'} = [
+            { $self->CanonicalizeUserInfo( %$issuer ) },
+        ];
+    }
+    if ( my $validity = delete $info{'Certificate'}{'Data'}{'Validity'} ) {
         $res{'Created'} = $self->ParseDate( $validity->{'Not Before'} );
         $res{'Expire'} = $self->ParseDate( $validity->{'Not After'} );
     }
@@ -597,5 +611,52 @@ sub ParseCertificateInfo {
     return %res;
 }
 
+sub ParsePKCS7Info {
+    my $self = shift;
+    my $string = shift;
+
+    return () unless defined $string && length $string && $string =~ /\S/;
+
+    my @res = ({});
+    foreach my $str ( split /\r*\n/, $string ) {
+        if ( $str =~ /^\s*$/ ) {
+            push @res, {} if keys %{ $res[-1] };
+        } elsif ( my ($who, $values) = ($str =~ /^(subject|issuer)=(.*)$/i) ) {
+            my %info;
+            while ( $values =~ s{^/([a-z]+)=(.*?)(?=$|/[a-z]+=)}{}i ) {
+                $info{ $1 } = $2;
+            }
+            die "Couldn't parse PKCS7 info: $string" if $values;
+
+            $res[-1]{ ucfirst lc $who } = { $self->CanonicalizeUserInfo( %info ) };
+        }
+        else {
+            $res[-1]{'Content'} ||= '';
+            $res[-1]{'Content'} .= $str ."\n";
+        }
+    }
+
+    # oddly, but a certificate can be duplicated
+    my %seen;
+    @res = grep !$seen{ $_->{'Content'} }++, grep keys %$_, @res;
+    $_->{'User'} = delete $_->{'Subject'} foreach @res;
+    
+    return @res;
+}
+
+sub CanonicalizeUserInfo {
+    my $self = shift;
+    my %info = @_;
+
+    my %res;
+    while ( my ($k, $v) = each %info ) {
+        $res{ $SHORT_NAMES{$k} || $LONG_NAMES{$k} || $k } = $v;
+    }
+    if ( $res{'EmailAddress'} ) {
+        my $email = Email::Address->new( @res{'Name', 'EmailAddress'} );
+        $res{'String'} = $email->format;
+    }
+    return %res;
+}
 
 1;

commit 66b0cfc384fb107172a314746db239195716e5e8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:13:06 2010 +0300

    FormatStatus function instead of composing string all the time

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 0739117..789964b 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -262,6 +262,11 @@ sub DecryptRFC3851 {
     if ( $res{'exit_code'} ) {
         $res{'message'} = "openssl exitted with error code ". ($? >> 8)
             ." and error: $res{stderr}";
+        $res{'status'} = $self->FormatStatus({
+            Operation => 'Decrypt', Status => 'ERROR',
+            Message => 'Decryption failed',
+            EncryptedTo => $address,
+        });
         return %res;
     }
 
@@ -272,14 +277,30 @@ sub DecryptRFC3851 {
     $args{'Data'}->parts([ $res_entity->parts ]);
     $args{'Data'}->make_singlepart;
 
-    $res{'status'} =
-        "Operation: Decrypt\nStatus: DONE\n"
-        ."Message: Decryption process succeeded\n"
-        ."EncryptedTo: $address\n";
+    $res{'status'} = $self->FormatStatus({
+        Operation => 'Decrypt', Status => 'DONE',
+        Message => 'Decryption process succeeded',
+        EncryptedTo => $address,
+    });
 
     return %res;
 }
 
+sub FormatStatus {
+    my $self = shift;
+    my @status = @_;
+
+    my $res = '';
+    foreach ( @status ) {
+        $res .= "\n" if $res;
+        while ( my ($k, $v) = each %$_ ) {
+            $res .= $k .": ". $v ."\n";
+        }
+    }
+
+    return $res;
+}
+
 sub ParseStatus {
     my $self = shift;
     my $status = shift;

commit d29f81a0191ecfa3d87898e31b4cbf03e34d3348
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:15:55 2010 +0300

    check that key file exists beforehead

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 789964b..b385e3f 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -237,6 +237,8 @@ sub DecryptRFC3851 {
     my $self = shift;
     my %args = (Data => undef, Queue => undef, @_ );
 
+    my %res;
+
     my $msg = $args{'Data'}->as_string;
 
     my $action = 'correspond';
@@ -245,8 +247,25 @@ sub DecryptRFC3851 {
     my $address = $action eq 'correspond'
         ? $args{'Queue'}->CorrespondAddress || RT->Config->Get('CorrespondAddress')
         : $args{'Queue'}->CommentAddress    || RT->Config->Get('CommentAddress');
+    my $key_file = File::Spec->catfile( 
+        RT->Config->Get('SMIME')->{'Keyring'}, $address .'.pem'
+    );
+    unless ( -e $key_file && -r _ ) {
+        $res{'exit_code'} = 1;
+        $res{'status'} = $self->FormatStatus({
+            Operation => 'KeyCheck',
+            Status    => 'MISSING',
+            Message   => "Secret key for '$address' is not available",
+            Key       => $address,
+            KeyType   => 'secret',
+        });
+        $res{'User'} = {
+            String => $address,
+            SecretKeyMissing => 1,
+        };
+        return %res;
+    }
 
-    my %res;
     my $buf;
     {
         local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
@@ -254,7 +273,7 @@ sub DecryptRFC3851 {
         my $cmd = join( ' ', shell_quote(
             $self->OpenSSLPath,
             qw(smime -decrypt -passin env:SMIME_PASS),
-            -recip => RT->Config->Get('SMIME')->{'Keyring'} .'/'. $address .'.pem',
+            -recip => $key_file,
         ) );
         safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
         $res{'exit_code'} = $?;

commit 61e51f51aa4d3d923a8d2a21af6f9eba5e643e76
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:17:08 2010 +0300

    message extracting exact option and postprocessing

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index b385e3f..5355b3e 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -289,7 +289,7 @@ sub DecryptRFC3851 {
         return %res;
     }
 
-    my $res_entity = _extract_msg_from_buf( \$buf );
+    my $res_entity = _extract_msg_from_buf( \$buf, 1 );
     $res_entity->make_multipart( 'mixed', Force => 1 );
 
     $args{'Data'}->make_multipart( 'mixed', Force => 1 );
@@ -341,9 +341,11 @@ sub ParseStatus {
 
 sub _extract_msg_from_buf {
     my $buf = shift;
+    my $exact = shift;
     my $rtparser = RT::EmailParser->new();
     my $parser   = MIME::Parser->new();
     $rtparser->_SetupMIMEParser($parser);
+    $parser->decode_bodies(0) if $exact;
     $parser->output_to_core(1);
     unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) {
         $RT::Logger->crit("Couldn't parse MIME stream and extract the submessages");
@@ -355,7 +357,6 @@ sub _extract_msg_from_buf {
             return (undef);
         }
     }
-    $rtparser->_PostProcessNewEntity;
     return $rtparser->Entity;
 }
 

commit 3416f000dea20885ee2816f7ec320d9f91b613b9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 03:18:19 2010 +0300

    figure out signer of the message

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 5355b3e..ae20332 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -32,7 +32,6 @@ sub SignEncrypt {
 
     my $entity = $args{'Entity'};
 
-
     my %res = (exit_code => 0, status => '');
 
     my @keys;
@@ -210,15 +209,33 @@ sub VerifyRFC3851 {
     if ( $res{'exit_code'} ) {
         $res{'message'} = "openssl exitted with error code ". ($? >> 8)
             ." and error: $res{stderr}";
+        $RT::Logger->error($res{'message'});
         return %res;
     }
 
-    my $res_entity = _extract_msg_from_buf( \$buf );
+    my @signers;
+    {
+        my $pkcs7_info;
+        local $SIG{CHLD} = 'DEFAULT';
+        my $cmd = join( ' ', shell_quote(
+            $self->OpenSSLPath, qw(smime -pk7out),
+        ) );
+        $cmd .= ' | '. join( ' ', shell_quote(
+            $self->OpenSSLPath, qw(pkcs7 -print_certs),
+        ) );
+        safe_run_child { run3( $cmd, \$msg, \$pkcs7_info, \$res{'stderr'} ) };
+        unless ( $? ) {
+            @signers = $self->ParsePKCS7Info( $pkcs7_info );
+        }
+    }
+
+    my $res_entity = _extract_msg_from_buf( \$buf, 1 );
     unless ( $res_entity ) {
         $res{'exit_code'} = 1;
         $res{'message'} = "verified message, but couldn't parse result";
         return %res;
     }
+
     $res_entity->make_multipart( 'mixed', Force => 1 );
 
     $args{'Data'}->make_multipart( 'mixed', Force => 1 );
@@ -228,6 +245,7 @@ sub VerifyRFC3851 {
     $res{'status'} =
         "Operation: Verify\nStatus: DONE\n"
         ."Message: The signature is good\n"
+        ."UserString: ". $signers[0]{'User'}{'String'} ."\n"
     ;
 
     return %res;

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


More information about the Rt-commit mailing list