[Rt-commit] rt branch, 3.8.8-smime, created. rt-3.8.8-97-gd0f0fd4

Kevin Falcone falcone at bestpractical.com
Thu Jul 29 15:23:02 EDT 2010


The branch, 3.8.8-smime has been created
        at  d0f0fd40603d3762b4d819dccf1de42351c2bd5b (commit)

- Log -----------------------------------------------------------------
commit 8e8be5fc10d15055e45868e5798530f9390a75ac
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 15 08:21:48 2010 +0300

    make pseudo OO API out of functions, it was always the plan

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 449b8ae..f9ce165 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -580,6 +580,7 @@ sub SignEncryptRFC3156 {
 }
 
 sub SignEncryptInline {
+    my $self = shift;
     my %args = ( @_ );
 
     my $entity = $args{'Entity'};
@@ -588,19 +589,20 @@ sub SignEncryptInline {
     $entity->make_singlepart;
     if ( $entity->is_multipart ) {
         foreach ( $entity->parts ) {
-            %res = SignEncryptInline( @_, Entity => $_ );
+            %res = $self->SignEncryptInline( @_, Entity => $_ );
             return %res if $res{'exit_code'};
         }
         return %res;
     }
 
-    return _SignEncryptTextInline( @_ )
+    return $self->_SignEncryptTextInline( @_ )
         if $entity->effective_type =~ /^text\//i;
 
-    return _SignEncryptAttachmentInline( @_ );
+    return $self->_SignEncryptAttachmentInline( @_ );
 }
 
 sub _SignEncryptTextInline {
+    my $self = shift;
     my %args = (
         Entity => undef,
 
@@ -689,6 +691,7 @@ sub _SignEncryptTextInline {
 }
 
 sub _SignEncryptAttachmentInline {
+    my $self = shift;
     my %args = (
         Entity => undef,
 
@@ -791,6 +794,7 @@ sub _SignEncryptAttachmentInline {
 }
 
 sub SignEncryptContent {
+    my $self = shift;
     my %args = (
         Content => undef,
 
@@ -1026,6 +1030,7 @@ sub FindProtectedParts {
 =cut
 
 sub VerifyDecrypt {
+    my $self = shift;
     my %args = (
         Entity    => undef,
         Detach    => 1,
@@ -1049,7 +1054,7 @@ sub VerifyDecrypt {
             push @res, { VerifyInline( %$item ) };
             $status_on = $item->{'Data'};
         } elsif ( $item->{'Format'} eq 'Attachment' ) {
-            push @res, { VerifyAttachment( %$item ) };
+            %res = $self->VerifyAttachment( %$item );
             if ( $args{'Detach'} ) {
                 $item->{'Top'}->parts( [
                     grep "$_" ne $item->{'Signature'}, $item->{'Top'}->parts
@@ -1087,9 +1092,10 @@ sub VerifyDecrypt {
     return @res;
 }
 
-sub VerifyInline { return DecryptInline( @_ ) }
+sub VerifyInline { return (shift)->DecryptInline( @_ ) }
 
 sub VerifyAttachment {
+    my $self = shift;
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
     my $gnupg = new GnuPG::Interface;
@@ -1144,6 +1150,7 @@ sub VerifyAttachment {
 }
 
 sub VerifyRFC3156 {
+    my $self = shift;
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
     my $gnupg = new GnuPG::Interface;
@@ -1191,6 +1198,7 @@ sub VerifyRFC3156 {
 }
 
 sub DecryptRFC3156 {
+    my $self = shift;
     my %args = (
         Data => undef,
         Info => undef,
@@ -1271,6 +1279,7 @@ sub DecryptRFC3156 {
 }
 
 sub DecryptInline {
+    my $self = shift;
     my %args = (
         Data => undef,
         Passphrase => undef,
@@ -1321,7 +1330,7 @@ sub DecryptInline {
             seek $block_fh, 0, 0;
 
             my ($res_fh, $res_fn);
-            ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
+            ($res_fh, $res_fn, %res) = $self->_DecryptInlineBlock(
                 %args,
                 GnuPG => $gnupg,
                 BlockHandle => $block_fh,
@@ -1379,6 +1388,7 @@ sub DecryptInline {
 }
 
 sub _DecryptInlineBlock {
+    my $self = shift;
     my %args = (
         GnuPG => undef,
         BlockHandle => undef,
@@ -1429,6 +1439,7 @@ sub _DecryptInlineBlock {
 }
 
 sub DecryptAttachment {
+    my $self = shift;
     my %args = (
         Top  => undef,
         Data => undef,
@@ -1462,7 +1473,7 @@ sub DecryptAttachment {
     $args{'Data'}->bodyhandle->print( $tmp_fh );
     seek $tmp_fh, 0, 0;
 
-    my ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
+    my ($res_fh, $res_fn, %res) = $self->_DecryptInlineBlock(
         %args,
         GnuPG => $gnupg,
         BlockHandle => $tmp_fh,
@@ -1481,6 +1492,7 @@ sub DecryptAttachment {
 }
 
 sub DecryptContent {
+    my $self = shift;
     my %args = (
         Content => undef,
         Passphrase => undef,
@@ -2384,7 +2396,7 @@ sub DrySign {
         Data    => ['t'],
     );
 
-    my %res = SignEncrypt(
+    my %res = $self->SignEncrypt(
         Sign    => 1,
         Encrypt => 0,
         Entity  => $mime,

commit a775b705f6d1171c21c168b53c329a41a9c8dfe4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 15 08:23:19 2010 +0300

    first pass on generic Crypt API for RT

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
new file mode 100644
index 0000000..b086333
--- /dev/null
+++ b/lib/RT/Crypt.pm
@@ -0,0 +1,114 @@
+
+use strict;
+use warnings;
+
+package RT::Crypt;
+
+require RT::Crypt::GnuPG;
+require RT::Crypt::SMIME;
+
+sub EnabledOnIncoming {
+    return 'GnuPG', 'SMIME';
+}
+
+# encryption and signatures can be nested one over another, for example:
+# GPG inline signed text can be signed with SMIME
+
+sub FindProtectedParts {
+    my $self = shift;
+    my %args = (
+        Entity => undef,
+        Skip => {},
+        Scattered => 1,
+        @_
+    );
+
+    my $entity = $args{'Entity'};
+    return () if $args{'Skip'}{ $entity };
+
+    my @protocols = $self->EnabledOnIncoming;
+
+    foreach my $protocol ( @protocols ) {
+        my $class = 'RT::Crypt::'. $protocol;
+        my %info = $class->CheckIfProtected( Entity => $entity );
+        next unless keys %info;
+
+        $args{'Skip'}{ $entity } = 1;
+        $info{'Protocol'} = $protocol;
+        return \%info;
+    }
+
+    # not protected itself, look inside
+    push @res, $self->FindProtectedParts(
+        %args, Entity => $_, Scattered => 0,
+    ) foreach grep !$args{'Skip'}{$_}, $entity->parts;
+
+    if ( $args{'Scattered'} ) {
+        my $filter; $filter = sub {
+            return grep !$args{'Skip'}{$_},
+                $_[0]->is_multipart ? () : $_[0],
+                map $filter->($_), grep !$args{'Skip'}{$_},
+                    $_[0]->parts;
+        };
+        my @parts = $filter->($entity);
+        foreach my $protocol ( @protocols ) {
+            my $class = 'RT::Crypt::'. $protocol;
+            my @list = $class->FindScatteredParts( Parts => \@parts, Skip => $args{'Skip'} );
+            next unless @list;
+
+            push @res, @list;
+            @parts = grep !$args{'Skip'}{$_}, @parts;
+        }
+    }
+
+    return @res;
+}
+
+sub SignEncrypt {
+    my $self = shift;
+    my %args = (@_);
+
+    my $entity = $args{'Entity'};
+    if ( $args{'Sign'} && !defined $args{'Signer'} ) {
+        $args{'Signer'} =
+            $self->UseKeyForSigning
+            || (Email::Address->parse( $entity->head->get( 'From' ) ))[0]->address;
+    }
+    if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
+        my %seen;
+        $args{'Recipients'} = [
+            grep $_ && !$seen{ $_ }++, map $_->address,
+            map Email::Address->parse( $entity->head->get( $_ ) ),
+            qw(To Cc Bcc)
+        ];
+    }
+
+    my $using = delete $args{'Using'} || 'GnuPG';
+    my $class = 'RT::Crypt::'. $using;
+
+    return $class->SignEncrypt( %args );
+}
+
+sub VerifyDecrypt {
+    my $self = shift;
+    my %args = (
+        Entity    => undef,
+        Detach    => 1,
+        SetStatus => 1,
+        AddStatus => 0,
+        @_
+    );
+
+    my @res;
+
+    my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} );
+    foreach my $protected ( @protected ) {
+        my $protocol = $protected->{'Protocol'};
+        my $class = 'RT::Crypt::'. $protocol;
+        my %res = $class->VerifyDecrypt( %args, %$protected );
+        push @res, \%res;
+    }
+    return @res;
+}
+
+1;

commit 5346ee1c19f6b710f042619e6b8642cafc779c54
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 15 08:24:30 2010 +0300

    switch SignEncrypt to new API
    
    RT::Crypt now drives this and prepares required data

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index f9ce165..e1fe736 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -395,27 +395,13 @@ Returns a hash with the following keys:
 =cut
 
 sub SignEncrypt {
-    my %args = (@_);
+    my $self = shift;
 
-    my $entity = $args{'Entity'};
-    if ( $args{'Sign'} && !defined $args{'Signer'} ) {
-        $args{'Signer'} = UseKeyForSigning()
-            || (Email::Address->parse( $entity->head->get( 'From' ) ))[0]->address;
-    }
-    if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
-        my %seen;
-        $args{'Recipients'} = [
-            grep $_ && !$seen{ $_ }++, map $_->address,
-            map Email::Address->parse( $entity->head->get( $_ ) ),
-            qw(To Cc Bcc)
-        ];
-    }
-    
     my $format = lc RT->Config->Get('GnuPG')->{'OutgoingMessagesFormat'} || 'RFC';
     if ( $format eq 'inline' ) {
-        return SignEncryptInline( %args );
+        return $self->SignEncryptInline( @_ );
     } else {
-        return SignEncryptRFC3156( %args );
+        return $self->SignEncryptRFC3156( @_ );
     }
 }
 

commit 54923306e89006bce78d119c56f9d7ca2a7ec558
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 15 08:28:46 2010 +0300

    re-write FindProtectedParts
    
    we have to split this functionality into two separate steps:
    1) CheckIfProtected
    2) FindScatteredParts

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index e1fe736..30831b2 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -875,142 +875,172 @@ sub SignEncryptContent {
     return %res;
 }
 
-sub FindProtectedParts {
-    my %args = ( Entity => undef, CheckBody => 1, @_ );
+sub CheckIfProtected {
+    my $self = shift;
+    my %args = ( Entity => undef, @_ );
+
     my $entity = $args{'Entity'};
 
-    # inline PGP block, only in singlepart
-    unless ( $entity->is_multipart ) {
-        my $io = $entity->open('r');
-        unless ( $io ) {
-            $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
-            return ();
-        }
-        while ( defined($_ = $io->getline) ) {
-            next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
-            my $type = $1? 'signed': 'encrypted';
-            $RT::Logger->debug("Found $type inline part");
-            return {
-                Type    => $type,
-                Format  => 'Inline',
-                Data  => $entity,
-            };
-        }
-        $io->close;
+    # we check inline PGP block later in another sub
+    return () unless $entity->is_multipart;
+
+    # RFC3156, multipart/{signed,encrypted}
+    my $type = $entity->effective_type;
+    return () unless $type = =~ /^multipart\/(?:encrypted|signed)$/;
+
+    unless ( $entity->parts == 2 ) {
+        $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
         return ();
     }
 
-    # RFC3156, multipart/{signed,encrypted}
-    if ( ( my $type = $entity->effective_type ) =~ /^multipart\/(?:encrypted|signed)$/ ) {
-        unless ( $entity->parts == 2 ) {
-            $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
+    my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
+    unless ( $protocol ) {
+        $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
+        return ();
+    }
+
+    if ( $type eq 'multipart/encrypted' ) {
+        unless ( $protocol eq 'application/pgp-encrypted' ) {
+            $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
             return ();
         }
-
-        my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
-        unless ( $protocol ) {
-            $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
+        $RT::Logger->debug("Found part encrypted according to RFC3156");
+        return (
+            Type   => 'encrypted',
+            Format => 'RFC3156',
+            Top    => $entity,
+            Data   => $entity->parts(1),
+            Info   => $entity->parts(0),
+        );
+    } else {
+        unless ( $protocol eq 'application/pgp-signature' ) {
+            $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" );
             return ();
         }
+        $RT::Logger->debug("Found part signed according to RFC3156");
+        return (
+            Type      => 'signed',
+            Format    => 'RFC3156',
+            Top       => $entity,
+            Data      => $entity->parts(0),
+            Signature => $entity->parts(1),
+        );
+    }
+    return ();
+}
+
+sub FindScatteredParts {
+    my $self = shift;
+    my %args = ( Parts => [], Skip => {}, @_ );
 
-        if ( $type eq 'multipart/encrypted' ) {
-            unless ( $protocol eq 'application/pgp-encrypted' ) {
-                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
-                return ();
+    my @res;
+
+    my @parts = @{ $args{'Parts'} };
+
+    # attachments signed with signature in another part
+    {
+        my @file_indices;
+        for (my $i = 0; $i < @parts; $i++ ) {
+            my $part = $parts[ $i ];
+
+            # we can not associate a signature within an attachment
+            # without file names
+            my $fname = $part->head->recommended_filename;
+            next unless $fname;
+
+            my $type = $part->effective_type;
+
+            if ( $type eq 'application/pgp-signature' ) {
+                push @file_indices, $i;
             }
-            $RT::Logger->debug("Found encrypted according to RFC3156 part");
-            return {
-                Type    => 'encrypted',
-                Format  => 'RFC3156',
-                Top   => $entity,
-                Data  => $entity->parts(1),
-                Info    => $entity->parts(0),
-            };
-        } else {
-            unless ( $protocol eq 'application/pgp-signature' ) {
-                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" );
-                return ();
+            elsif ( $type eq 'application/octet-stream' && $fname =~ /\.sig$/i ) {
+                push @file_indices, $i;
             }
-            $RT::Logger->debug("Found signed according to RFC3156 part");
-            return {
-                Type      => 'signed',
-                Format    => 'RFC3156',
-                Top     => $entity,
-                Data    => $entity->parts(0),
-                Signature => $entity->parts(1),
-            };
         }
-    }
 
-    # attachments signed with signature in another part
-    my @file_indices;
-    foreach my $i ( 0 .. $entity->parts - 1 ) {
-        my $part = $entity->parts($i);
+        foreach my $i ( @file_indices ) {
+            my $sig_part = $parts[ $i ];
+            my $sig_name = $sig_part->head->recommended_filename;
+            my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/;
+
+            my ($data_part_idx) =
+                grep $file_name eq ($parts[$_]->head->recommended_filename||''),
+                grep $sig_part  ne  $parts[$_],
+                    0 .. @parts - 1;
+            unless ( defined $data_part_idx ) {
+                $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
+                next;
+            }
 
-        # we can not associate a signature within an attachment
-        # without file names
-        my $fname = $part->head->recommended_filename;
-        next unless $fname;
+            my $data_part_in = $parts[ $data_part_idx ];
 
-        if ( $part->effective_type eq 'application/pgp-signature' ) {
-            push @file_indices, $i;
-        }
-        elsif ( $fname =~ /\.sig$/i && $part->effective_type eq 'application/octet-stream' ) {
-            push @file_indices, $i;
+            $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
+
+            $args{'Skip'}{$data_part_in} = 1;
+            $args{'Skip'}{$sig_part} = 1;
+            push @res, {
+                Type      => 'signed',
+                Format    => 'Attachment',
+                Top       => $entity,
+                Data      => $data_part_in,
+                Signature => $sig_part,
+            };
         }
     }
 
-    my (@res, %skip);
-    foreach my $i ( @file_indices ) {
-        my $sig_part = $entity->parts($i);
-        $skip{"$sig_part"}++;
-        my $sig_name = $sig_part->head->recommended_filename;
-        my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/;
+    # attachments with inline encryption
+    foreach my $part ( @parts ) {
+        next if $args{'Skip'}{$part};
 
-        my ($data_part_idx) =
-            grep $file_name eq ($entity->parts($_)->head->recommended_filename||''),
-            grep $sig_part  ne  $entity->parts($_),
-                0 .. $entity->parts - 1;
-        unless ( defined $data_part_idx ) {
-            $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
-            next;
-        }
-        my $data_part_in = $entity->parts($data_part_idx);
+        my $fname = $part->head->recommended_filename || '';
+        next unless $fname =~ /\.pgp$/;
 
-        $skip{"$data_part_in"}++;
-        $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
-        push @res, {
-            Type      => 'signed',
-            Format    => 'Attachment',
-            Top       => $entity,
-            Data      => $data_part_in,
-            Signature => $sig_part,
-        };
-    }
+        $RT::Logger->debug("Found encrypted attachment '$fname'");
 
-    # attachments with inline encryption
-    my @encrypted_indices =
-        grep {($entity->parts($_)->head->recommended_filename || '') =~ /\.pgp$/}
-            0 .. $entity->parts - 1;
-
-    foreach my $i ( @encrypted_indices ) {
-        my $part = $entity->parts($i);
-        $skip{"$part"}++;
-        $RT::Logger->debug("Found encrypted attachment '". $part->head->recommended_filename ."'");
+        $args{'Skip'}{$part} = 1;
         push @res, {
-            Type      => 'encrypted',
-            Format    => 'Attachment',
+            Type    => 'encrypted',
+            Format  => 'Attachment',
             Top     => $entity,
             Data    => $part,
         };
     }
 
-    push @res, FindProtectedParts( Entity => $_ )
-        foreach grep !$skip{"$_"}, $entity->parts;
+    # inline PGP block
+    foreach my $part ( @parts ) {
+        next if $args{'Skip'}{$part};
+
+        my $type = $self->_CheckIfProtectedInline( $entity );
+        next unless $type;
+
+        $args{'Skip'}{$part} = 1;
+        push @res, {
+            Type      => $type,
+            Format    => 'Inline',
+            Data      => $entity,
+        };
+    }
 
     return @res;
 }
 
+sub _CheckIfProtectedInline {
+    my $self = shift;
+    my $entity = shift;
+
+    my $io = $entity->open('r');
+    unless ( $io ) {
+        $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
+        return '';
+    }
+    while ( defined($_ = $io->getline) ) {
+        next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
+        return $1? 'signed': 'encrypted';
+    }
+    $io->close;
+    return '';
+}
+
 =head2 VerifyDecrypt Entity => undef, [ Detach => 1, Passphrase => undef, SetStatus => 1 ]
 
 =cut

commit 1769dd2a46f756357326eb6e104b59e7aa6e3b35
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 15 08:32:29 2010 +0300

    VerifyDecrypt works with one part only now

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 30831b2..2496b10 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1048,26 +1048,27 @@ sub _CheckIfProtectedInline {
 sub VerifyDecrypt {
     my $self = shift;
     my %args = (
-        Entity    => undef,
+        Info      => undef,
         Detach    => 1,
         SetStatus => 1,
         AddStatus => 0,
         @_
     );
-    my @protected = FindProtectedParts( Entity => $args{'Entity'} );
-    my @res;
-    # XXX: detaching may brake nested signatures
-    foreach my $item( grep $_->{'Type'} eq 'signed', @protected ) {
+
+    my %res;
+
+    my $item = $args{'Info'};
+    if ( $item->{'Type'} eq 'signed' ) {
         my $status_on;
         if ( $item->{'Format'} eq 'RFC3156' ) {
-            push @res, { VerifyRFC3156( %$item, SetStatus => $args{'SetStatus'} ) };
+            %res = $self->VerifyRFC3156( %$item, SetStatus => $args{'SetStatus'} );
             if ( $args{'Detach'} ) {
                 $item->{'Top'}->parts( [ $item->{'Data'} ] );
                 $item->{'Top'}->make_singlepart;
             }
             $status_on = $item->{'Top'};
         } elsif ( $item->{'Format'} eq 'Inline' ) {
-            push @res, { VerifyInline( %$item ) };
+            %res = $self->VerifyInline( %$item );
             $status_on = $item->{'Data'};
         } elsif ( $item->{'Format'} eq 'Attachment' ) {
             %res = $self->VerifyAttachment( %$item );
@@ -1078,6 +1079,8 @@ sub VerifyDecrypt {
                 $item->{'Top'}->make_singlepart;
             }
             $status_on = $item->{'Data'};
+        } else {
+            die "Unknow format '". $item->{'Format'} ."' of GnuPG signed part";
         }
         if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
             my $method = $args{'AddStatus'} ? 'add' : 'set';
@@ -1085,18 +1088,19 @@ sub VerifyDecrypt {
                 'X-RT-GnuPG-Status' => $res[-1]->{'status'}
             );
         }
-    }
-    foreach my $item( grep $_->{'Type'} eq 'encrypted', @protected ) {
+    } elsif ( $item->{'Type'} eq 'encrypted' ) {
         my $status_on;
         if ( $item->{'Format'} eq 'RFC3156' ) {
-            push @res, { DecryptRFC3156( %$item ) };
+            %res = $self->DecryptRFC3156( %$item );
             $status_on = $item->{'Top'};
         } elsif ( $item->{'Format'} eq 'Inline' ) {
-            push @res, { DecryptInline( %$item ) };
+            %res = $self->DecryptInline( %$item );
             $status_on = $item->{'Data'};
         } elsif ( $item->{'Format'} eq 'Attachment' ) {
-            push @res, { DecryptAttachment( %$item ) };
+            %res = $self->DecryptAttachment( %$item );
             $status_on = $item->{'Data'};
+        } else {
+            die "Unknow format '". $item->{'Format'} ."' of GnuPG encrypted part";
         }
         if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
             my $method = $args{'AddStatus'} ? 'add' : 'set';
@@ -1104,6 +1108,8 @@ sub VerifyDecrypt {
                 'X-RT-GnuPG-Status' => $res[-1]->{'status'}
             );
         }
+    } else {
+        die "Unknow type '". $item->{'Type'} ."' of protected item";
     }
     return @res;
 }

commit bb664b4ffcbdbeaa6e06ef29517ba4abdaee771e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 18 17:06:21 2010 +0300

    make sure Auth::Crypt is used as mail plugin

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index f8d121c..9e28cf2 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -413,7 +413,29 @@ our %META = (
             $self->Set( DisableGD => 1 );
         },
     },
-    MailPlugins  => { Type => 'ARRAY' },
+    MailPlugins  => {
+        Type => 'ARRAY',
+        PostLoadCheck => sub {
+            my $self = shift;
+            my @plugins = $self->Get('MailPlugins');
+            if ( grep $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME', @plugins ) {
+                $RT::Logger->warning(
+                    'Auth::GnuPG and Auth::SMIME (from an extension) has been'
+                    .' replaced with Auth::Crypt as generic mail plugin for'
+                    .' processing encrypted/signed emails using GnuPG, SMIME'
+                    .' and other security providing solutions.'
+                );
+                my %seen;
+                @plugins =
+                    grep !$seen{$_}++,
+                    grep {
+                        $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME'
+                        ? 'Auth::Crypt' : $_
+                    } @plugins;
+                $self->Set( MailPlugins => @plugins );
+            }
+        },
+    },
     Plugins      => { Type => 'ARRAY' },
     GnuPG        => { Type => 'HASH' },
     GnuPGOptions => { Type => 'HASH',

commit 085867fc8d554935fb10cd4588dc15f23c668ddc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 18 17:34:27 2010 +0300

    Auth::Crypt mail plugin out of Auth::GnuPG

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
new file mode 100644
index 0000000..e24a32c
--- /dev/null
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -0,0 +1,292 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse at bestpractical.com>
+# 
+# (Except where explicitly superseded by other copyright notices)
+# 
+# 
+# LICENSE:
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+# 
+# 
+# CONTRIBUTION SUBMISSION POLICY:
+# 
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+# 
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+# 
+# END BPS TAGGED BLOCK }}}
+
+package RT::Interface::Email::Auth::Crypt;
+
+use strict;
+use warnings;
+
+=head1 NAME
+
+RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
+
+=head2 DESCRIPTION
+
+This mail plugin decrypts and verifies incomming emails. Supported
+encryption protocols are GnuPG and SMIME.
+
+This code is independant from code that encrypts/sign outgoing emails, so
+it's possible to decrypt data without bringing in encryption. To enable
+it put the module in the mail plugins list:
+
+    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filters...);
+
+=head3 GnuPG
+
+To use the gnupg-secured mail gateway, you need to do the following:
+
+Set up a GnuPG key directory with a pubring containing only the keys
+you care about and specify the following in your SiteConfig.pm
+
+    Set(%GnuPGOptions, homedir => '/opt/rt3/var/data/GnuPG');
+    
+Read also: L<RT::Crypt> and L<RT::Crypt::GnuPG>.
+
+=head3 SMIME
+
+Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
+
+=cut
+
+sub ApplyBeforeDecode { return 1 }
+
+use RT::Crypt;
+use RT::EmailParser ();
+
+sub GetCurrentUser {
+    my %args = (
+        Message       => undef,
+        RawMessageRef => undef,
+        @_
+    );
+
+    # we clean all possible headers
+    my @headers =
+        qw(
+            X-RT-Incoming-Encrypton
+            X-RT-Incoming-Signature
+            X-RT-Privacy
+        ),
+        map "X-RT-$_-Status", RT::Crypt->Protocols;
+    foreach my $p ( $args{'Message'}->parts_DFS ) {
+        $p->head->delete($_) for @headers;
+    }
+
+    my @check_protocols = RT::Crypt->EnabledOnIncoming;
+
+    my $msg = $args{'Message'}->dup;
+
+    my ($status, @res) = VerifyDecrypt(
+        Entity => $args{'Message'}, AddStatus => 1,
+    );
+    if ( $status && !@res ) {
+        $args{'Message'}->head->add(
+            'X-RT-Incoming-Encryption' => 'Not encrypted'
+        );
+        return 1;
+    }
+
+    unless ( $status ) {
+        $RT::Logger->error("Had a problem during decrypting and verifying");
+        my $reject = HandleErrors( Message => $args{'Message'}, Result => \@res );
+        return (0, 'rejected because of problems during decrypting and verifying')
+            if $reject;
+    }
+
+    # attach the original encrypted message
+    $args{'Message'}->attach(
+        Type        => 'application/x-rt-original-message',
+        Disposition => 'inline',
+        Data        => ${ $args{'RawMessageRef'} },
+    );
+
+    my @found;
+    foreach my $part ( $args{'Message'}->parts_DFS ) {
+        my $decrypted;
+
+        foreach my $protocol ( @check_protocols ) {
+            my $status = $part->head->get( "X-RT-$protocol-Status" );
+            next unless $status;
+
+            push @found, $protocol;
+
+            for ( RT::Crypt->ParseStatus( Protocol => $protocol, Status => $status ) ) {
+                if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
+                    $decrypted = 1;
+                }
+                if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
+                    $part->head->add(
+                        'X-RT-Incoming-Signature' => $_->{UserString}
+                    );
+                }
+            }
+        }
+
+        $part->head->add(
+            'X-RT-Incoming-Encryption' => 
+                $decrypted ? 'Success' : 'Not encrypted'
+        );
+    }
+
+    my %seen;
+    $args{'Message'}->head->add( 'X-RT-Privacy' => $_ )
+        foreach grep !$seen{$_}++, @found;
+
+    return 1;
+}
+
+sub HandleErrors {
+    my %args = (
+        Message => undef,
+        Result => [],
+        @_
+    );
+
+    my $reject = 0;
+
+    my %sent_once = ();
+    foreach my $run ( @{ $args{'Result'} } ) {
+        my @status = RT::Crypt->ParseStatus( Protocol => $run->{'Protocol'}, Status => $run->{'status'} );
+        unless ( $sent_once{'NoPrivateKey'} ) {
+            unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
+                $sent_once{'NoPrivateKey'}++;
+                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnMissingPrivateKey'};
+            }
+        }
+        unless ( $sent_once{'BadData'} ) {
+            unless ( CheckBadData( Message => $args{'Message'}, Status => \@status ) ) {
+                $sent_once{'BadData'}++;
+                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnBadData'};
+            }
+        }
+    }
+    return $reject;
+}
+
+sub CheckNoPrivateKey {
+    my %args = (Message => undef, Status => [], @_ );
+    my @status = @{ $args{'Status'} };
+
+    my @decrypts = grep $_->{'Operation'} eq 'Decrypt', @status;
+    return 1 unless @decrypts;
+    foreach my $action ( @decrypts ) {
+        # if at least one secrete key exist then it's another error
+        return 1 if
+            grep !$_->{'User'}{'SecretKeyMissing'},
+                @{ $action->{'EncryptedTo'} };
+    }
+
+    $RT::Logger->error("Couldn't decrypt a message: have no private key");
+
+    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
+    my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
+        To        => $address,
+        Template  => 'Error: no private key',
+        Arguments => {
+            Message   => $args{'Message'},
+            TicketObj => $args{'Ticket'},
+        },
+        InReplyTo => $args{'Message'},
+    );
+    unless ( $status ) {
+        $RT::Logger->error("Couldn't send 'Error: no private key'");
+    }
+    return 0;
+}
+
+sub CheckBadData {
+    my %args = (Message => undef, Status => [], @_ );
+    my @bad_data_messages = 
+        map $_->{'Message'},
+        grep $_->{'Status'} ne 'DONE' && $_->{'Operation'} eq 'Data',
+        @{ $args{'Status'} };
+    return 1 unless @bad_data_messages;
+
+    $RT::Logger->error("Couldn't process a message: ". join ', ', @bad_data_messages );
+
+    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
+    my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
+        To        => $address,
+        Template  => 'Error: bad GnuPG data',
+        Arguments => {
+            Messages  => [ @bad_data_messages ],
+            TicketObj => $args{'Ticket'},
+        },
+        InReplyTo => $args{'Message'},
+    );
+    unless ( $status ) {
+        $RT::Logger->error("Couldn't send 'Error: bad GnuPG data'");
+    }
+    return 0;
+}
+
+sub VerifyDecrypt {
+    my %args = (
+        Entity => undef,
+        @_
+    );
+
+    my @res = RT::Crypt->VerifyDecrypt( %args );
+    unless ( @res ) {
+        $RT::Logger->debug("No more encrypted/signed parts");
+        return 1;
+    }
+
+    $RT::Logger->debug('Found encrypted/signed parts');
+
+    # return on any error
+    if ( grep $_->{'exit_code'}, @res ) {
+        $RT::Logger->debug("Error during verify/decrypt operation");
+        return (0, @res);
+    }
+
+    # nesting
+    my ($status, @nested) = VerifyDecrypt( %args );
+    return $status, @res, @nested;
+}
+
+eval "require RT::Interface::Email::Auth::Crypt_Vendor";
+die $@
+  if ( $@
+    && $@ !~ qr{^Can't locate RT/Interface/Email/Auth/Crypt_Vendor.pm} );
+eval "require RT::Interface::Email::Auth::Crypt_Local";
+die $@
+  if ( $@
+    && $@ !~ qr{^Can't locate RT/Interface/Email/Auth/Crypt_Local.pm} );
+
+1;

commit d3b01d9e4ca515408340420b175ad354b3ebeeaf
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 18 17:34:57 2010 +0300

    shrink lib/RT/Interface/Email/Auth/Crypt.pm to shortcut

diff --git a/lib/RT/Interface/Email/Auth/GnuPG.pm b/lib/RT/Interface/Email/Auth/GnuPG.pm
index f0fe2c9..866a870 100644
--- a/lib/RT/Interface/Email/Auth/GnuPG.pm
+++ b/lib/RT/Interface/Email/Auth/GnuPG.pm
@@ -59,196 +59,15 @@ Set up a GnuPG key directory with a pubring containing only the keys
 you care about and specify the following in your SiteConfig.pm
 
     Set(%GnuPGOptions, homedir => '/opt/rt3/var/data/GnuPG');
-    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::GnuPG', ...other filter...);
+    Set(@MailPlugins, 'Auth::MailFrom', 'Auth::Crypt', ...other filter...);
 
 =cut
 
 sub ApplyBeforeDecode { return 1 }
 
-use RT::Crypt::GnuPG;
-use RT::EmailParser ();
+use RT::Interface::Email::Auth::Crypt;
 
-sub GetCurrentUser {
-    my %args = (
-        Message       => undef,
-        RawMessageRef => undef,
-        @_
-    );
-
-    foreach my $p ( $args{'Message'}->parts_DFS ) {
-        $p->head->delete($_) for qw(
-            X-RT-GnuPG-Status X-RT-Incoming-Encrypton
-            X-RT-Incoming-Signature X-RT-Privacy
-        );
-    }
-
-    my $msg = $args{'Message'}->dup;
-
-    my ($status, @res) = VerifyDecrypt(
-        Entity => $args{'Message'}, AddStatus => 1,
-    );
-    if ( $status && !@res ) {
-        $args{'Message'}->head->add(
-            'X-RT-Incoming-Encryption' => 'Not encrypted'
-        );
-
-        return 1;
-    }
-
-    # FIXME: Check if the message is encrypted to the address of
-    # _this_ queue. send rejecting mail otherwise.
-
-    unless ( $status ) {
-        $RT::Logger->error("Had a problem during decrypting and verifying");
-        my $reject = HandleErrors( Message => $args{'Message'}, Result => \@res );
-        return (0, 'rejected because of problems during decrypting and verifying')
-            if $reject;
-    }
-
-    # attach the original encrypted message
-    $args{'Message'}->attach(
-        Type        => 'application/x-rt-original-message',
-        Disposition => 'inline',
-        Data        => ${ $args{'RawMessageRef'} },
-    );
-
-    $args{'Message'}->head->add( 'X-RT-Privacy' => 'PGP' );
-
-    foreach my $part ( $args{'Message'}->parts_DFS ) {
-        my $decrypted;
-
-        my $status = $part->head->get( 'X-RT-GnuPG-Status' );
-        if ( $status ) {
-            for ( RT::Crypt::GnuPG::ParseStatus( $status ) ) {
-                if ( $_->{Operation} eq 'Decrypt' && $_->{Status} eq 'DONE' ) {
-                    $decrypted = 1;
-                }
-                if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
-                    $part->head->add(
-                        'X-RT-Incoming-Signature' => $_->{UserString}
-                    );
-                }
-            }
-        }
-
-        $part->head->add(
-            'X-RT-Incoming-Encryption' => 
-                $decrypted ? 'Success' : 'Not encrypted'
-        );
-    }
-
-    return 1;
-}
-
-sub HandleErrors {
-    my %args = (
-        Message => undef,
-        Result => [],
-        @_
-    );
-
-    my $reject = 0;
-
-    my %sent_once = ();
-    foreach my $run ( @{ $args{'Result'} } ) {
-        my @status = RT::Crypt::GnuPG::ParseStatus( $run->{'status'} );
-        unless ( $sent_once{'NoPrivateKey'} ) {
-            unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
-                $sent_once{'NoPrivateKey'}++;
-                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnMissingPrivateKey'};
-            }
-        }
-        unless ( $sent_once{'BadData'} ) {
-            unless ( CheckBadData( Message => $args{'Message'}, Status => \@status ) ) {
-                $sent_once{'BadData'}++;
-                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnBadData'};
-            }
-        }
-    }
-    return $reject;
-}
-
-sub CheckNoPrivateKey {
-    my %args = (Message => undef, Status => [], @_ );
-    my @status = @{ $args{'Status'} };
-
-    my @decrypts = grep $_->{'Operation'} eq 'Decrypt', @status;
-    return 1 unless @decrypts;
-    foreach my $action ( @decrypts ) {
-        # if at least one secrete key exist then it's another error
-        return 1 if
-            grep !$_->{'User'}{'SecretKeyMissing'},
-                @{ $action->{'EncryptedTo'} };
-    }
-
-    $RT::Logger->error("Couldn't decrypt a message: have no private key");
-
-    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
-    my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
-        To        => $address,
-        Template  => 'Error: no private key',
-        Arguments => {
-            Message   => $args{'Message'},
-            TicketObj => $args{'Ticket'},
-        },
-        InReplyTo => $args{'Message'},
-    );
-    unless ( $status ) {
-        $RT::Logger->error("Couldn't send 'Error: no private key'");
-    }
-    return 0;
-}
-
-sub CheckBadData {
-    my %args = (Message => undef, Status => [], @_ );
-    my @bad_data_messages = 
-        map $_->{'Message'},
-        grep $_->{'Status'} ne 'DONE' && $_->{'Operation'} eq 'Data',
-        @{ $args{'Status'} };
-    return 1 unless @bad_data_messages;
-
-    $RT::Logger->error("Couldn't process a message: ". join ', ', @bad_data_messages );
-
-    my $address = (RT::Interface::Email::ParseSenderAddressFromHead( $args{'Message'}->head ))[0];
-    my ($status) = RT::Interface::Email::SendEmailUsingTemplate(
-        To        => $address,
-        Template  => 'Error: bad GnuPG data',
-        Arguments => {
-            Messages  => [ @bad_data_messages ],
-            TicketObj => $args{'Ticket'},
-        },
-        InReplyTo => $args{'Message'},
-    );
-    unless ( $status ) {
-        $RT::Logger->error("Couldn't send 'Error: bad GnuPG data'");
-    }
-    return 0;
-}
-
-sub VerifyDecrypt {
-    my %args = (
-        Entity => undef,
-        @_
-    );
-
-    my @res = RT::Crypt::GnuPG::VerifyDecrypt( %args );
-    unless ( @res ) {
-        $RT::Logger->debug("No more encrypted/signed parts");
-        return 1;
-    }
-
-    $RT::Logger->debug('Found GnuPG protected parts');
-
-    # return on any error
-    if ( grep $_->{'exit_code'}, @res ) {
-        $RT::Logger->debug("Error during verify/decrypt operation");
-        return (0, @res);
-    }
-
-    # nesting
-    my ($status, @nested) = VerifyDecrypt( %args );
-    return $status, @res, @nested;
-}
+sub GetCurrentUser { return RT::Interface::Email::Auth::Crypt::GetCurrentUser(@_) }
 
 eval "require RT::Interface::Email::Auth::GnuPG_Vendor";
 die $@

commit 0c26e3f0125f9f3455a7ef96a05ca19adcc546d4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 18 17:37:13 2010 +0300

    abstracting and maintaining currently used protocol

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index b086333..071e24c 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -7,6 +7,12 @@ package RT::Crypt;
 require RT::Crypt::GnuPG;
 require RT::Crypt::SMIME;
 
+our @PROTOCOLS = ('GnuPG', 'SMIME');
+
+sub Protocols {
+    return @PROTOCOLS;
+}
+
 sub EnabledOnIncoming {
     return 'GnuPG', 'SMIME';
 }
@@ -17,8 +23,9 @@ sub EnabledOnIncoming {
 sub FindProtectedParts {
     my $self = shift;
     my %args = (
-        Entity => undef,
-        Skip => {},
+        Entity    => undef,
+        Protocols => undef,
+        Skip      => {},
         Scattered => 1,
         @_
     );
@@ -26,7 +33,9 @@ sub FindProtectedParts {
     my $entity = $args{'Entity'};
     return () if $args{'Skip'}{ $entity };
 
-    my @protocols = $self->EnabledOnIncoming;
+    my @protocols = $args{'Protocols'}
+        ? @{ $args{'Protocols'} } 
+        : $self->EnabledOnIncoming;
 
     foreach my $protocol ( @protocols ) {
         my $class = 'RT::Crypt::'. $protocol;
@@ -83,7 +92,7 @@ sub SignEncrypt {
         ];
     }
 
-    my $using = delete $args{'Using'} || 'GnuPG';
+    my $using = delete $args{'Protocol'} || 'GnuPG';
     my $class = 'RT::Crypt::'. $using;
 
     return $class->SignEncrypt( %args );
@@ -106,6 +115,7 @@ sub VerifyDecrypt {
         my $protocol = $protected->{'Protocol'};
         my $class = 'RT::Crypt::'. $protocol;
         my %res = $class->VerifyDecrypt( %args, %$protected );
+        $res{'Protocol'} = $protocol;
         push @res, \%res;
     }
     return @res;

commit ca117aa45eee2974cf4e44cf745ddee2cdbcf7f3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 18 17:38:08 2010 +0300

    ParseStatus in RT::Crypt

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 071e24c..b3c393d 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -121,4 +121,15 @@ sub VerifyDecrypt {
     return @res;
 }
 
+sub ParseStatus {
+    my $self = shift;
+    my %args = (
+        Protocol => undef,
+        Status   => '',
+        @_
+    );
+    my $class = 'RT::Crypt::'. $args{'Protocol'};
+    return $class->ParseStatus( $args{'Status'} );
+}
+
 1;

commit 4d554f1113b60a68186ba673650744ff98ae8a2a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 18 17:47:03 2010 +0300

    more on methods instead of functions

diff --git a/lib/RT/Attachment_Overlay.pm b/lib/RT/Attachment_Overlay.pm
index e2707a7..f6f60df 100644
--- a/lib/RT/Attachment_Overlay.pm
+++ b/lib/RT/Attachment_Overlay.pm
@@ -672,7 +672,7 @@ sub Encrypt {
     $self->SetHeader( 'Content-Type' => $type );
 
     my $content = $self->Content;
-    my %res = RT::Crypt::GnuPG::SignEncryptContent(
+    my %res = RT::Crypt->SignEncryptContent(
         Content => \$content,
         Sign => 0,
         Encrypt => 1,
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 2496b10..f488264 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1710,6 +1710,7 @@ my %ignore_keyword = map { $_ => 1 } qw(
 );
 
 sub ParseStatus {
+    my $self = shift;
     my $status = shift;
     return () unless $status;
 
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index e0815fb..5c57158 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -750,8 +750,8 @@ sub SignEncrypt {
     $RT::Logger->debug("$msgid Signing message") if $args{'Sign'};
     $RT::Logger->debug("$msgid Encrypting message") if $args{'Encrypt'};
 
-    require RT::Crypt::GnuPG;
-    my %res = RT::Crypt::GnuPG::SignEncrypt( %args );
+    require RT::Crypt;
+    my %res = RT::Crypt->SignEncrypt( %args );
     return 1 unless $res{'exit_code'};
 
     my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
@@ -818,7 +818,7 @@ sub SignEncrypt {
     }
 
     # redo without broken recipients
-    %res = RT::Crypt::GnuPG::SignEncrypt( %args );
+    %res = RT::Crypt->SignEncrypt( %args );
     return 0 if $res{'exit_code'};
 
     return 1;
diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
index f33fbab..42d218f 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -38,7 +38,7 @@ diag 'only signing. correct passphrase' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
     ok( $entity, 'signed entity');
     ok( !$res{'logger'}, "log is here as well" ) or diag $res{'logger'};
     my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
@@ -52,7 +52,7 @@ diag 'only signing. correct passphrase' if $ENV{'TEST_VERBOSE'};
     ok( $entity->is_multipart, 'signed message is multipart' );
     is( $entity->parts, 2, 'two parts' );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 1, 'one protected part' );
     is( $parts[0]->{'Type'}, 'signed', "have signed part" );
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
@@ -74,7 +74,7 @@ diag 'only signing. missing passphrase' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => '' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => '' );
     ok( $res{'exit_code'}, "couldn't sign without passphrase");
     ok( $res{'error'} || $res{'logger'}, "error is here" );
 
@@ -91,7 +91,7 @@ diag 'only signing. wrong passphrase' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'wrong' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'wrong' );
     ok( $res{'exit_code'}, "couldn't sign with bad passphrase");
     ok( $res{'error'} || $res{'logger'}, "error is here" );
 
@@ -109,7 +109,7 @@ diag 'encryption only' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( !$res{'exit_code'}, "successful encryption" );
     ok( !$res{'logger'}, "no records in logger" );
 
@@ -120,7 +120,7 @@ diag 'encryption only' if $ENV{'TEST_VERBOSE'};
 
     ok($entity, 'get an encrypted part');
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 1, 'one protected part' );
     is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
@@ -135,7 +135,7 @@ diag 'encryption only, bad recipient' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( $res{'exit_code'}, 'no way to encrypt without keys of recipients');
     ok( $res{'logger'}, "errors are in logger" );
 
@@ -152,7 +152,7 @@ diag 'encryption and signing with combined method' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Passphrase => 'test' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Passphrase => 'test' );
     ok( !$res{'exit_code'}, "successful encryption with signing" );
     ok( !$res{'logger'}, "no records in logger" );
 
@@ -167,7 +167,7 @@ diag 'encryption and signing with combined method' if $ENV{'TEST_VERBOSE'};
 
     ok($entity, 'get an encrypted and signed part');
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 1, 'one protected part' );
     is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
@@ -182,14 +182,14 @@ diag 'encryption and signing with cascading, sign on encrypted' if $ENV{'TEST_VE
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( !$res{'exit_code'}, 'successful encryption' );
     ok( !$res{'logger'}, "no records in logger" );
-    %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+    %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
     ok( !$res{'exit_code'}, 'successful signing' );
     ok( !$res{'logger'}, "no records in logger" );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 1, 'one protected part, top most' );
     is( $parts[0]->{'Type'}, 'signed', "have signed part" );
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
@@ -204,7 +204,7 @@ diag 'find signed/encrypted part deep inside' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( !$res{'exit_code'}, "success" );
     $entity->make_multipart( 'mixed', Force => 1 );
     $entity->attach(
@@ -212,7 +212,7 @@ diag 'find signed/encrypted part deep inside' if $ENV{'TEST_VERBOSE'};
         Data => ['-'x76, 'this is mailing list'],
     );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 1, 'one protected part' );
     is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
@@ -227,11 +227,11 @@ diag 'wrong signed/encrypted parts: no protocol' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => undef );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 0, 'no protected parts' );
 }
 
@@ -243,11 +243,11 @@ diag 'wrong signed/encrypted parts: not enought parts' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( !$res{'exit_code'}, 'success' );
     $entity->parts([ $entity->parts(0) ]);
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 0, 'no protected parts' );
 }
 
@@ -259,11 +259,11 @@ diag 'wrong signed/encrypted parts: wrong proto' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Sign => 0 );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Sign => 0 );
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => 'application/bad-proto' );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 0, 'no protected parts' );
 }
 
@@ -275,11 +275,11 @@ diag 'wrong signed/encrypted parts: wrong proto' if $ENV{'TEST_VERBOSE'};
         Subject => 'test',
         Data    => ['test'],
     );
-    my %res = RT::Crypt::GnuPG::SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
+    my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => 'application/bad-proto' );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 0, 'no protected parts' );
 }
 
@@ -289,7 +289,7 @@ diag 'verify inline and in attachment signatures' if $ENV{'TEST_VERBOSE'};
     my $parser = new MIME::Parser;
     my $entity = $parser->parse( $fh );
 
-    my @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     is( scalar @parts, 2, 'two protected parts' );
     is( $parts[1]->{'Type'}, 'signed', "have signed part" );
     is( $parts[1]->{'Format'}, 'Inline', "inline format" );

commit 6354fd26a70cc6bd708f3f5b2cf80e16242d9424
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 12:05:08 2010 +0300

    more conversions from funstions to class methods

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index f488264..fdfc3d6 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -438,7 +438,7 @@ sub SignEncryptRFC3156 {
     my $entity = $args{'Entity'};
 
     if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
     }
 
     my %res;
@@ -620,7 +620,7 @@ sub _SignEncryptTextInline {
     );
 
     if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
     }
 
     if ( $args{'Encrypt'} ) {
@@ -709,7 +709,7 @@ sub _SignEncryptAttachmentInline {
     );
 
     if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
     }
 
     my $entity = $args{'Entity'};
@@ -812,7 +812,7 @@ sub SignEncryptContent {
     );
 
     if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
     }
 
     if ( $args{'Encrypt'} ) {
@@ -886,7 +886,7 @@ sub CheckIfProtected {
 
     # RFC3156, multipart/{signed,encrypted}
     my $type = $entity->effective_type;
-    return () unless $type = =~ /^multipart\/(?:encrypted|signed)$/;
+    return () unless $type =~ /^multipart\/(?:encrypted|signed)$/;
 
     unless ( $entity->parts == 2 ) {
         $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
@@ -1247,7 +1247,7 @@ sub DecryptRFC3156 {
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
+    $args{'Passphrase'} = $self->GetPassphrase()
         unless defined $args{'Passphrase'};
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
@@ -1326,7 +1326,7 @@ sub DecryptInline {
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
+    $args{'Passphrase'} = $self->GetPassphrase()
         unless defined $args{'Passphrase'};
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
@@ -1487,7 +1487,7 @@ sub DecryptAttachment {
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
+    $args{'Passphrase'} = $self->GetPassphrase()
         unless defined $args{'Passphrase'};
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
@@ -1534,7 +1534,7 @@ sub DecryptContent {
         meta_interactive => 0,
     );
 
-    $args{'Passphrase'} = GetPassphrase()
+    $args{'Passphrase'} = $self->GetPassphrase()
         unless defined $args{'Passphrase'};
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
@@ -1600,6 +1600,7 @@ Returns passphrase, called whenever it's required with Address as a named argume
 =cut
 
 sub GetPassphrase {
+    my $self = shift;
     my %args = ( Address => undef, @_ );
     return 'test';
 }
@@ -1999,7 +2000,7 @@ also listed.
 
 sub GetKeysForEncryption {
     my $key_id = shift;
-    my %res = GetKeysInfo( $key_id, 'public', @_ );
+    my %res = $self->GetKeysInfo( $key_id, 'public', @_ );
     return %res if $res{'exit_code'};
     return %res unless $res{'info'};
 
@@ -2020,7 +2021,7 @@ sub GetKeysForEncryption {
 
 sub GetKeysForSigning {
     my $key_id = shift;
-    return GetKeysInfo( $key_id, 'private', @_ );
+    return $self->GetKeysInfo( $key_id, 'private', @_ );
 }
 
 sub CheckRecipients {
@@ -2097,12 +2098,13 @@ sub GetPrivateKeyInfo {
 }
 
 sub GetKeyInfo {
-    my %res = GetKeysInfo(@_);
+    my %res = $self->GetKeysInfo(@_);
     $res{'info'} = $res{'info'}->[0];
     return %res;
 }
 
 sub GetKeysInfo {
+    my $self = shift;
     my $email = shift;
     my $type = shift || 'public';
     my $force = shift;
@@ -2308,6 +2310,7 @@ sub _ParseDate {
 }
 
 sub DeleteKey {
+    my $self = shift;
     my $key = shift;
 
     my $gnupg = new GnuPG::Interface;
@@ -2356,6 +2359,7 @@ sub DeleteKey {
 }
 
 sub ImportKey {
+    my $self = shift;
     my $key = shift;
 
     my $gnupg = new GnuPG::Interface;
@@ -2409,6 +2413,7 @@ Returns a true value if all went well.
 =cut
 
 sub DrySign {
+    my $self = shift;
     my $from = shift;
 
     my $mime = MIME::Entity->build(
@@ -2441,6 +2446,7 @@ properly (and false otherwise).
 
 
 sub Probe {
+    my $self = shift;
     my $gnupg = new GnuPG::Interface;
     my %opt = RT->Config->Get('GnuPGOptions');
     $gnupg->options->hash_init(

commit 470690611b89f596bc09c11bacd735e254a28284
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 12:06:05 2010 +0300

    when we SignEncrypt store protocol in results

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index b3c393d..8b8a6e2 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -92,10 +92,12 @@ sub SignEncrypt {
         ];
     }
 
-    my $using = delete $args{'Protocol'} || 'GnuPG';
-    my $class = 'RT::Crypt::'. $using;
+    my $protocol = delete $args{'Protocol'} || 'GnuPG';
+    my $class = 'RT::Crypt::'. $protocol;
 
-    return $class->SignEncrypt( %args );
+    my %res = $class->SignEncrypt( %args );
+    $res{'Protocol'} = $protocol;
+    return %res;
 }
 
 sub VerifyDecrypt {

commit e8328cb3f347f0ab2f847326ffec99a3f1a43510
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 12:08:15 2010 +0300

    switch to using class methods, for example generic ParseStatus

diff --git a/lib/RT/Attachment_Overlay.pm b/lib/RT/Attachment_Overlay.pm
index f6f60df..f1cad37 100644
--- a/lib/RT/Attachment_Overlay.pm
+++ b/lib/RT/Attachment_Overlay.pm
@@ -658,7 +658,7 @@ sub Encrypt {
         RT->Config->Get('CorrespondAddress'),
         RT->Config->Get('CommentAddress'),
     ) {
-        my %res = RT::Crypt::GnuPG::GetKeysInfo( $address, 'private' );
+        my %res = RT::Crypt::GnuPG->GetKeysInfo( $address, 'private' );
         next if $res{'exit_code'} || !$res{'info'};
         %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address );
         next if $res{'exit_code'} || !$res{'info'};
diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 5c57158..6269d07 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -754,7 +754,9 @@ sub SignEncrypt {
     my %res = RT::Crypt->SignEncrypt( %args );
     return 1 unless $res{'exit_code'};
 
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'},
+    );
 
     my @bad_recipients;
     foreach my $line ( @status ) {
diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index b8d1683..b870a05 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -860,7 +860,7 @@ sub import_gnupg_key {
     die "can't find the dir where gnupg keys are stored"
       unless $abs_path;
 
-    return RT::Crypt::GnuPG::ImportKey(
+    return RT::Crypt::GnuPG->ImportKey(
         RT::Test->file_content( [ $abs_path, $key ] ) );
 }
 

commit d666963f48ef0c35d4148d2c327b09982c6179ae
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 12:28:59 2010 +0300

    convert t/mail/crypt-gnupg.t over new API

diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
index 42d218f..b05a903 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -41,7 +41,9 @@ diag 'only signing. correct passphrase' if $ENV{'TEST_VERBOSE'};
     my %res = RT::Crypt->SignEncrypt( Entity => $entity, Encrypt => 0, Passphrase => 'test' );
     ok( $entity, 'signed entity');
     ok( !$res{'logger'}, "log is here as well" ) or diag $res{'logger'};
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'}
+    );
     is( scalar @status, 2, 'two records: passphrase, signing');
     is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
     is( $status[0]->{'Status'}, 'DONE', 'good passphrase');
@@ -58,9 +60,11 @@ diag 'only signing. correct passphrase' if $ENV{'TEST_VERBOSE'};
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
     is( $parts[0]->{'Top'}, $entity, "it's the same entity" );
 
-    my @res = RT::Crypt::GnuPG::VerifyDecrypt( Entity => $entity );
+    my @res = RT::Crypt->VerifyDecrypt( Entity => $entity );
     is scalar @res, 1, 'one operation';
-    @status = RT::Crypt::GnuPG::ParseStatus( $res[0]{'status'} );
+    @status = RT::Crypt->ParseStatus(
+        Protocol => $res[0]{'Protocol'}, Status => $res[0]{'status'}
+    );
     is( scalar @status, 1, 'one record');
     is( $status[0]->{'Operation'}, 'Verify', 'operation is correct');
     is( $status[0]->{'Status'}, 'DONE', 'good passphrase');
@@ -78,7 +82,9 @@ diag 'only signing. missing passphrase' if $ENV{'TEST_VERBOSE'};
     ok( $res{'exit_code'}, "couldn't sign without passphrase");
     ok( $res{'error'} || $res{'logger'}, "error is here" );
 
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'}
+    );
     is( scalar @status, 1, 'one record');
     is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
     is( $status[0]->{'Status'}, 'MISSING', 'missing passphrase');
@@ -95,7 +101,9 @@ diag 'only signing. wrong passphrase' if $ENV{'TEST_VERBOSE'};
     ok( $res{'exit_code'}, "couldn't sign with bad passphrase");
     ok( $res{'error'} || $res{'logger'}, "error is here" );
 
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'}
+    );
     is( scalar @status, 1, 'one record');
     is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
     is( $status[0]->{'Status'}, 'BAD', 'wrong passphrase');
@@ -113,7 +121,9 @@ diag 'encryption only' if $ENV{'TEST_VERBOSE'};
     ok( !$res{'exit_code'}, "successful encryption" );
     ok( !$res{'logger'}, "no records in logger" );
 
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'}
+    );
     is( scalar @status, 1, 'one record');
     is( $status[0]->{'Operation'}, 'Encrypt', 'operation is correct');
     is( $status[0]->{'Status'}, 'DONE', 'done');
@@ -139,7 +149,9 @@ diag 'encryption only, bad recipient' if $ENV{'TEST_VERBOSE'};
     ok( $res{'exit_code'}, 'no way to encrypt without keys of recipients');
     ok( $res{'logger'}, "errors are in logger" );
 
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'}
+    );
     is( scalar @status, 1, 'one record');
     is( $status[0]->{'Keyword'}, 'INV_RECP', 'invalid recipient');
 }
@@ -156,7 +168,9 @@ diag 'encryption and signing with combined method' if $ENV{'TEST_VERBOSE'};
     ok( !$res{'exit_code'}, "successful encryption with signing" );
     ok( !$res{'logger'}, "no records in logger" );
 
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res{'status'} );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res{'Protocol'}, Status => $res{'status'}
+    );
     is( scalar @status, 3, 'three records: passphrase, sign and encrypt');
     is( $status[0]->{'Operation'}, 'PassphraseCheck', 'operation is correct');
     is( $status[0]->{'Status'}, 'DONE', 'done');
@@ -300,8 +314,10 @@ diag 'verify inline and in attachment signatures' if $ENV{'TEST_VERBOSE'};
     is( $parts[0]->{'Data'}, $entity->parts(1), "data in second part" );
     is( $parts[0]->{'Signature'}, $entity->parts(2), "file's signature in third part" );
 
-    my @res = RT::Crypt::GnuPG::VerifyDecrypt( Entity => $entity );
-    my @status = RT::Crypt::GnuPG::ParseStatus( $res[0]->{'status'} );
+    my @res = RT::Crypt->VerifyDecrypt( Entity => $entity );
+    my @status = RT::Crypt->ParseStatus(
+        Protocol => $res[0]{'Protocol'}, Status => $res[0]{'status'}
+    );
     is( scalar @status, 1, 'one record');
     is( $status[0]->{'Operation'}, 'Verify', 'operation is correct');
     is( $status[0]->{'Status'}, 'DONE', 'good passphrase');

commit 03bf019da2c04ea1319c01274736e5e50e8405ca
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 12:29:32 2010 +0300

    partialy convert ShowGnuPGStatus over new API
    
    re-verification still needs more love

diff --git a/share/html/Ticket/Elements/ShowGnuPGStatus b/share/html/Ticket/Elements/ShowGnuPGStatus
index 08814aa..dc1178a 100644
--- a/share/html/Ticket/Elements/ShowGnuPGStatus
+++ b/share/html/Ticket/Elements/ShowGnuPGStatus
@@ -62,10 +62,12 @@ $Reverify     => 1
 my @runs;
 my $needs_unsigned_warning = $WarnUnsigned;
 
+my @protocols = RT::Crypt->Protocols;
+my $re_protocols = join '|', map "\Q$_\E", @protocols;
+
 foreach ( $Attachment->SplitHeaders ) {
-    if ( s/^X-RT-GnuPG-Status:\s*//i ) {
-        require RT::Crypt::GnuPG;
-        push @runs, [ RT::Crypt::GnuPG::ParseStatus( $_ ) ];
+    if ( s/^X-RT-($re_protocols)-Status:\s*//io ) {
+        push @runs, [ RT::Crypt->ParseStatus( Protocol => "$1", Status => $_ ) ];
     }
 
     $needs_unsigned_warning = 0 if /^X-RT-Incoming-Signature:/;
@@ -103,14 +105,14 @@ my $reverify_cb = sub {
         return (0, "Couldn't parse content of attachment #". $original->id);
     }
 
-    use RT::Interface::Email::Auth::GnuPG;
-    my ($status, @res) = RT::Interface::Email::Auth::GnuPG::VerifyDecrypt( Entity => $entity );
+    use RT::Interface::Email::Auth::Crypt;
+    my ($status, @res) = RT::Interface::Email::Auth::Crypt::VerifyDecrypt( Entity => $entity );
     if ( $status && !@res ) {
         # imposible in this situation
         return (0, "Content of attachment #". $original->id ." is not signed and/or encrypted");
     }
     elsif ( @res ) {
-        require RT::Crypt::GnuPG;
+        require RT::Crypt;
 
         $top->DelHeader('X-RT-GnuPG-Status');
         $top->AddHeader(map { ('X-RT-GnuPG-Status' => $_->{'status'} ) } @res);

commit bc06474e161a12abde75a597ffc0d25686f6ce0b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 12:38:10 2010 +0300

    more on ::ParseStatus ->  ->ParseStatus

diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
index 9303fb5..a5bdd23 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -129,7 +129,7 @@ if ( $self->{'Sign'} ) {
         ? ( $QueueObj->CommentAddress || RT->Config->Get('CommentAddress') )
         : ( $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') );
 
-    unless ( RT::Crypt::GnuPG::DrySign( $address ) ) {
+    unless ( RT::Crypt::GnuPG->DrySign( $address ) ) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
     } else {
diff --git a/share/html/Ticket/Elements/ShowGnuPGStatus b/share/html/Ticket/Elements/ShowGnuPGStatus
index dc1178a..fa78bc8 100644
--- a/share/html/Ticket/Elements/ShowGnuPGStatus
+++ b/share/html/Ticket/Elements/ShowGnuPGStatus
@@ -119,7 +119,10 @@ my $reverify_cb = sub {
         $top->SetHeader('X-RT-Privacy' => 'PGP' );
         $top->DelHeader('X-RT-Incoming-Signature');
 
-        my @status = RT::Crypt::GnuPG::ParseStatus( $res[0]->{'status'} );
+        my @status = RT::Crypt->ParseStatus(
+            Protocol => $res[0]{'Protocol'},
+            Status => $res[0]{'status'},
+        );
         for ( @status ) {
             if ( $_->{'Operation'} eq 'Verify' && $_->{'Status'} eq 'DONE' ) {
                 $top->AddHeader( 'X-RT-Incoming-Signature' => $_->{'UserString'} );

commit f4b4e6e848a394ea0afba720ac6ae49c004ce7db
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:48:38 2010 +0300

    syntax error fixes

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index fdfc3d6..69e9eed 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -406,6 +406,7 @@ sub SignEncrypt {
 }
 
 sub SignEncryptRFC3156 {
+    my $self = shift;
     my %args = (
         Entity => undef,
 
@@ -1010,14 +1011,14 @@ sub FindScatteredParts {
     foreach my $part ( @parts ) {
         next if $args{'Skip'}{$part};
 
-        my $type = $self->_CheckIfProtectedInline( $entity );
+        my $type = $self->_CheckIfProtectedInline( $part );
         next unless $type;
 
         $args{'Skip'}{$part} = 1;
         push @res, {
             Type      => $type,
             Format    => 'Inline',
-            Data      => $entity,
+            Data      => $part,
         };
     }
 
@@ -1085,7 +1086,7 @@ sub VerifyDecrypt {
         if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
             my $method = $args{'AddStatus'} ? 'add' : 'set';
             $status_on->head->$method(
-                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
+                'X-RT-GnuPG-Status' => $res{'status'}
             );
         }
     } elsif ( $item->{'Type'} eq 'encrypted' ) {
@@ -1105,13 +1106,13 @@ sub VerifyDecrypt {
         if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
             my $method = $args{'AddStatus'} ? 'add' : 'set';
             $status_on->head->$method(
-                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
+                'X-RT-GnuPG-Status' => $res{'status'}
             );
         }
     } else {
         die "Unknow type '". $item->{'Type'} ."' of protected item";
     }
-    return @res;
+    return %res;
 }
 
 sub VerifyInline { return (shift)->DecryptInline( @_ ) }
@@ -1999,6 +2000,7 @@ also listed.
 =cut
 
 sub GetKeysForEncryption {
+    my $self = shift;
     my $key_id = shift;
     my %res = $self->GetKeysInfo( $key_id, 'public', @_ );
     return %res if $res{'exit_code'};
@@ -2020,18 +2022,20 @@ sub GetKeysForEncryption {
 }
 
 sub GetKeysForSigning {
+    my $self = shift;
     my $key_id = shift;
     return $self->GetKeysInfo( $key_id, 'private', @_ );
 }
 
 sub CheckRecipients {
+    my $self = shift;
     my @recipients = (@_);
 
     my ($status, @issues) = (1, ());
 
     my %seen;
     foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
-        my %res = GetKeysForEncryption( $address );
+        my %res = $self->GetKeysForEncryption( $address );
         if ( $res{'info'} && @{ $res{'info'} } == 1 && $res{'info'}[0]{'TrustLevel'} > 0 ) {
             # good, one suitable and trusted key 
             next;
@@ -2090,14 +2094,15 @@ sub CheckRecipients {
 }
 
 sub GetPublicKeyInfo {
-    return GetKeyInfo( shift, 'public', @_ );
+    return (shift)->GetKeyInfo( shift, 'public', @_ );
 }
 
 sub GetPrivateKeyInfo {
-    return GetKeyInfo( shift, 'private', @_ );
+    return (shift)->GetKeyInfo( shift, 'private', @_ );
 }
 
 sub GetKeyInfo {
+    my $self = shift;
     my %res = $self->GetKeysInfo(@_);
     $res{'info'} = $res{'info'}->[0];
     return %res;

commit 15af9a14482d4c0f15c331b1d9669383b8f6f8ba
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:50:10 2010 +0300

    RT::Crypt->LoadImplementation method

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 8b8a6e2..aef988a 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -17,6 +17,15 @@ sub EnabledOnIncoming {
     return 'GnuPG', 'SMIME';
 }
 
+{ my %cache;
+sub LoadImplementation {
+    my $class = 'RT::Crypt::'. $_[1];
+    return $class if $cache{ $class }++;
+
+    eval "require $class; 1" or do { require Carp; Carp::confess( $@ ) };
+    return $class;
+} }
+
 # encryption and signatures can be nested one over another, for example:
 # GPG inline signed text can be signed with SMIME
 
@@ -38,7 +47,7 @@ sub FindProtectedParts {
         : $self->EnabledOnIncoming;
 
     foreach my $protocol ( @protocols ) {
-        my $class = 'RT::Crypt::'. $protocol;
+        my $class = $self->LoadImplementation( $protocol );
         my %info = $class->CheckIfProtected( Entity => $entity );
         next unless keys %info;
 
@@ -93,7 +102,7 @@ sub SignEncrypt {
     }
 
     my $protocol = delete $args{'Protocol'} || 'GnuPG';
-    my $class = 'RT::Crypt::'. $protocol;
+    my $class = $self->LoadImplementation( $protocol );
 
     my %res = $class->SignEncrypt( %args );
     $res{'Protocol'} = $protocol;
@@ -115,8 +124,8 @@ sub VerifyDecrypt {
     my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} );
     foreach my $protected ( @protected ) {
         my $protocol = $protected->{'Protocol'};
-        my $class = 'RT::Crypt::'. $protocol;
-        my %res = $class->VerifyDecrypt( %args, %$protected );
+        my $class = $self->LoadImplementation( $protocol );
+        my %res = $class->VerifyDecrypt( %args, Info => $protected );
         $res{'Protocol'} = $protocol;
         push @res, \%res;
     }
@@ -130,8 +139,7 @@ sub ParseStatus {
         Status   => '',
         @_
     );
-    my $class = 'RT::Crypt::'. $args{'Protocol'};
-    return $class->ParseStatus( $args{'Status'} );
+    return $self->LoadImplementation( $args{'Protocol'} )->ParseStatus( $args{'Status'} );
 }
 
 1;

commit f7704018ce857180d7e252cfbcbda76a5a02e76b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:50:52 2010 +0300

    don't recurse into multipart/{signed,encrypted} messages

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index aef988a..9f5eb77 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -56,6 +56,15 @@ sub FindProtectedParts {
         return \%info;
     }
 
+    if ( $entity->effective_type =~ /^multipart\/(?:signed|encrypted)/ ) {
+        # if no module claimed that it supports these types then
+        # we don't dive in and check sub-parts
+        $args{'Skip'}{ $entity } = 1;
+        return ();
+    }
+
+    my @res;
+
     # not protected itself, look inside
     push @res, $self->FindProtectedParts(
         %args, Entity => $_, Scattered => 0,

commit 48c402060a72559cd31f25e05c71fad39734ba16
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:52:03 2010 +0300

    pass parents into FindScatteredParts

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 9f5eb77..f408b36 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -71,16 +71,23 @@ sub FindProtectedParts {
     ) foreach grep !$args{'Skip'}{$_}, $entity->parts;
 
     if ( $args{'Scattered'} ) {
+        my %parent;
         my $filter; $filter = sub {
-            return grep !$args{'Skip'}{$_},
-                $_[0]->is_multipart ? () : $_[0],
-                map $filter->($_), grep !$args{'Skip'}{$_},
+            $parent{$_[0]} = $_[1];
+            return
+                grep !$args{'Skip'}{$_},
+                $_[0]->is_multipart ? () : $_,
+                map $filter->($_, $_[0]), grep !$args{'Skip'}{$_},
                     $_[0]->parts;
         };
         my @parts = $filter->($entity);
         foreach my $protocol ( @protocols ) {
-            my $class = 'RT::Crypt::'. $protocol;
-            my @list = $class->FindScatteredParts( Parts => \@parts, Skip => $args{'Skip'} );
+            my $class = $self->LoadImplementation( $protocol );
+            my @list = $class->FindScatteredParts(
+                Parts   => \@parts,
+                Parents => \%parent,
+                Skip    => $args{'Skip'}
+            );
             next unless @list;
 
             push @res, @list;

commit da06ec68d5755614f5a768910f56177059bd09d4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:52:38 2010 +0300

    set Protocol when we find scattered part(s)

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index f408b36..916b2de 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -90,6 +90,7 @@ sub FindProtectedParts {
             );
             next unless @list;
 
+            $_->{'Protocol'} = $protocol foreach @list;
             push @res, @list;
             @parts = grep !$args{'Skip'}{$_}, @parts;
         }

commit e7aa1b4d08dd1a0f537e44647acd8227e92a13b4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:54:54 2010 +0300

    handle multipart/{singned,encrypted} without protocol set

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 69e9eed..63b3e57 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -896,11 +896,32 @@ sub CheckIfProtected {
 
     my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
     unless ( $protocol ) {
-        $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
-        return ();
+        # if protocol is not set then we can check second part for PGP message
+        $RT::Logger->error( "Entity is '$type', but has no protocol defined. Checking for PGP part" );
+        my $protected = $self->_CheckIfProtectedInline( $entity->parts(1), 1 );
+        return () unless $protected;
+
+        if ( $protected eq 'signature' ) {
+            $RT::Logger->debug("Found part signed according to RFC3156");
+            return (
+                Type      => 'signed',
+                Format    => 'RFC3156',
+                Top       => $entity,
+                Data      => $entity->parts(0),
+                Signature => $entity->parts(1),
+            );
+        } else {
+            $RT::Logger->debug("Found part encrypted according to RFC3156");
+            return (
+                Type   => 'encrypted',
+                Format => 'RFC3156',
+                Top    => $entity,
+                Data   => $entity->parts(1),
+                Info   => $entity->parts(0),
+            );
+        }
     }
-
-    if ( $type eq 'multipart/encrypted' ) {
+    elsif ( $type eq 'multipart/encrypted' ) {
         unless ( $protocol eq 'application/pgp-encrypted' ) {
             $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
             return ();
@@ -1028,6 +1049,7 @@ sub FindScatteredParts {
 sub _CheckIfProtectedInline {
     my $self = shift;
     my $entity = shift;
+    my $check_for_signature = shift || 0;
 
     my $io = $entity->open('r');
     unless ( $io ) {
@@ -1035,8 +1057,12 @@ sub _CheckIfProtectedInline {
         return '';
     }
     while ( defined($_ = $io->getline) ) {
-        next unless /^-----BEGIN PGP (SIGNED )?MESSAGE-----/;
-        return $1? 'signed': 'encrypted';
+        if ( /^-----BEGIN PGP (SIGNED )?MESSAGE-----/ ) {
+            return $1? 'signed': 'encrypted';
+        }
+        elsif ( $check_for_signature && !/^-----BEGIN PGP SIGNATURE-----/ ) {
+            return 'signature';
+        }
     }
     $io->close;
     return '';

commit 5b364d637a781aaf12b1c006fc6d85d6bfeb145d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:55:56 2010 +0300

    without parents we can not complete detection of some parts

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 63b3e57..a29ae9e 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1003,7 +1003,7 @@ sub FindScatteredParts {
             push @res, {
                 Type      => 'signed',
                 Format    => 'Attachment',
-                Top       => $entity,
+                Top       => $args{'Parents'}{$data_part_in},
                 Data      => $data_part_in,
                 Signature => $sig_part,
             };
@@ -1023,7 +1023,7 @@ sub FindScatteredParts {
         push @res, {
             Type    => 'encrypted',
             Format  => 'Attachment',
-            Top     => $entity,
+            Top     => $args{'Parents'}{$part},
             Data    => $part,
         };
     }

commit c3412629e3920b2240a1e5eaabc403c1f10e9156
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 21 22:57:38 2010 +0300

    adjust tests, now we support additional detection

diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
index b05a903..11663fd 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -3,7 +3,7 @@
 use strict;
 use warnings;
 
-use RT::Test nodata => 1, tests => 92;
+use RT::Test nodata => 1, tests => 95;
 plan skip_all => 'GnuPG required.'
     unless eval 'use GnuPG::Interface; 1';
 plan skip_all => 'gpg executable is required.'
@@ -18,7 +18,7 @@ my $homedir = RT::Test::get_abs_relocatable_dir(File::Spec->updir(),
 
 mkdir $homedir;
 
-use_ok('RT::Crypt::GnuPG');
+use_ok('RT::Crypt');
 use_ok('MIME::Entity');
 
 RT->Config->Set( 'GnuPG',
@@ -246,7 +246,10 @@ diag 'wrong signed/encrypted parts: no protocol' if $ENV{'TEST_VERBOSE'};
     $entity->head->mime_attr( 'Content-Type.protocol' => undef );
 
     my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
-    is( scalar @parts, 0, 'no protected parts' );
+    is( scalar @parts, 1, 'one protected part' );
+    is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
+    is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );
+    is( $parts[0]->{'Top'}, $entity, "it's the same entity" );
 }
 
 diag 'wrong signed/encrypted parts: not enought parts' if $ENV{'TEST_VERBOSE'};

commit 960b93ed7673f09cbd2f492ec44330690239ecbd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 22 16:03:20 2010 +0300

    add option --enable-smime

diff --git a/configure.ac b/configure.ac
index 0a5c0f8..dd52632 100755
--- a/configure.ac
+++ b/configure.ac
@@ -314,6 +314,20 @@ fi
 AC_SUBST(RT_GPG)
 
 
+dnl RT's SMIME support
+AC_CHECK_PROG([RT_SMIME], [openssl], "yes", "no")
+AC_ARG_ENABLE(gpg,
+            AC_HELP_STRING([--enable-smime],
+                           [Turns on Secure MIME (SMIME) support]),
+            RT_SMIME=$enableval)
+if test "$RT_SMIME" = yes; then
+        RT_SMIME="1"
+else
+        RT_SMIME="0"
+fi
+AC_SUBST(RT_SMIME)
+
+
 dnl This section maps the variable names this script 'natively' generates
 dnl to their existing names. They should be removed from here as the .in
 dnl files are changed to use the new names.

commit 1f4665655251ae56d3d8f5885903922e5c24789f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 22 19:08:41 2010 +0300

    bring new config options required for SMIM and genric interface

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 9bd809a..ea1b7ac 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -693,37 +693,83 @@ Set($DashboardSubject, '%s Dashboard: %s');
 
 =back
 
-=head1 GnuPG Configuration
+=head1 Cryptography
+
+Description of cryptography capabilities can be found by running the command
+`perldoc L<RT::Crypt>` (or `perldoc lib/RT/Crypt.pm` from RT install
+directory). At this momement support for GnuPG (PGP) and SMIME security
+protocols are supported.
+
+=over 4
+
+=item C<%Crypt>
+
+Options generic for all cryptography protocols.
+
+Set C<ProcessIncomming> to list of security protocols that should be
+analyzed in incomming emails. Note that C<Auth::Crypt> mail plugin
+should be added anyway to the C<@MailPlugins> option.
+
+Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
+emails encrypted for key RT doesn't have and can not decrypt.
+
+Set C<RejectOnBadData> to false if you don't want to reject letters
+with incorrect data.
+
+=back
+
+=cut
+
+Set( %Crypt,
+    ProcessIncomming          => undef, # ['GnuPG', 'SMIME']
+
+    RejectOnMissingPrivateKey => 1,
+    RejectOnBadData           => 1,
+);
+
+=head2 SMIME Configuration
+
+A full description of the SMIME integration can be found 
+by running the command `perldoc L<RT::Crypt::SMIME>`  (or `perldoc
+lib/RT/Crypt/SMIME.pm` from your RT install directory).
+
+=over 4
+
+=item C<%SMIME>
+
+=back
+
+=cut
+
+Set( %SMIME,
+    Enable => @RT_SMIME@,
+);
+
+=head2 GnuPG Configuration
 
 A full description of the (somewhat extensive) GnuPG integration can be found 
 by running the command `perldoc L<RT::Crypt::GnuPG>`  (or `perldoc
-        lib/RT/Crypt/GnuPG.pm` from your RT install directory).
+lib/RT/Crypt/GnuPG.pm` from your RT install directory).
 
 =over 4
 
 =item C<%GnuPG>
 
+Set C<Enable> to false or true value to disable or enable GnuPG interfaces
+for encryptng and signing outgoing messages.
+
 Set C<OutgoingMessagesFormat> to 'inline' to use inline encryption and
 signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
 
 If you want to allow people to encrypt attachments inside the DB then
-set C<AllowEncryptDataInDB> to true
-
-Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
-emails encrypted for key RT doesn't have and can not decrypt.
-
-Set C<RejectOnBadData> to false if you don't want to reject letters
-with incorrect GnuPG data.
+set C<AllowEncryptDataInDB> to true.
 
 =cut
 
 Set( %GnuPG,
-    Enable => @RT_GPG@,
+    Enable                 => @RT_GPG@,
     OutgoingMessagesFormat => 'RFC', # Inline
     AllowEncryptDataInDB   => 0,
-
-    RejectOnMissingPrivateKey => 1,
-    RejectOnBadData           => 1,
 );
 
 =item C<%GnuPGOptions>

commit 4e0897cf80bfdbd4a2c76ab220906686a7d92f32
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 22 19:14:50 2010 +0300

    check old and new crypt related config options

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 9e28cf2..382bd26 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -437,8 +437,22 @@ our %META = (
         },
     },
     Plugins      => { Type => 'ARRAY' },
+    Crypt        => {
+        Type => 'HASH',
+        PostLoadCheck => sub {
+            my $self = shift;
+            my $opt = $self->Get('Crypt');
+            unless ( $opt->{'ProcessIncomming'} && @{ $opt->{'ProcessIncomming'} } ) {
+                require RT::Crypt;
+                my @enabled = grep $self->Get($_)->{'Enabled'}, RT::Crypt->Protocols;
+                $opt->{'ProcessIncomming'} = \@enabled;
+            }
+        },
+    },
+    SMIME        => { Type => 'HASH' },
     GnuPG        => { Type => 'HASH' },
-    GnuPGOptions => { Type => 'HASH',
+    GnuPGOptions => {
+        Type => 'HASH',
         PostLoadCheck => sub {
             my $self = shift;
             my $gpg = $self->Get('GnuPG');
@@ -453,7 +467,6 @@ our %META = (
                 return;
             }
 
-
             require RT::Crypt::GnuPG;
             unless (RT::Crypt::GnuPG->Probe()) {
                 $RT::Logger->debug(
@@ -461,6 +474,13 @@ our %META = (
                     " PGP support has been disabled");
                 $gpg->{'Enable'} = 0;
             }
+
+            if ( grep exists $gpg->{$_}, qw(RejectOnMissingPrivateKey RejectOnBadData) ) {
+                $RT::Logger->error(
+                    "RejectOnMissingPrivateKey and RejectOnBadData GnuPG options"
+                    ." are now generic to GnuPG and SMIME."
+                );
+            }
         }
     },
 );

commit b57a1d294b0f442124af529a96fdc73c760797bc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 22 19:23:04 2010 +0300

    base class for cryptography protocols

diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
new file mode 100644
index 0000000..b9a8ddd
--- /dev/null
+++ b/lib/RT/Crypt/Base.pm
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+
+package RT::Crypt::Base;
+
+sub SignEncrypt {
+    return (exit_code => 1, status => []);    
+}
+
+sub VerifyDecrypt {
+    return (exit_code => 1, status => []);
+}
+
+sub CheckIfProtected { return () }
+
+sub FindScatteredParts { return () }
+
+1;

commit e732e3218b5c0a7181a2f833b3d7a98777de7382
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 20:48:47 2010 +0300

    change cryptography config
    
    * Crypt->{'Incomming'} and Crypt->{'Outgoing'}
    * set Crypt->{'Enable'} in post load check if any protocol is enabled
      don't announce this

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index ea1b7ac..d43b26b 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -721,7 +721,8 @@ with incorrect data.
 =cut
 
 Set( %Crypt,
-    ProcessIncomming          => undef, # ['GnuPG', 'SMIME']
+    Incomming                 => undef, # ['GnuPG', 'SMIME']
+    Outgoing                  => 'GnuPG', # 'SMIME'
 
     RejectOnMissingPrivateKey => 1,
     RejectOnBadData           => 1,
diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 382bd26..b8bdbe1 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -441,11 +441,16 @@ our %META = (
         Type => 'HASH',
         PostLoadCheck => sub {
             my $self = shift;
+            require RT::Crypt;
+
             my $opt = $self->Get('Crypt');
-            unless ( $opt->{'ProcessIncomming'} && @{ $opt->{'ProcessIncomming'} } ) {
-                require RT::Crypt;
-                my @enabled = grep $self->Get($_)->{'Enabled'}, RT::Crypt->Protocols;
-                $opt->{'ProcessIncomming'} = \@enabled;
+            my @enabled = RT::Crypt->EnabledProtocols;
+            $opt->{'Enable'} = scalar @enabled;;
+            unless ( $opt->{'Incomming'} && @{ $opt->{'Incomming'} } ) {
+                $opt->{'Incomming'} = \@enabled;
+            }
+            unless ( $opt->{'Outgoing'} ) {
+                $opt->{'Outgoing'} = $enabled[0];
             }
         },
     },
@@ -457,6 +462,7 @@ our %META = (
             my $self = shift;
             my $gpg = $self->Get('GnuPG');
             return unless $gpg->{'Enable'};
+
             my $gpgopts = $self->Get('GnuPGOptions');
             unless (-d $gpgopts->{homedir}  && -r _ ) { # no homedir, no gpg
                 $RT::Logger->debug(

commit 63f9cb32bee74a7b256c764763f767ad37cbb949
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 20:52:08 2010 +0300

    minor GnuPG changes
    
    * base class
    * turn another function into method

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index a29ae9e..3d22d5c 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -50,6 +50,7 @@ use strict;
 use warnings;
 
 package RT::Crypt::GnuPG;
+use base 'RT::Crypt::Base';
 
 use IO::Handle;
 use GnuPG::Interface;
@@ -2037,7 +2038,7 @@ sub GetKeysForEncryption {
         next if $key->{'Capabilities'} =~ /D/;
         # skip keys not suitable for encryption
         next unless $key->{'Capabilities'} =~ /e/i;
-        # skip disabled, expired, revoke and keys with no trust,
+        # skip disabled, expired, revoked and keys with no trust,
         # but leave keys with unknown trust level
         next if $key->{'TrustLevel'} < 0;
 
@@ -2186,12 +2187,13 @@ sub GetKeysInfo {
         return %res;
     }
 
-    @info = ParseKeysInfo( @info );
+    @info = $self->ParseKeysInfo( @info );
     $res{'info'} = \@info;
     return %res;
 }
 
 sub ParseKeysInfo {
+    my $self = shift;
     my @lines = @_;
 
     my %gpg_opt = RT->Config->Get('GnuPGOptions');

commit 332921c1e423da0846698e9bc85f67e39588da21
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 20:55:41 2010 +0300

    config based methods in RT::Crypt
    
    EnabledProtocols, UseForOutgoing and EnabledOnIncomming

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 916b2de..a3a4a85 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -13,8 +13,17 @@ sub Protocols {
     return @PROTOCOLS;
 }
 
+sub EnabledProtocols {
+    my $self = shift;
+    return grep RT->Config->Get($_)->{'Enable'}, $self->Protocols;
+}
+
+sub UseForOutgoing {
+    return RT->Config->Get('Crypt')->{'Outgoing'};
+}
+
 sub EnabledOnIncoming {
-    return 'GnuPG', 'SMIME';
+    return @{ scalar RT->Config->Get('Crypt')->{'Incomming'} };
 }
 
 { my %cache;

commit 03a71dd26bc6623358243030e54836678f1e039b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 21:00:57 2010 +0300

    return asap if there is no parts for scattered search

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index a3a4a85..672dad1 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -90,6 +90,8 @@ sub FindProtectedParts {
                     $_[0]->parts;
         };
         my @parts = $filter->($entity);
+        return @res unless @parts;
+
         foreach my $protocol ( @protocols ) {
             my $class = $self->LoadImplementation( $protocol );
             my @list = $class->FindScatteredParts(

commit 112273cc2febfddb0d9c57fe67397a7eb604ff11
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 21:02:08 2010 +0300

    generic crypt interface for keys information retrieval

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 672dad1..e6101a4 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -170,4 +170,29 @@ sub ParseStatus {
     return $self->LoadImplementation( $args{'Protocol'} )->ParseStatus( $args{'Status'} );
 }
 
+
+sub GetPublicKeyInfo {
+    return (shift)->GetKeyInfo( @_, Type => 'public' );
+}
+
+sub GetPrivateKeyInfo {
+    return (shift)->GetKeyInfo( @_, Type => 'private' );
+}
+
+sub GetKeyInfo {
+    my $self = shift;
+    my %res = $self->GetKeysInfo( @_ );
+    $res{'info'} = $res{'info'}->[0];
+    return %res;
+}
+
+sub GetKeysInfo {
+    my $self = shift;
+    my %args = ( Protocol => undef, @_ );
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my %res = $self->LoadImplementation( $protocol )->GetKeysInfo( @_ );
+    $res{'Protocol'} = $protocol;
+    return %res;
+}
+
 1;

commit c8a4bf557fd676f7374b5c37ee07d01577f420df
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 21:05:17 2010 +0300

    clean GnuPG's keys info implementation

diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
index b9a8ddd..940d907 100644
--- a/lib/RT/Crypt/Base.pm
+++ b/lib/RT/Crypt/Base.pm
@@ -15,4 +15,11 @@ sub CheckIfProtected { return () }
 
 sub FindScatteredParts { return () }
 
+sub GetKeysInfo {
+    return (
+        exit_code => 1,
+        message => 'Not implemented',
+    );
+}
+
 1;
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 3d22d5c..1aa1a2f 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2029,7 +2029,7 @@ also listed.
 sub GetKeysForEncryption {
     my $self = shift;
     my $key_id = shift;
-    my %res = $self->GetKeysInfo( $key_id, 'public', @_ );
+    my %res = $self->GetKeysInfo( Key => $key_id, Type => 'public', @_ );
     return %res if $res{'exit_code'};
     return %res unless $res{'info'};
 
@@ -2051,7 +2051,7 @@ sub GetKeysForEncryption {
 sub GetKeysForSigning {
     my $self = shift;
     my $key_id = shift;
-    return $self->GetKeysInfo( $key_id, 'private', @_ );
+    return $self->GetKeysInfo( Key => $key_id, Type => 'private', @_ );
 }
 
 sub CheckRecipients {
@@ -2120,29 +2120,19 @@ sub CheckRecipients {
     return ($status, @issues);
 }
 
-sub GetPublicKeyInfo {
-    return (shift)->GetKeyInfo( shift, 'public', @_ );
-}
-
-sub GetPrivateKeyInfo {
-    return (shift)->GetKeyInfo( shift, 'private', @_ );
-}
-
-sub GetKeyInfo {
-    my $self = shift;
-    my %res = $self->GetKeysInfo(@_);
-    $res{'info'} = $res{'info'}->[0];
-    return %res;
-}
-
 sub GetKeysInfo {
     my $self = shift;
-    my $email = shift;
-    my $type = shift || 'public';
-    my $force = shift;
+    my %args = (
+        Key   => undef,
+        Type  => 'public',
+        Force => 0,
+        @_
+    );
 
+    my $email = $args{'Key'};
+    my $type = $args{'Type'};
     unless ( $email ) {
-        return (exit_code => 0) unless $force;
+        return (exit_code => 0) unless $args{'Force'};
     }
 
     my $gnupg = new GnuPG::Interface;

commit f88a0c2f0760a412851adf3e4d4f22328185a746
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 21:08:44 2010 +0300

    in Admin interface show keys info for every enabled protocol

diff --git a/share/html/Admin/Elements/ShowKeyInfo b/share/html/Admin/Elements/ShowKeyInfo
index 35d38a7..4f99fb5 100644
--- a/share/html/Admin/Elements/ShowKeyInfo
+++ b/share/html/Admin/Elements/ShowKeyInfo
@@ -45,13 +45,22 @@
 %# those contributions and any derivatives thereof.
 %# 
 %# END BPS TAGGED BLOCK }}}
-<&| /Widgets/TitleBox, title => $title &>
-% if ( $res{'exit_code'} || !keys %{ $res{'info'} } ) {
-<% loc('No keys for this address') %>
+<&| /Widgets/TitleBox, title => $title &><table>
+% while ( my $protocol = shift @protocols ) {
+% my %res = RT::Crypt->GetKeyInfo(
+%     Protocol => $protocol,
+%     Key      => $EmailAddress,
+%     Type     => $Type,
+% );
+% if ( $res{'exit_code'} ) {
+<tr><th colspan="2"><% loc("Couldn't get [_1] keys information", $protocol) %></th></tr>
+% } elsif ( !keys %{ $res{'info'} } ) {
+<tr><th colspan="2"><% loc('No [_1] keys for this address', $protocol) %></th></tr>
 % } else {
-<table>
 
-% unless ( $Type eq 'private' ) {
+<tr><th colspan="2"><% loc("[_1] key '[_2]'", $protocol, $res{'info'}{'id'} || $res{'info'}{'Fingerprint'} ) %></th></tr>
+
+% if ( $Type ne 'private' && $res{'info'}{'Trust'} ) {
 <tr><th><% loc('Trust') %>:</th>  <td><% loc( $res{'info'}{'Trust'} ) %></td></tr>
 % }
 
@@ -69,23 +78,27 @@
 </td></tr>
 % }
 
-</table>
 % }
-</&>
+
+% if ( @protocols ) {
+<tr><th colspan="2">&nbsp;</th></tr>
+% }
+
+% }
+</table></&>
 
 <%ARGS>
 $EmailAddress
 $Type => 'public'
 </%ARGS>
 <%INIT>
-require RT::Crypt::GnuPG;
-my %res = RT::Crypt::GnuPG::GetKeyInfo( $EmailAddress, $Type );
+my @protocols = RT::Crypt->EnabledProtocols;
+return unless @protocols;
 
 my $title;
 unless ( $Type eq 'private' ) {
-    $title = loc('GnuPG public key(s) for [_1]', $EmailAddress);
+    $title = loc('Public key(s) for [_2]', $EmailAddress);
 } else {
-    $title = loc('GnuPG private key(s) for [_1]', $EmailAddress);
+    $title = loc('Private key(s) for [_2]', $EmailAddress);
 }
-
 </%INIT>

commit 35a8bed386494f977033b97a2998ff8ce4d1acb4
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 21:11:31 2010 +0300

    variouse fixes in web ui regarding crypto changes

diff --git a/share/html/Admin/Queues/Modify.html b/share/html/Admin/Queues/Modify.html
index c6ffe17..f0255f4 100755
--- a/share/html/Admin/Queues/Modify.html
+++ b/share/html/Admin/Queues/Modify.html
@@ -107,7 +107,7 @@
 </td></tr>
 % }
 
-% if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+% if ( RT->Config->Get('Crypt')->{'Enable'} ) {
 <tr><td align="right"><input type="checkbox" class="checkbox" name="Sign" value="1" <% $QueueObj->Sign? 'checked="checked"': '' |n%> /></td>
 <td><&|/l&>Sign by default</&></td>
 <td align="right"><input type="checkbox" class="checkbox" name="Encrypt" value="1" <% $QueueObj->Encrypt? 'checked="checked"': '' |n%> /></td>
@@ -120,7 +120,7 @@
 % $m->callback( %ARGS, QueueObj => $QueueObj, results => \@results );
 </td></tr>
 
-% if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+% if ( RT->Config->Get('Crypt')->{'Enable'} ) {
 % if ( my $email = $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') ) {
 <tr><td colspan="4"><& /Admin/Elements/ShowKeyInfo, Type => 'private', EmailAddress => $email &></td></tr>
 % }
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
index a5bdd23..38bca7a 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -133,7 +133,7 @@ if ( $self->{'Sign'} ) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
     } else {
-        RT::Crypt::GnuPG::UseKeyForSigning( $self->{'SignUsing'} )
+        RT::Crypt::GnuPG->UseKeyForSigning( $self->{'SignUsing'} )
             if $self->{'SignUsing'};
     }
 }
@@ -168,13 +168,13 @@ if ( $self->{'Encrypt'} ) {
     my %seen;
     @recipients = grep !$seen{ lc $_ }++, @recipients;
 
-    RT::Crypt::GnuPG::UseKeyForEncryption(
+    RT::Crypt::GnuPG->UseKeyForEncryption(
         map { (/^UseKey-(.*)$/)[0] => $self->{ $_ } }
         grep $self->{ $_ } && /^UseKey-/,
         keys %$self
     );
 
-    my ($status, @issues) = RT::Crypt::GnuPG::CheckRecipients( @recipients );
+    my ($status, @issues) = RT::Crypt::GnuPG->CheckRecipients( @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 c1f9e31..2162afa 100644
--- a/share/html/Elements/RT__Ticket/ColumnMap
+++ b/share/html/Elements/RT__Ticket/ColumnMap
@@ -259,7 +259,7 @@ $COLUMN_MAP = {
             my @requestors = $t->Requestors->MemberEmailAddresses;
             for my $email (@requestors)
             {
-                my %key = RT::Crypt::GnuPG::GetKeyInfo($email);
+                my %key = RT::Crypt->GetKeyInfo( Key => $email );
                 if (!defined $key{'info'}) {
                     $email .= loc(" (no pubkey!)");
                 }
@@ -276,7 +276,7 @@ $COLUMN_MAP = {
         value     => sub {
             my $t = shift;
             my $name = $t->OwnerObj->Name;
-            my %key = RT::Crypt::GnuPG::GetKeyInfo($t->OwnerObj->EmailAddress);
+            my %key = RT::Crypt->GetKeyInfo( Key => $t->OwnerObj->EmailAddress );
             if (!defined $key{'info'}) {
                 $name .= ' '. loc("(no pubkey!)");
             }
diff --git a/share/html/Ticket/Elements/ShowGnuPGStatus b/share/html/Ticket/Elements/ShowGnuPGStatus
index fa78bc8..7a81de0 100644
--- a/share/html/Ticket/Elements/ShowGnuPGStatus
+++ b/share/html/Ticket/Elements/ShowGnuPGStatus
@@ -67,7 +67,7 @@ my $re_protocols = join '|', map "\Q$_\E", @protocols;
 
 foreach ( $Attachment->SplitHeaders ) {
     if ( s/^X-RT-($re_protocols)-Status:\s*//io ) {
-        push @runs, [ RT::Crypt->ParseStatus( Protocol => "$1", Status => $_ ) ];
+        push @runs, [ $1, RT::Crypt->ParseStatus( Protocol => "$1", Status => $_ ) ];
     }
 
     $needs_unsigned_warning = 0 if /^X-RT-Incoming-Signature:/;
@@ -135,6 +135,7 @@ my $reverify_cb = sub {
 
 my @messages;
 foreach my $run ( @runs ) {
+    my $protocol = shift @$run;
     foreach my $line ( @$run ) {
         if ( $line->{'Operation'} eq 'KeyCheck' ) {
             next unless $Reverify;
@@ -142,7 +143,9 @@ foreach my $run ( @runs ) {
             next unless $line->{'KeyType'} eq 'public' && $line->{'Status'} eq 'MISSING';
 
             # but only if we have key
-            my %key = RT::Crypt::GnuPG::GetPublicKeyInfo( $line->{'Key'} );
+            my %key = RT::Crypt->GetPublicKeyInfo(
+                Protocol => $protocol, Key => $line->{'Key'}
+            );
             if ( $key{'info'} ) {
                 my ($status, $msg) = $reverify_cb->($Attachment);
                 unless ($status) {

commit e1a7ca4501fd1af6c191b3f31fa82cd957af0b90
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 23 21:18:57 2010 +0300

    update tests

diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
index ec31333..3285a33 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -29,7 +29,7 @@ RT->Config->Set( 'GnuPGOptions',
                  homedir => $homedir,
                  'no-permission-warning' => undef);
 
-RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
 
 my ($baseurl, $m) = RT::Test->started_ok;
 
diff --git a/t/web/gnupg-select-keys-on-create.t b/t/web/gnupg-select-keys-on-create.t
index deee6b2..a62959d 100644
--- a/t/web/gnupg-select-keys-on-create.t
+++ b/t/web/gnupg-select-keys-on-create.t
@@ -68,7 +68,7 @@ diag "check that signing doesn't work if there is no key" if $ENV{TEST_VERBOSE};
 {
     RT::Test->import_gnupg_key('rt-recipient at example.com');
     RT::Test->trust_gnupg_key('rt-recipient at example.com');
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-recipient at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com');
     is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key';
 }
 
@@ -102,7 +102,7 @@ diag "import first key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
 my $fpr1 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com', 'public');
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com');
     is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr1 = $res{'info'}[0]{'Fingerprint'};
 }
@@ -149,7 +149,7 @@ diag "import a second key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
 my $fpr2 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com.2', 'public');
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com');
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr2 = $res{'info'}[2]{'Fingerprint'};
 }
@@ -194,7 +194,7 @@ diag "check that things still doesn't work if two keys are not trusted" if $ENV{
 
 {
     RT::Test->lsign_gnupg_key( $fpr1 );
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com');
     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 76817dd..432faac 100644
--- a/t/web/gnupg-select-keys-on-update.t
+++ b/t/web/gnupg-select-keys-on-update.t
@@ -80,7 +80,7 @@ diag "check that signing doesn't work if there is no key" if $ENV{TEST_VERBOSE};
 {
     RT::Test->import_gnupg_key('rt-recipient at example.com');
     RT::Test->trust_gnupg_key('rt-recipient at example.com');
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-recipient at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-recipient at example.com');
     is $res{'info'}[0]{'TrustTerse'}, 'ultimate', 'ultimately trusted key';
 }
 
@@ -116,7 +116,7 @@ diag "import first key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
 my $fpr1 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com', 'public');
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com');
     is $res{'info'}[0]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr1 = $res{'info'}[0]{'Fingerprint'};
 }
@@ -164,7 +164,7 @@ diag "import a second key of rt-test\@example.com" if $ENV{TEST_VERBOSE};
 my $fpr2 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com.2', 'public');
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com');
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
     $fpr2 = $res{'info'}[2]{'Fingerprint'};
 }
@@ -210,7 +210,7 @@ diag "check that things still doesn't work if two keys are not trusted" if $ENV{
 
 {
     RT::Test->lsign_gnupg_key( $fpr1 );
-    my %res = RT::Crypt::GnuPG::GetKeysInfo('rt-test at example.com');
+    my %res = RT::Crypt->GetKeysInfo( Key => 'rt-test at example.com');
     ok $res{'info'}[0]{'TrustLevel'} > 0, 'trusted key';
     is $res{'info'}[1]{'TrustLevel'}, 0, 'is not trusted key';
 }

commit a55589cc3ef852f137c19641b198d0a891e889fe
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Jan 25 09:57:36 2010 +0300

    refactor UseKeyFor* and GetKeysFor* for generic use

diff --git a/lib/RT/Attachment_Overlay.pm b/lib/RT/Attachment_Overlay.pm
index f1cad37..d84ea9c 100644
--- a/lib/RT/Attachment_Overlay.pm
+++ b/lib/RT/Attachment_Overlay.pm
@@ -658,9 +658,9 @@ sub Encrypt {
         RT->Config->Get('CorrespondAddress'),
         RT->Config->Get('CommentAddress'),
     ) {
-        my %res = RT::Crypt::GnuPG->GetKeysInfo( $address, 'private' );
+        my %res = RT::Crypt->GetKeysInfo( Key => $address, Type => 'private' );
         next if $res{'exit_code'} || !$res{'info'};
-        %res = RT::Crypt::GnuPG::GetKeysForEncryption( $address );
+        %res = RT::Crypt->GetKeysForEncryption( $address );
         next if $res{'exit_code'} || !$res{'info'};
         $encrypt_for = $address;
     }
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index e6101a4..7063eae 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -137,6 +137,13 @@ sub SignEncrypt {
     return %res;
 }
 
+sub DrySign {
+    my $self = shift;
+    my %args = ( Protocol => undef, Signer => undef, @_ );
+    my $protocol = $args{'Protocol'} || $self->UseForOutgoing;
+    return $self->LoadImplementation( $protocol )->DrySign( @_ );
+}
+
 sub VerifyDecrypt {
     my $self = shift;
     my %args = (
@@ -170,6 +177,59 @@ sub ParseStatus {
     return $self->LoadImplementation( $args{'Protocol'} )->ParseStatus( $args{'Status'} );
 }
 
+=head2 UseKeyForSigning
+
+Returns or sets identifier of the key that should be used for signing.
+
+Returns the current value when called without arguments.
+
+Sets new value when called with one argument and unsets if it's undef.
+
+=cut
+
+{ my $key;
+sub UseKeyForSigning {
+    my $self = shift;
+    if ( @_ ) {
+        $key = $_[0];
+    }
+    return $key;
+} }
+
+{ my %key;
+# no args -> clear
+# one arg -> return preferred key
+# many -> set
+sub UseKeyForEncryption {
+    my $self = shift;
+    unless ( @_ ) {
+        %key = ();
+    } elsif ( @_ > 1 ) {
+        %key = (%key, @_);
+        $key{ lc($_) } = delete $key{ $_ } foreach grep lc ne $_, keys %key;
+    } else {
+        return $key{ $_[0] };
+    }
+    return ();
+} }
+
+sub GetKeysForEncryption {
+    my $self = shift;
+    my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my %res = $self->LoadImplementation( $protocol )->GetKeysForEncryption( @_ );
+    $res{'Protocol'} = $protocol;
+    return %res;
+}
+
+sub GetKeysForSigning {
+    my $self = shift;
+    my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_);
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my %res = $self->LoadImplementation( $protocol )->GetKeysForSigning( @_ );
+    $res{'Protocol'} = $protocol;
+    return %res;
+}
 
 sub GetPublicKeyInfo {
     return (shift)->GetKeyInfo( @_, Type => 'public' );
@@ -188,7 +248,7 @@ sub GetKeyInfo {
 
 sub GetKeysInfo {
     my $self = shift;
-    my %args = ( Protocol => undef, @_ );
+    my %args = @_%2 ? (Key => @_) : ( Protocol => undef, Key => undef, @_ );
     my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
     my %res = $self->LoadImplementation( $protocol )->GetKeysInfo( @_ );
     $res{'Protocol'} = $protocol;
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 1aa1a2f..56571ae 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -504,7 +504,7 @@ sub SignEncryptRFC3156 {
     if ( $args{'Encrypt'} ) {
         my %seen;
         $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             grep !$seen{ $_ }++, map $_->address,
             map Email::Address->parse( $entity->head->get( $_ ) ),
             qw(To Cc Bcc);
@@ -627,7 +627,7 @@ sub _SignEncryptTextInline {
 
     if ( $args{'Encrypt'} ) {
         $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             @{ $args{'Recipients'} || [] };
     }
 
@@ -717,7 +717,7 @@ sub _SignEncryptAttachmentInline {
     my $entity = $args{'Entity'};
     if ( $args{'Encrypt'} ) {
         $gnupg->options->push_recipients( $_ ) foreach
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             @{ $args{'Recipients'} || [] };
     }
 
@@ -819,7 +819,7 @@ sub SignEncryptContent {
 
     if ( $args{'Encrypt'} ) {
         $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             @{ $args{'Recipients'} || [] };
     }
 
@@ -1983,40 +1983,6 @@ sub _PrepareGnuPGOptions {
     return %res;
 }
 
-{ my %key;
-# no args -> clear
-# one arg -> return preferred key
-# many -> set
-sub UseKeyForEncryption {
-    unless ( @_ ) {
-        %key = ();
-    } elsif ( @_ > 1 ) {
-        %key = (%key, @_);
-        $key{ lc($_) } = delete $key{ $_ } foreach grep lc ne $_, keys %key;
-    } else {
-        return $key{ $_[0] };
-    }
-    return ();
-} }
-
-=head2 UseKeyForSigning
-
-Returns or sets identifier of the key that should be used for signing.
-
-Returns the current value when called without arguments.
-
-Sets new value when called with one argument and unsets if it's undef.
-
-=cut
-
-{ my $key;
-sub UseKeyForSigning {
-    if ( @_ ) {
-        $key = $_[0];
-    }
-    return $key;
-} }
-
 =head2 GetKeysForEncryption
 
 Takes identifier and returns keys suitable for encryption.
@@ -2028,8 +1994,8 @@ also listed.
 
 sub GetKeysForEncryption {
     my $self = shift;
-    my $key_id = shift;
-    my %res = $self->GetKeysInfo( Key => $key_id, Type => 'public', @_ );
+    my %args = (Recipient => undef, @_);
+    my %res = $self->GetKeysInfo( Key => delete $args{'Recipient'}, %args, Type => 'public' );
     return %res if $res{'exit_code'};
     return %res unless $res{'info'};
 
@@ -2050,8 +2016,8 @@ sub GetKeysForEncryption {
 
 sub GetKeysForSigning {
     my $self = shift;
-    my $key_id = shift;
-    return $self->GetKeysInfo( Key => $key_id, Type => 'private', @_ );
+    my %args = (Signer => undef, @_)
+    return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' );
 }
 
 sub CheckRecipients {
@@ -2062,7 +2028,7 @@ sub CheckRecipients {
 
     my %seen;
     foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
-        my %res = $self->GetKeysForEncryption( $address );
+        my %res = $self->GetKeysForEncryption( Recipient => $address );
         if ( $res{'info'} && @{ $res{'info'} } == 1 && $res{'info'}[0]{'TrustLevel'} > 0 ) {
             # good, one suitable and trusted key 
             next;
@@ -2072,7 +2038,7 @@ sub CheckRecipients {
         # it's possible that we have no User record with the email
         $user = undef unless $user->id;
 
-        if ( my $fpr = UseKeyForEncryption( $address ) ) {
+        if ( my $fpr = RT::Crypt->UseKeyForEncryption( $address ) ) {
             if ( $res{'info'} && @{ $res{'info'} } ) {
                 next if
                     grep lc $_->{'Fingerprint'} eq lc $fpr,
@@ -2425,10 +2391,10 @@ sub ImportKey {
     return %res;
 }
 
-=head2 KEY
+=head2 DrySign Signer => KEY
 
 Signs a small message with the key, to make sure the key exists and 
-we have a useable passphrase. The first argument MUST be a key identifier
+we have a useable passphrase. The Signer argument MUST be a key identifier
 of the signer: either email address, key id or finger print.
 
 Returns a true value if all went well.
@@ -2437,7 +2403,8 @@ Returns a true value if all went well.
 
 sub DrySign {
     my $self = shift;
-    my $from = shift;
+    my %args = ( Signer => undef, @_ );
+    my $from = $args{'Signer'};
 
     my $mime = MIME::Entity->build(
         Type    => "text/plain",
@@ -2464,7 +2431,6 @@ sub DrySign {
 This routine returns true if RT's GnuPG support is configured and working 
 properly (and false otherwise).
 
-
 =cut
 
 
diff --git a/lib/RT/Interface/Web/Handler.pm b/lib/RT/Interface/Web/Handler.pm
index 6a06606..e2bb357 100644
--- a/lib/RT/Interface/Web/Handler.pm
+++ b/lib/RT/Interface/Web/Handler.pm
@@ -185,7 +185,7 @@ and is not recommended to change.
 
 =item Clean up state of RT::Action::SendEmail using 'CleanSlate' method
 
-=item Flush tmp GnuPG key preferences
+=item Flush tmp crypt key preferences
 
 =back
 
@@ -212,10 +212,10 @@ sub CleanupRequest {
     require RT::Action::SendEmail;
     RT::Action::SendEmail->CleanSlate;
     
-    if (RT->Config->Get('GnuPG')->{'Enable'}) {
-        require RT::Crypt::GnuPG;
-        RT::Crypt::GnuPG::UseKeyForEncryption();
-        RT::Crypt::GnuPG::UseKeyForSigning( undef );
+    if (RT->Config->Get('Crypt')->{'Enable'}) {
+        require RT::Crypt;
+        RT::Crypt->UseKeyForEncryption();
+        RT::Crypt->UseKeyForSigning( undef );
     }
 
     %RT::Ticket::MERGE_CACHE = ( effective => {}, merged => {} );
diff --git a/lib/RT/User_Overlay.pm b/lib/RT/User_Overlay.pm
index 8582d78..5078101 100755
--- a/lib/RT/User_Overlay.pm
+++ b/lib/RT/User_Overlay.pm
@@ -1710,19 +1710,19 @@ sub PreferredKey
     return $prefkey->Content if $prefkey;
 
     # we don't have a preferred key for this user, so now we must query GPG
-    require RT::Crypt::GnuPG;
-    my %res = RT::Crypt::GnuPG::GetKeysForEncryption($self->EmailAddress);
+    require RT::Crypt;
+    my %res = RT::Crypt->GetKeysForEncryption($self->EmailAddress);
     return undef unless defined $res{'info'};
     my @keys = @{ $res{'info'} };
     return undef if @keys == 0;
 
     if (@keys == 1) {
-        $prefkey = $keys[0]->{'Fingerprint'};
+        $prefkey = $keys[0]->{'id'} || $keys[0]->{'Fingerprint'};
     }
     else {
         # prefer the maximally trusted key
         @keys = sort { $b->{'TrustLevel'} <=> $a->{'TrustLevel'} } @keys;
-        $prefkey = $keys[0]->{'Fingerprint'};
+        $prefkey = $keys[0]->{'id'} || $keys[0]->{'Fingerprint'};
     }
 
     $self->SetAttribute(Name => 'PreferredKey', Content => $prefkey);
@@ -1751,7 +1751,7 @@ sub SetPrivateKey {
 
     # check that it's really private key
     {
-        my %tmp = RT::Crypt::GnuPG::GetKeysForSigning( $key );
+        my %tmp = RT::Crypt->GetKeysForSigning( $key );
         return (0, $self->loc("No such key or it's not suitable for signing"))
             if $tmp{'exit_code'} || !$tmp{'info'};
     }
diff --git a/share/html/Admin/Users/GnuPG.html b/share/html/Admin/Users/GnuPG.html
index dd0d05a..1f5daf3 100644
--- a/share/html/Admin/Users/GnuPG.html
+++ b/share/html/Admin/Users/GnuPG.html
@@ -82,7 +82,7 @@ $Update     => undef
 <%INIT>
 return unless RT->Config->Get('GnuPG')->{'Enable'};
 
-require RT::Crypt::GnuPG;
+require RT::Crypt;
 
 my @results;
 
@@ -94,7 +94,7 @@ unless ( $UserObj->id ) {
 $id = $ARGS{'id'} = $UserObj->id;
 
 my $email = $UserObj->EmailAddress;
-my %keys_meta = RT::Crypt::GnuPG::GetKeysForSigning( $email, 'force' );
+my %keys_meta = RT::Crypt->GetKeysForSigning( $email, Force => 1 );
 
 $ARGS{'PrivateKey'} = $m->comp('/Widgets/Form/Select:Process',
     Name      => 'PrivateKey',
diff --git a/share/html/Elements/GnuPG/SelectKeyForEncryption b/share/html/Elements/GnuPG/SelectKeyForEncryption
index c28c5ad..c93047d 100644
--- a/share/html/Elements/GnuPG/SelectKeyForEncryption
+++ b/share/html/Elements/GnuPG/SelectKeyForEncryption
@@ -50,7 +50,7 @@
 % } else {
 <select name="<% $Name %>">
 %     foreach my $key (@keys) {
-<option value="<% $key->{'Fingerprint'} %>"><% $key->{'Fingerprint'} %> <% loc("(trust: [_1])", $key->{'TrustTerse'}) %></option>
+<option value="<% $key->{'id'} || $key->{'Fingerprint'} %>"><% $key->{'id'} || $key->{'Fingerprint'} %> <% loc("(trust: [_1])", $key->{'TrustTerse'}) %></option>
 %     }
 </select>
 % }
@@ -59,10 +59,10 @@
 require RT::Crypt::GnuPG;
 my $d;
 
-my %res = RT::Crypt::GnuPG::GetKeysForEncryption($EmailAddress);
+my %res = RT::Crypt->GetKeysForEncryption($EmailAddress);
 # move the preferred key to the top of the list
 my @keys = map {
-               $_->{'Fingerprint'} eq ( $Default || '' )
+               ($_->{'id'} || $_->{'Fingerprint'}) eq ( $Default || '' )
                    ?  do { $d = $_; () }
                    : $_
            }
@@ -78,4 +78,3 @@ $Name         => 'PreferredKey'
 $EmailAddress => undef
 $Default      => undef
 </%ARGS>
-
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
index 38bca7a..f3c2b1d 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -65,9 +65,7 @@ return unless $self;
 $Arguments => {}
 </%ARGS>
 <%INIT>
-return undef unless RT->Config->Get('GnuPG')->{'Enable'};
-
-require RT::Crypt::GnuPG;
+return undef unless RT->Config->Get('Crypt')->{'Enable'};
 return { %$Arguments };
 </%INIT>
 </%METHOD>
@@ -129,11 +127,11 @@ if ( $self->{'Sign'} ) {
         ? ( $QueueObj->CommentAddress || RT->Config->Get('CommentAddress') )
         : ( $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') );
 
-    unless ( RT::Crypt::GnuPG->DrySign( $address ) ) {
+    unless ( RT::Crypt->DrySign( Signer => $address ) ) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
     } else {
-        RT::Crypt::GnuPG->UseKeyForSigning( $self->{'SignUsing'} )
+        RT::Crypt->UseKeyForSigning( $self->{'SignUsing'} )
             if $self->{'SignUsing'};
     }
 }
@@ -168,7 +166,7 @@ if ( $self->{'Encrypt'} ) {
     my %seen;
     @recipients = grep !$seen{ lc $_ }++, @recipients;
 
-    RT::Crypt::GnuPG->UseKeyForEncryption(
+    RT::Crypt->UseKeyForEncryption(
         map { (/^UseKey-(.*)$/)[0] => $self->{ $_ } }
         grep $self->{ $_ } && /^UseKey-/,
         keys %$self

commit a3e53473c7f5ddeabd79a1b43fd9853adb040b0a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 27 13:02:12 2010 +0300

    missing semicolon

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 56571ae..1349949 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2016,7 +2016,7 @@ sub GetKeysForEncryption {
 
 sub GetKeysForSigning {
     my $self = shift;
-    my %args = (Signer => undef, @_)
+    my %args = (Signer => undef, @_);
     return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' );
 }
 

commit c504007d4c7840d28ca51907da4a1853db4226a0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 28 21:16:15 2010 +0300

    fix parts_DFS like filter we have, we need only leafs

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 7063eae..43631f8 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -83,11 +83,11 @@ sub FindProtectedParts {
         my %parent;
         my $filter; $filter = sub {
             $parent{$_[0]} = $_[1];
-            return
-                grep !$args{'Skip'}{$_},
-                $_[0]->is_multipart ? () : $_,
-                map $filter->($_, $_[0]), grep !$args{'Skip'}{$_},
-                    $_[0]->parts;
+            unless ( $_[0]->is_multipart ) {
+                return () if $args{'Skip'}{$_[0]};
+                return $_[0];
+            }
+            return map $filter->($_, $_[0]), grep !$args{'Skip'}{$_}, $_[0]->parts;
         };
         my @parts = $filter->($entity);
         return @res unless @parts;

commit 814a55b3cc0201460788a10473c2da04c288ea0c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 28 21:17:03 2010 +0300

    don't report unsafe permissions on gpg tests

diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
index 3285a33..44f48df 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -84,6 +84,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"fnord\r\n",
     \$buf,
@@ -126,6 +127,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"clearfnord\r\n",
     \$buf,
@@ -168,6 +170,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"orzzzzzz\r\n",
     \$buf,
@@ -216,6 +219,7 @@ run3(
         '--default-key' => 'rt at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'test',
+        '--no-permission-warning',
     ),
     \"alright\r\n",
     \$buf,
@@ -251,6 +255,7 @@ run3(
         qw(gpg --armor --encrypt),
         '--recipient'   => 'random at localhost',
         '--homedir'     => $homedir,
+        '--no-permission-warning',
     ),
     \"should not be there either\r\n",
     \$buf,
@@ -288,6 +293,7 @@ run3(
         qw(gpg --armor --encrypt),
         '--recipient'   => 'rt at example.com',
         '--homedir'     => $homedir,
+        '--no-permission-warning',
     ),
     \"really should not be there either\r\n",
     \$buf,

commit 13b5ef7d0cf416519ca55f079035e82dd313ab66
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:44:15 2010 +0300

    s/Incomming/Incoming/

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index b8bdbe1..02cd374 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -441,13 +441,14 @@ our %META = (
         Type => 'HASH',
         PostLoadCheck => sub {
             my $self = shift;
+
             require RT::Crypt;
+            my @enabled = RT::Crypt->EnabledProtocols;
 
             my $opt = $self->Get('Crypt');
-            my @enabled = RT::Crypt->EnabledProtocols;
             $opt->{'Enable'} = scalar @enabled;;
-            unless ( $opt->{'Incomming'} && @{ $opt->{'Incomming'} } ) {
-                $opt->{'Incomming'} = \@enabled;
+            unless ( $opt->{'Incoming'} && @{ $opt->{'Incoming'} } ) {
+                $opt->{'Incoming'} = \@enabled;
             }
             unless ( $opt->{'Outgoing'} ) {
                 $opt->{'Outgoing'} = $enabled[0];

commit 83a95ad692f76f51f875a82958b07c80bf5f201f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:44:40 2010 +0300

    PostLoadCheck for SMIME config option

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 02cd374..477d7c3 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -455,7 +455,33 @@ our %META = (
             }
         },
     },
-    SMIME        => { Type => 'HASH' },
+    SMIME        => {
+        Type => 'HASH',
+        PostLoadCheck => sub {
+            my $self = shift;
+            my $opt = $self->Get('SMIME');
+            return unless $opt->{'Enable'};
+
+            my $path = $opt->{'OpenSSL'};
+            if ( defined $path ) {
+                unless ( -e $path && -x _) {
+                    $RT::Logger->debug(
+                        "'$path' doesn't exist or is not an executable."
+                        ." Please change SMIME->OpenSSL option."
+                        ." SMIME support has been disabled"
+                    );
+                    $opt->{'Enable'} = 0;
+                    return;
+                }
+            } else {
+                $opt->{'OpenSSL'} = 'openssl';
+            }
+
+            unless ( ref $opt->{'Passphrase'} eq 'HASH' ) {
+                $opt->{'Passphrase'} = { '' => $opt->{'Passphrase'} };
+            }
+        },
+    },
     GnuPG        => { Type => 'HASH' },
     GnuPGOptions => {
         Type => 'HASH',

commit 834bd8675cfa73afe28c67d479f361f5e9f5fe5e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:46:00 2010 +0300

    new functions for testing: smime, temp storang, relocatable

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index b870a05..cd0e790 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -210,6 +210,50 @@ sub bootstrap_tempdir {
     );
 }
 
+sub new_temp_file {
+    my $self = shift;
+    my @keys = @_;
+    my $name = pop @keys;
+
+    my $path = File::Spec->catfile( "$tmp{'directory'}", $name );
+
+    my $last_key = pop @keys;
+    my $tmp = \%tmp;
+    while ( my $key = shift @keys ) {
+        $tmp = ($tmp->{$key} ||= {});
+    }
+    return $tmp->{$last_key} = $path;
+}
+
+sub new_temp_dir {
+    my $self = shift;
+    my @keys = @_;
+    my $name = pop @keys;
+
+    my $path = File::Spec->catdir( "$tmp{'directory'}", $name );
+    mkpath( $path );
+
+    my $last_key = pop @keys;
+    my $tmp = \%tmp;
+    while ( my $key = shift @keys ) {
+        $tmp = ($tmp->{$key} ||= {});
+    }
+    return $tmp->{$last_key} = $path;
+}
+
+sub temp {
+    my $self = shift;
+    my @keys = @_;
+
+    my $last_key = pop @keys;
+    my $tmp = \%tmp;
+    while ( my $key = shift @keys ) {
+        return undef unless $tmp->{$key};
+        $tmp = $tmp->{$key};
+    }
+    return $tmp->{$last_key};
+}
+
 sub bootstrap_config {
     my $self = shift;
     my %args = @_;
@@ -217,10 +261,11 @@ sub bootstrap_config {
     $tmp{'config'}{'RT'} = File::Spec->catfile(
         "$tmp{'directory'}", 'RT_SiteConfig.pm'
     );
-    open my $config, '>', $tmp{'config'}{'RT'}
-        or die "Couldn't open $tmp{'config'}{'RT'}: $!";
+    my $config = $self->new_temp_file( config => RT => 'RT_SiteConfig.pm' );
+    open my $config_fh, '>', $config
+        or die "Couldn't open $config: $!";
 
-    print $config qq{
+    print $config_fh qq{
 Set( \$WebPort , $port);
 Set( \$WebBaseURL , "http://localhost:\$WebPort");
 Set( \$LogToSyslog , undef);
@@ -229,20 +274,20 @@ Set( \$RTAddressRegexp , qr/^bad_re_that_doesnt_match\$/);
 Set( \$MailCommand, 'testfile');
 };
     if ( $ENV{'RT_TEST_DB_SID'} ) { # oracle case
-        print $config "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n";
-        print $config "Set( \$DatabaseUser , '$dbname');\n";
+        print $config_fh "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n";
+        print $config_fh "Set( \$DatabaseUser , '$dbname');\n";
     } else {
-        print $config "Set( \$DatabaseName , '$dbname');\n";
-        print $config "Set( \$DatabaseUser , 'u${dbname}');\n";
+        print $config_fh "Set( \$DatabaseName , '$dbname');\n";
+        print $config_fh "Set( \$DatabaseUser , 'u${dbname}');\n";
     }
-    print $config "Set( \$DevelMode, 0 );\n"
+    print $config_fh "Set( \$DevelMode, 0 );\n"
         if $INC{'Devel/Cover.pm'};
 
     # set mail catcher
     my $mail_catcher = $tmp{'mailbox'} = File::Spec->catfile(
         $tmp{'directory'}->dirname, 'mailbox.eml'
     );
-    print $config <<END;
+    print $config_fh <<END;
 Set( \$MailCommand, sub {
     my \$MIME = shift;
 
@@ -255,11 +300,11 @@ Set( \$MailCommand, sub {
 } );
 END
 
-    print $config $args{'config'} if $args{'config'};
+    print $config_fh $args{'config'} if $args{'config'};
 
-    print $config "\n1;\n";
-    $ENV{'RT_SITE_CONFIG'} = $tmp{'config'}{'RT'};
-    close $config;
+    print $config_fh "\n1;\n";
+    $ENV{'RT_SITE_CONFIG'} = $config;
+    close $config_fh;
 
     return $config;
 }
@@ -280,7 +325,7 @@ sub set_config_wrapper {
                 SCALAR => '$',
             );
             my $sigil = $sigils{$type} || $sigils{'SCALAR'};
-            open my $fh, '>>', $tmp{'config'}{'RT'}
+            open my $fh, '>>', RT::Test->temp( config => 'RT' )
                 or die "Couldn't open config file: $!";
             require Data::Dumper;
             my $dump = Data::Dumper::Dumper([@_[2 .. $#_]]);
@@ -823,6 +868,21 @@ sub get_relocatable_file {
     return File::Spec->catfile(get_relocatable_dir(@_), $file);
 }
 
+sub find_relocatable_path {
+    my @path = @_;
+
+    # simple strategy find data/gnupg/keys, from the dir where test file lives
+    # to updirs, try 3 times in total
+    my $path = File::Spec->catfile( @path );
+    for my $up ( 0 .. 2 ) {
+        my $p = get_relocatable_dir($path);
+        return $p if -e $p;
+
+        $path = File::Spec->catfile( File::Spec->updir(), $path );
+    }
+    return undef;
+}
+
 sub get_abs_relocatable_dir {
     (my $volume, my $directories, my $file) = File::Spec->splitpath($0);
     if (File::Spec->file_name_is_absolute($directories)) {
@@ -837,34 +897,19 @@ sub import_gnupg_key {
     my $key  = shift;
     my $type = shift || 'secret';
 
+    my $path = find_relocatable_path( 'data', 'gnupg', 'keys' );
+
     $key =~ s/\@/-at-/g;
     $key .= ".$type.key";
 
-    require RT::Crypt::GnuPG;
-
-    # simple strategy find data/gnupg/keys, from the dir where test file lives
-    # to updirs, try 3 times in total
-    my $path = File::Spec->catfile( 'data', 'gnupg', 'keys' );
-    my $abs_path;
-    for my $up ( 0 .. 2 ) {
-        my $p = get_relocatable_dir($path);
-        if ( -e $p ) {
-            $abs_path = $p;
-            last;
-        }
-        else {
-            $path = File::Spec->catfile( File::Spec->updir(), $path );
-        }
-    }
-
     die "can't find the dir where gnupg keys are stored"
-      unless $abs_path;
+      unless $path;
 
+    require RT::Crypt::GnuPG;
     return RT::Crypt::GnuPG->ImportKey(
-        RT::Test->file_content( [ $abs_path, $key ] ) );
+        RT::Test->file_content( [ $path, $key ] ) );
 }
 
-
 sub lsign_gnupg_key {
     my $self = shift;
     my $key = shift;
@@ -990,6 +1035,29 @@ sub trust_gnupg_key {
     return %res;
 }
 
+sub import_smime_key {
+    my $self = shift;
+    my $key  = shift;
+
+    my $path = 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";
+
+    my $content = RT::Test->file_content( [ $path, $key ] );
+    open my $fh, '>:raw', File::Spec->catfile($keyring, $key)
+        or die "can't open file: $!";
+    print $fh $content;
+    close $fh;
+
+    return;
+}
+
 sub started_ok {
     my $self = shift;
 
@@ -1036,12 +1104,6 @@ sub start_apache_server {
 
     my %info = $self->apache_server_info( variant => $variant );
 
-    Test::More::diag(do {
-        open my $fh, '<', $tmp{'config'}{'RT'};
-        local $/;
-        <$fh>
-    });
-
     my $tmpl = File::Spec->rel2abs( File::Spec->catfile(
         't', 'data', 'configs',
         'apache'. $info{'version'} .'+'. $variant .'.conf'
@@ -1064,16 +1126,10 @@ sub start_apache_server {
         my $method = 'apache_'.$variant.'_server_options';
         $self->$method( \%info, \%opt );
     }
-    $tmp{'config'}{'apache'} = File::Spec->catfile(
-        "$tmp{'directory'}", "apache.conf"
-    );
-    $self->process_in_file(
-        in      => $tmpl, 
-        out     => $tmp{'config'}{'apache'},
-        options => \%opt,
-    );
+    my $apache_config = $self->new_temp_file( config => apache => "apache.conf" );
+    $self->process_in_file( in => $tmpl, out => $apache_config, options => \%opt );
 
-    $self->fork_exec($info{'executable'}, '-f', $tmp{'config'}{'apache'});
+    $self->fork_exec($info{'executable'}, '-f', $apache_config);
     my $pid = do {
         my $tries = 10;
         while ( !-e $opt{'pid_file'} ) {

commit 13277d95899078e7b00422ebfa315eed57628000
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:47:15 2010 +0300

    expect that we get Queue and Actions arguments and pass them further

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index e24a32c..b716f82 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -57,7 +57,7 @@ RT::Interface::Email::Auth::Crypt - decrypting and verifying protected emails
 
 =head2 DESCRIPTION
 
-This mail plugin decrypts and verifies incomming emails. Supported
+This mail plugin decrypts and verifies incoming emails. Supported
 encryption protocols are GnuPG and SMIME.
 
 This code is independant from code that encrypts/sign outgoing emails, so
@@ -92,6 +92,8 @@ sub GetCurrentUser {
     my %args = (
         Message       => undef,
         RawMessageRef => undef,
+        Queue         => undef,
+        Actions       => undef,
         @_
     );
 
@@ -112,7 +114,9 @@ sub GetCurrentUser {
     my $msg = $args{'Message'}->dup;
 
     my ($status, @res) = VerifyDecrypt(
-        Entity => $args{'Message'}, AddStatus => 1,
+        %args,
+        Entity => $args{'Message'},
+        AddStatus => 1,
     );
     if ( $status && !@res ) {
         $args{'Message'}->head->add(

commit a109327229ac3a54d09e4aef046b4001b3be3b71
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:48:01 2010 +0300

    pass "queue" and actions into early mail plugins

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 6269d07..55bf028 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1302,6 +1302,10 @@ sub Gateway {
     push @mail_plugins, "Auth::MailFrom" unless @mail_plugins;
     @mail_plugins = _LoadPlugins( @mail_plugins );
 
+    #Set up a queue object
+    my $SystemQueueObj = RT::Queue->new( $RT::SystemUser );
+    $SystemQueueObj->Load( $args{'queue'} );
+
     my %skip_plugin;
     foreach my $class( grep !ref, @mail_plugins ) {
         # check if we should apply filter before decoding
@@ -1313,6 +1317,8 @@ sub Gateway {
         next unless $check_cb->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
         );
 
         $skip_plugin{ $class }++;
@@ -1324,6 +1330,8 @@ sub Gateway {
         my ($status, $msg) = $Code->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
         );
         next if $status > 0;
 
@@ -1374,10 +1382,6 @@ sub Gateway {
         $Right = 'CreateTicket';
     }
 
-    #Set up a queue object
-    my $SystemQueueObj = RT::Queue->new( $RT::SystemUser );
-    $SystemQueueObj->Load( $args{'queue'} );
-
     # We can safely have no queue of we have a known-good ticket
     unless ( $SystemTicket->id || $SystemQueueObj->id ) {
         return ( -75, "RT couldn't find the queue: " . $args{'queue'}, undef );

commit 332f7c74b8f4e49bf01a5baec7be62081a27f665
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:48:58 2010 +0300

    s/Incomming/Incoming/

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 43631f8..610aab1 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -23,7 +23,7 @@ sub UseForOutgoing {
 }
 
 sub EnabledOnIncoming {
-    return @{ scalar RT->Config->Get('Crypt')->{'Incomming'} };
+    return @{ scalar RT->Config->Get('Crypt')->{'Incoming'} };
 }
 
 { my %cache;
@@ -54,7 +54,7 @@ sub FindProtectedParts {
     my @protocols = $args{'Protocols'}
         ? @{ $args{'Protocols'} } 
         : $self->EnabledOnIncoming;
-
+        
     foreach my $protocol ( @protocols ) {
         my $class = $self->LoadImplementation( $protocol );
         my %info = $class->CheckIfProtected( Entity => $entity );

commit 885d8f929ee362cf8c53d531465e9eb00208d453
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:49:49 2010 +0300

    add smime data for testing

diff --git a/t/data/smime/keys/MailEncrypted.txt b/t/data/smime/keys/MailEncrypted.txt
new file mode 100644
index 0000000..4500b30
--- /dev/null
+++ b/t/data/smime/keys/MailEncrypted.txt
@@ -0,0 +1,112 @@
+To: recipient at test.com
+From: sender at test.com
+Subject: Some subject
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIITLQYJKoZIhvcNAQcDoIITHjCCExoCAQAxggKqMIICpgIBADCBjTCBhzELMAkG
+A1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVy
+c2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRl
+c3RlcjEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbQIBBDANBgkqhkiG9w0B
+AQEFAASCAgCrsv4zsYnWl4VbYRxZRlBcbo7rao+YInXrxi4QTucbk0CdfLN1O5nw
+NgWTQLHlLs1L1riiWOcSMSpg6fLQ0cSewjtWrGoPd/cTOeP8u2zeC8Zc0McW2sy8
+sUDF/7l78RgOA7o7R3ioc2FsZ3vjVEg2ZkuUdJvdt6CzRSkD6tN4LBJsC9dVn3r7
+wLa5X012OKBX+52TvIIuEJRgEXtg1xAXjL5jPQwVxymvXHhracveXl1vLmbfid1Z
+0aO+Xgl1tDlihIFzLN1vZkHYBrs3ERR57gOf7UemwZxVq1im5K2hMPgQur92Anmr
+n2HNeu0yCrf4LJWqJpsBvJuI0BMZLMyMij8jp0rQ0pQmcJESQRznup2F2KGus3K3
+eERASZ3KSkZQw4Qsdv1vc8FpuPSTQCliGfUyTqIQDAH2+3VeGODFZNKIcgMkCTs4
+im8Y7Bs4Q9TbsI8eycOU2fcA4wkyrmvDysE/tKbZVfSoe9s8pfRswUIR9UOOH0lP
+7t+oRTl0pKeky1bbQ+8+9A7snEitesj5+oloh5UVpaEVY1zhU3gTSk5F98ca3U7f
+9GAI8jzT5YVzaQ/3eb4sWW/7eILB4r0lwALQ+TmGPSniYoLUXtUyrQO4v+jL3Z7g
+I5nYMxrpitc0No7buR8tVKAQs9L+w4+54+HxUKTtFaprQiDOSZZjBTCCEGUGCSqG
+SIb3DQEHATAUBggqhkiG9w0DBwQIQ+PKGACxGo6AghBAKqmWK6rB+gUjwNIvRuUK
+a+42FWq/LNY5touVS8Ilkz8pUaU/AGJ/nEUx3yYwPqFxsnSdFPb1hE5nXOORlfKM
+cme4yEEpQaWtwOOZPw9iNzhEorPrO0P6iT6r8WhjSaVUDP1phWWmw8dEfcKQdmlE
+LwDF9mv2Xf9HaWxe1iVQsE3uKfyZH1tIPE4LhEmtAjaJsD8LiIITzjUq/4mS1yq+
+uS//CMwwmiELsDj9YNoodEAfTk2OIrABsUTE5SZmhSzinJfelHGEmar/RREJsNAl
+9UnazyJoDa/4QZmO8Br6QLVSp6dhrKs2tluHqfg6uiHl631vrsUS7x4RSL8wgNB7
+fxHgDHkTFLLnJgAhSPNqKdN922wfR2Q3rDTTSKwc3rKI67O31u8Vf7AVUjOrKgiY
+w867XlCKY92CxnTkmMjvQ+nJ8kGfQBLpHq68d0wbcwbLLV8Pv/hITC0EihrNvvzC
+KimMtI8XZN2j23jpoHKQoCHiPO3pb2GfOs9t8m0YQPhHoTAao2fuVQI5sYaitBqt
+oBMp86ivPXGD4K/0YcmploQr1+ILJrKzkjhk7LDIZ8kqSn9DGltZNDjb607RJMO1
+auLv3ngl9CRVr8sMsp8Elz+Vvg2XcmpQUy90Thx/LzDgIqhANMMXi7biBrQCRpkm
+gk0utcHSRJJjsJfv4h98ZcCRb+qf6p4GleWy7E0jepBHtsb2blyH5RjrNqFkRmDQ
+eXLXw2sxPjATP7W3xjTNffqyilKBekQKSqbtADLGAiIRRURcL1drBhN0EsrJNTAi
+DwYmUj7SscW0JANuQzJcOHzdZ/GkM755SiO5Ln6cZz6BBg8E892MAphP7U5yGOt3
+sxTqy++O425yd55oUyZRC4QZCF9/yWsmLdKdBvs7ZdUGif/s/i+vxtkoefwByLbs
+FKnpUyEF9ehyHb/VKZAcdrLTBpxkyqfOqbG0HwsFgKhPB4kq5Or6SSJWzFGtTjG7
+Y7kS0bz4vy5DRGp6aW5FZ0tgcaaGEpqlwJb1p0MdQkciuNb0rJofY6QRfKtfD9YU
+hcrD1SufxvfZdt5LW8EWmZcfG+Rlwv6tYgH8/6+iqeoFdtsb8IeZHw+xPRwbYzGl
+e3fMXyOxxczu+EFRGya8ihlqjy1jXLeI8QeCrrelSErjAqIc62CovBdJiQXFu9SD
+6NZUlS4G8R2BqXe0uG12cJy/tygZAltdCLemcF1tIikCpwBZwfKSZyIeCk6zcp0N
+d9NrvwBi6LDM+hN79AxWuHaONfgWid1YLX8xJKP7U91TcltsYLw2t4sHnUSazZzq
+Xg9jto/nAMZzNmyHY6oj/S90a1QF7uP+0uhQ+GQOiMRl4+XuiR1zThAJo5HD6ziI
+OI9X/a1ArxLlHuptALI1VguvCDfEvoT1AeLfBWuIma3vvlUZVKi0AyloQ4k88EjD
+KLSb0kftHbj+18SMLDit109PQ7qHkViYgwDAshy5D16h/SexMsE87gx3xiBbDTo4
+EoGX4F/YnJb8zoK94kEp9U/zqyc+iZNXpSXK4aRmWibmXqu39l4ZalONZDkO3drX
+lduub0VoKqy9OTbvUmvlwm5FJ0zDYJ80FYtc5u+7QG7xsMclfN/E8fkS5NBf/5a6
+eHJhj+/x/P9IcIcMdHGSXyDlmehyKYsxzIvouPe5s+I5+KZs30JYOciSyHCUlgl7
+mJ8cjOcD1Kmo/27FZxt+o6zznfDku2B8KMd2J+cAl4K+0nveINrTml038Eu/x5k0
+VxZvig0k3ZXKZeubikSTL4A+FqOP3Q2kgJ7SnnF+wLG2rVNgvteKZa7oKvo/mrGN
+L5BFfI/mAkygUQkOQbX6JA96A5HSPkZ30hmb9bKVH6QgOCYWOPi8nw0FNhSGtlUi
+dAIAg2xWFIjBN6Fn0Gl5+FdDbk12l+4pj5z3eTJjpyfTuMYstJPKnOmxMMz4+VdP
+GyCJNJbkfKp/us6SCQm27ERtK32jUgy0mvauqDD751i+2xVXLgX2WnnGGAE16ZpV
+HjjhXf+6X3Ob40mP/BgPrGQFPTlGbkZI8XxqeGImq1H5M3SnZzU8ZwlUyvVarC/y
+sA5mKUBhJMot1HcYDekrYCFGst0KsBc1SCS0zuZ+DsMJs/KRcbz4UZz+c2fvoVAa
+xsej0/XEGYXZNVb4IJsYsxSAHc6YoWAjE7C5peNhkEQbMr9wNSU9705m9Vp4i2mY
+2BzsQM9PxIOKIuk56+cf1GOuuR0wtid4DNfTq0V+q7yBMSC72zCoPQ5efZ2XA3J0
+paKOgdmOmo+FVfOwbqwDJ9+zeZ8k3o/VpzznwSqhuOQ/jERXh5Evfoe9UqqCysP6
+Zcf1ENbb/xZ9WHW/mL6CBYIoWG/GDW9w0vjbe4CScUiy2btpq4eRjrXZ0oV9rAoz
+gVnD/EIdLTQIGR6HCJyb9c31Jo9Ac7xfdaaxZrSJCNNzq+Fl+ZodqZ/x2uiyWCtt
+7xqCXYd4/HbnvrWVYNNxERWlHBkNjtEuPhXgWIvSpbYSlpDb4+AOj+f4YrN7WzgS
+QEeif9QcxP9oU7TWnHrRr/+5D8iHMO/CqNxGCRGCGlqrX5J/OH6a2b0i+i4v9jl1
+VSbXiAy06ELIO9zQ72/cuh6qHZQ4xWTuRojBwZrTBni+NW7Rf6IATSUQqe+8E8y9
+pogn2PKzNltYcXpPvGupQ/2AHFyxrS42kV/iURxI65Chur3GIWsZOfkrqIFQyL6y
+rsT1ngUX3E1hoTdPVSDUNqvtZvZdPZNLzxuElObvvLNYxUGTOvSdbit4PXI/Lhn/
+pyKp8p9nlWfs19uE30cnU4M5LySW6H5xhi8FEdMjU7SpRiUj5cRZtfCW4+qGY3Qb
+APACQluGOqJgUyiGJpbROyvUxaauR2EfEHUgpXXpSLw9j0zAkA5S6Xxgu5Q7q1v8
+Ocy2E+ziBfSbmQRPICxEt10ZFy5KsbJt2byGf0chE8EfFVEhvlIO4XaUxILQxKoJ
+k6Y2d/sB81ZZsjWHOo5nXh0NfoZ5XDSZkbPopUenGz+7SHu0SlG3Ggnz8z+6pxqZ
++CkRnmWQYVbLRSTTE2RG1bM4LAiy2EOeU54pe0DS2tjFBCvVl38BUuPRGkAOL8aN
+0sFDF3EJZyXa1hs2NMMFc5Zgeh2zAmrJzrnMDfWvHWYhWYRVJZodJxrSAlZZc2Zc
+ilTH3jipdYk0+hei8W5Vv79NqbjwzODDglcFUJLP4ZITSAjzYq03UXnMc4tb+Hgk
+ruPruKg+SDqCFKtZsSBx29H4exkSIH9M5dxFZvwresCcfEoK2GycviqtUhRAw6H/
+S4Jc4NmxkX8payJm66F6YKvKM07cNXsE2elP6M4FFjGIy+6SYeMLlN+kf3HgGM3k
+8wPFp53wQcVDu85nttTxx6fRCKspedGlgrq1yKd5AMmimEep0t7KDVMojJ8K1Yuf
+BvJ3hxiiLOlb/uFrP1qOV55Ssbi9ez8e4d9iphQT0yEFK/4CvJZtaE30u46dSx5I
+hiX/d8CC3LpASAHCZIQEAQF3cBAeSlsCJTVPlJTvfO49UTXAGQGSg2kOWI7kSoJo
+LDI5rAFVkrf3EeNxQ6xt66mcIJsR3wtI3WLv4yJ575A0j6LYwuljnloss+gVz7d7
+EcpvzX4iH+ddMDymCUcVJLXR3Rb0eTeLHl3DOzElhFQuPyIGfkhb8Y3ISH4nu5/L
+riDb8zAGK8FCu08Ixl2U2aBbZI5An/YmSpWByhlB3FB0rrOeK+C2MSJwE9fx5eJB
+EIYy9CYr5bk/fX4dXan/jSj3bxHzHLqMWROiVW1sHkSHIlPsOeOMnhefDwXQiCuq
+UzMg/eM2KG70SEVOuutT3UjXTLkhtj7KfSJdl68egzewOGJyq5N7ig+Y82O3nQp+
+zuafwmMAuLSLg5aYb+UuaOzRGIIgRbSLuB4RkDsjzcg1Uu1QZK9oTPbR3a5UCWsT
+IF/kXNMIOgkN44cUYX3qTgvehttzi1mv3fGNG/FCgy1Vshq/6r/MUzjsYxz+fRPT
+FdUi2ZPp1mMz12l/6ngwYzlPWNQqrb4dDm9alwtewfmoWii8e9s24BO+AIhkZ4Yd
+GqzYd/eOgA2xemkxyPbMsLpwcw+MpMtZlvOiKx+g8ZH0Va7Z3EreTWbSfbrjnZkU
+fz7lA8kHhNc7K1sSZ/DabONUM1ulBO7zhIa5bkW8njewzvr3zRMq2xSWV/Yn13na
+RCMZ+Z6FiLgpT0+8/JLC6lglNzqYloorBTNlU4O0QhGaY9b5is0luoMbsjtFTdVM
+irIUBtwxhheP20X4qw74pLO6a+oVIeNqd2RV697ZdELNsbQFKeWlGZDAFEHa+dfg
+en4P7lsTQl7Ra36fkXMzfsQ7GUyx3vVmObzbnUgzgdnx4iPxIzpJUlCcrHJmKyRM
+1QEkeFs00GYxxE7I+F/M0ujrSw5WPIrc7a+VHeJywbuq8fcoJqs8SCLeuDGIjiP3
+8cltDU5ACqWaDPjjx8o5aSlQBplTCIGNlPPKFRr5nEVwcOXMJkUoyzE/FmYU50wC
+7HOeIiSlGGUs33SSnT25vyknEdlS0tg272ad4OLcZnxMvf+Ys2Pmtn5NCEhnyQYm
+VSqf1boF4nuvmYZFvDwOl2M/syt8DO674XBeEqL9HJ3T+Xt3b0fu9wcF5VZpDVff
+jWjc4ycKotM/vbc1W1XQS+QOHXkgebRo/K9/W/hq4CLl6yzQKD016lEadrc9/ypR
+eq/SsjYZrNNMQJB2dN8nLr7zW6f8dCP2xfS9qXwrb/ZXcGwf2HLzh7Jdkj9NbuBk
+1tBMG0lcJbrVFESWHXfbv1QRUa4TMjpQHFD/R7yoF06cF5A3JtE0b6TpyS1YVhK4
+WRxbXnX4xKZLs6YxOBcBseEW70NyPG13gVm5jP5jGUAYaJDLN/dMmwH/fggQBzyS
+Qx2A0PvEN+0CWNcw1Nv+cV+vqwJ+IUb3SnBtMef1/G+exlScGDCrNZZP1ctQm4dT
+jZaPxqLY+GmcfA9F9UlptwV1YEV4nIOGp7ka7l6RcMpHqxqBXOrqBAXXLmAdRUaT
+em7udaowQxhTA39QoqyIUYADiMQkUeOxbF/qnakjZQuXW8VauDUW0ZER7FefXJtd
+G8ZNcUXUpRI+HJBYIAOFGdV0PL9XQellrEoQask1XMj3HZ0GeCIohAFvHLjfpabu
+epLKIlTJOv9g44gn5auGLRMYz1PJD6Bxgi45uBnurbnhlrp6RWoKiDJZBeQP/GSL
+c6hLmHhAAW/i44j7IMtRmjUiYtMcyTa+yr7bOoFPVTmKvazriaCbLjtS5pDT7jps
+shL/z3tWX4ixAlIt3g0W04zdtN7EI0sXGwe9Vlq2R/Ku5voYq6y45ksCM1jg5giG
+Z/Afp1SobmcxaKGLjTFG+0lq21DQbahvVNLfua0i4AYsYoR+koH13cQ7fcWDyJLX
+zlOs48/tu+KvcvYMqFk/1Lm+wdkb+aAVLxPpSeuUpk3Rgh6NgDPFXhPgZpgYyi6N
+UEKypkCAZyaJKhfvPIpqKcwnot6BIHJcxDaeWiiDbSozz4FcKsnQh4ib21fWnHFT
+wJRBdsUShcnZ97/lDYsvdmY=
+
diff --git a/t/data/smime/keys/MailForSend.txt b/t/data/smime/keys/MailForSend.txt
new file mode 100644
index 0000000..b28052a
--- /dev/null
+++ b/t/data/smime/keys/MailForSend.txt
@@ -0,0 +1,7 @@
+Some data
+Some data
+Some data
+Some data
+Some data
+Some data
+Some data
diff --git a/t/data/smime/keys/README b/t/data/smime/keys/README
new file mode 100644
index 0000000..57a09f9
--- /dev/null
+++ b/t/data/smime/keys/README
@@ -0,0 +1,60 @@
+To decrypt signed and encrypted messages 
+openssl smime -in MailEncrypted.txt -decrypt  -recip recipient.crt \
+-inkey recipient.key > MailDecrypted.txt
+
+To verify signature
+openssl smime -in MailDecrypted.txt  -verify -out Mail.txt \
+-signer sender.crt -CAfile ca.crt 
+
+Password on all certificates: 123456
+
+###################################################################
+
+If you gonna to run your own CA and/or create your own certificates 
+here is some help. DONT ASK ME ANY SINGLE STUPID QUESTION please. 10x.
+DONT ASK ME ANY BUNCH OF STUPID QUESTIONS EITHER. Thanks.
+
+1. Run CA.pl -newca   it will create directory structure
+Look at misc directory which comes with openssl
+
+2. You may hack defaults in openssl config file, or create your own
+config file. If so run export OPENSSL_CONFIG=`pwd`/openssl.cnf to 
+force openssl to use YOUR config
+
+3. ROOT CA KEY 4096 bits long
+openssl genrsa -des3 -out ca.key 4096 
+
+4. ROOT Self signed certificate for 4 years
+openssl req -new -x509 -days 1460 -key ca.key -out ca.crt
+
+5. Key for user 4096 keys long
+openssl genrsa -des3 -out user.key 4096
+
+6. Certificate signing request for user for 4 years
+openssl req -new -days 1460 -key user.key -out user.csr
+
+7. Sign it with ROOT CA CERT
+openssl ca -in user.csr -out user.crt -cert ca.crt -keyfile ca.key -days 1460
+
+8. To load into Outlook or Mozilla you will need PKCS12 file format.
+NOTE !!!!! NOTE !!!!!
+**** This format joins private key and certificate leaving private key unprotected.
+**** Make sure you provide export password and don't send this file
+**** via open link if you are really paranoid
+****
+openssl pkcs12 -export -in user.crt -out user.p12 -inkey user.key -name "Dear User Personal Certificate" 
+
+
+9. To sign message
+
+openssl smime -sign -in /etc/passwd -signer user.crt -text -inkey user.key > signed.txt
+
+10. To encrypt
+
+openssl smime -encrypt -out encrypted.txt -from user at test.com -to user at test.com -subject "puzzle" -des3 user.crt
+
+11. Hope that helps to prevent III World War and to improve your brain ability.
+So finally it makes our life on this planet more peacefull and enjoyable.
+
+Dmitry Dorofeev.
+dima at yasp.com
\ No newline at end of file
diff --git a/t/data/smime/keys/ca.crt b/t/data/smime/keys/ca.crt
new file mode 100644
index 0000000..9cacaa7
--- /dev/null
+++ b/t/data/smime/keys/ca.crt
@@ -0,0 +1,37 @@
+-----BEGIN CERTIFICATE-----
+MIIGczCCBFugAwIBAgIBADANBgkqhkiG9w0BAQQFADCBhzELMAkGA1UEBhMCUlUx
+EzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVyc2J1cmcxDTAL
+BgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRlc3RlcjEcMBoG
+CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wMzAyMTAxNDA2MDBaFw0wNzAy
+MDkxNDA2MDBaMIGHMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEW
+MBQGA1UEBxMNU3QtUGV0ZXJzYnVyZzENMAsGA1UEChMEVGVzdDENMAsGA1UECxME
+dGVzdDEPMA0GA1UEAxMGVGVzdGVyMRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3Qu
+Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAm6o4DVBUf0CzcwW/
+psHnERPPLuZYXGY+ItoGtdFd0FYFTLFWF7wZJH3kJj3TIRYJ6tF/QXaLFcizqnCV
+VhrdIBOWlyz7oOZ0z+SKkVn6f0rg6NX8RKnWKeVSstn2wy3/EEiRnpnAxH6/4Ra4
+dKWcldvqRzgmrbQL5rWfwN+TOSFUiNC8HL18zPkwoAmq/D8nGvGO6W/5KUFiMRoH
+ZonmcO29rPBf/o1nCABQHeTH0MZoad4c453U8bpYy5iBx5TX7tdMCnVAcWMaU9qE
+N701rlp73n08veX40pVSSPm43caXYco+AZ82sT2MKbhPw/IJauaSflYdUXSKV0PN
+71yCYhQ8/d1L04jiuR+1LnAGeLh1nKmhK8uYA8B6f25/sjZN1rDjriRLa7+xFTTY
+kxnY8T+z/+LylHgEjOi4yfDibjyMoNyJSg/DT3BW04/aZuUAsTrAwAN/JwwW1QN1
+pZu6m0STOINFcdmP+NHpxQ/z1Q8aPwu4eB74vNCitJNrSe90Ow8+ufxaZVsi4+Eq
+HHScKLtdQxPs5rHshIXI+6WZhqoQEBPNkhSRyFkUXTYG07ro1/jeJnKdD/Lz7hYZ
+GSt5/km/NdeYYhRoHVF6z6yhAdX5VUyeCiDQqd7EA9oO3BsYn11G9qJBYSJd7gFQ
+UB8pTZhbINhhZxr2byomHobU9pkCAwEAAaOB5zCB5DAdBgNVHQ4EFgQUkzZg7t4t
+stLSkqP6sGLsN35+LPIwgbQGA1UdIwSBrDCBqYAUkzZg7t4tstLSkqP6sGLsN35+
+LPKhgY2kgYowgYcxCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRYw
+FAYDVQQHEw1TdC1QZXRlcnNidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwR0
+ZXN0MQ8wDQYDVQQDEwZUZXN0ZXIxHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5j
+b22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOCAgEAmkPTDqkAixy0
+kNlTdsKdtMQDV+mc2OMNc1s2clYiAqhDx4AznTy6IYg4wEOaxbnjxQ8TnbOLC9cs
+M0lAGh15kPxeEe8yESJgVtj4IP41TPVkR/6+rKD7hP4y5JOSRPhYh9IkTH9byeJl
+6Q6WvQp6psO+eDQZGwR2ceqb6/NbImKfNW73sRm9OHBwz4S2NtmIx4LoqaQjcPck
+z+81gctY16MFs4ILRfa+lydLIyqa97KcMIzntWl2YgkWX9QvlPIF9ornLlAy1rvN
+cRaNdkmA9vOcCmXxmTFttZ5DHCxRYPx4pV8oJMjqW4xLQk9Ob60oUWfBLxwScrTP
+MuzgybzTWrAY5bJakperd35Qat56bgUDvRscgmXKuCeQZ6m1DyeYAxtJfkrOTyTw
+GzUg9+IQX+tcq0UJ77Rx2hUYyINTOuwEEBUF36l0QDO8QWlS9XkF/iif7fQMHqej
+if8umdWWzdEpHWiZMFvTDnjvlF/2htX2PVnRcOXRyLemcPsutum0RJJyCNjgGmJN
+4WcM6kS8lbBgJGJUz3Cs2ILoj6e8XXqgxVF4Cpb3troYuFzPUuWlNOyN8FkAgztU
+cycqvtAUOvAYKwlGbT9oiYyGgraVmmS4Cc/2OZ8BsG5gxk7HfFXRkt8l7U/0DWxq
+++kpxBnI+Tz1RGNNIcfC6M5XB39b+bY=
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/ca.key b/t/data/smime/keys/ca.key
new file mode 100644
index 0000000..0f58a24
--- /dev/null
+++ b/t/data/smime/keys/ca.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,B51FD7E25F477D16
+
+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
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/recipient.crt b/t/data/smime/keys/recipient.crt
new file mode 100644
index 0000000..19768ac
--- /dev/null
+++ b/t/data/smime/keys/recipient.crt
@@ -0,0 +1,124 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=RU, ST=Some-State, L=St-Petersburg, O=Test, OU=test, CN=Tester/emailAddress=test at test.com
+        Validity
+            Not Before: May 23 07:41:48 2007 GMT
+            Not After : Jan 29 07:41:48 2021 GMT
+        Subject: C=RU, ST=Some-State, O=Test, CN=root/emailAddress=root at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (4096 bit)
+                Modulus (4096 bit):
+                    00:af:b6:6f:04:9f:81:cc:44:a4:b1:f1:0c:c1:7c:
+                    d7:41:9d:8c:10:df:dc:f2:8f:b5:13:ce:f6:03:8d:
+                    0b:b3:f0:bf:b8:dd:cc:f6:4d:26:8c:41:9a:cd:08:
+                    60:36:68:0d:4d:5c:c3:b5:2a:71:cc:39:de:19:19:
+                    61:fb:80:64:5c:ac:64:b8:66:c2:2d:77:2b:8e:5c:
+                    88:82:41:51:f8:d8:8b:35:4f:80:66:57:8c:2e:a4:
+                    cb:4b:4f:c3:38:69:ec:43:c7:00:00:b0:0c:db:01:
+                    50:d2:2c:6e:b0:a5:83:8a:dc:aa:0a:84:a4:4c:82:
+                    f3:77:e1:a9:a2:cc:20:36:80:65:86:62:2b:50:4a:
+                    2c:86:d0:c5:f0:52:7b:ca:5a:04:a9:ed:28:57:bc:
+                    07:e5:e3:a1:a7:60:01:69:09:eb:e5:2c:75:61:53:
+                    4a:e7:00:3b:b6:e9:3c:e2:d2:b3:99:76:d9:5f:70:
+                    a4:7f:3c:d1:46:1e:8e:ea:9d:a3:c0:95:d5:a5:45:
+                    ba:68:64:fb:92:5c:23:05:2e:36:50:aa:0a:68:ba:
+                    23:cd:92:29:42:f8:d6:d8:14:09:e7:f2:52:c5:f1:
+                    9a:c8:9a:aa:4e:bc:26:9f:19:00:bf:46:37:d4:3d:
+                    f3:b8:72:aa:b5:05:a5:94:31:2c:dc:9c:bb:e3:a1:
+                    1e:82:03:ca:29:b5:d4:64:43:51:4c:f2:77:04:bb:
+                    43:97:a3:a5:e7:56:13:47:4c:02:36:db:d9:53:20:
+                    da:bb:a6:d9:54:36:dc:76:15:e0:eb:d7:50:9c:72:
+                    66:6c:7d:59:2f:dd:51:d6:7c:b6:2d:f3:0e:f9:7a:
+                    cf:0f:63:59:3a:6b:8e:3f:c7:76:b0:11:1b:12:e5:
+                    b9:90:66:30:d2:f6:ba:04:de:33:27:58:61:8f:ed:
+                    ed:14:b8:df:2e:5a:b5:cc:ff:a3:72:1c:ef:46:09:
+                    37:1c:81:ca:13:e9:43:9f:38:fb:69:bb:64:21:9e:
+                    e4:b4:44:be:79:61:52:46:cf:a5:83:7f:b5:c7:91:
+                    5b:28:92:17:d2:5e:45:4b:ad:9f:c7:53:fb:1c:3c:
+                    e7:56:92:a7:9f:e7:e3:59:19:36:64:8c:8c:78:00:
+                    2a:f7:93:2d:79:90:91:e1:73:31:24:ca:4b:4d:9b:
+                    1e:35:61:8a:3f:0c:f8:c8:c0:76:6f:01:d8:2b:c1:
+                    e0:0a:f1:fb:c7:a0:2b:48:30:d4:30:bf:47:2b:e5:
+                    b9:8d:64:e1:d4:3a:80:71:60:1e:02:02:d2:1a:f2:
+                    2c:b2:88:d6:21:da:27:83:36:6c:51:45:e3:72:39:
+                    83:16:65:5c:58:e6:14:87:02:95:88:07:f9:b3:20:
+                    58:d9:91
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                48:07:DC:D8:10:D8:20:53:F5:7E:23:52:84:81:E7:2C:F3:16:BC:DB
+            X509v3 Authority Key Identifier: 
+                keyid:93:36:60:EE:DE:2D:B2:D2:D2:92:A3:FA:B0:62:EC:37:7E:7E:2C:F2
+
+    Signature Algorithm: sha1WithRSAEncryption
+        74:de:e4:e3:4c:54:07:25:ed:d5:a1:e2:7e:8b:63:1d:a7:2a:
+        e9:73:84:08:dc:00:33:99:12:60:73:c1:a7:a8:01:fa:48:35:
+        87:81:97:7a:86:fc:6d:67:fc:9e:ce:79:52:7f:7e:86:bf:ca:
+        08:cc:2d:58:ac:9b:49:d0:97:bb:75:82:79:93:52:e8:f1:26:
+        eb:1a:90:c3:1c:c3:23:6b:d2:18:a4:d4:11:2c:ac:7a:60:ce:
+        e8:77:b1:73:1a:0a:31:41:02:76:38:78:ef:95:cb:0e:60:35:
+        9e:6d:1f:dc:3a:fc:77:e2:dc:67:34:65:f2:c5:78:c2:96:f7:
+        d7:1a:24:66:61:5c:fb:b9:14:13:7e:5d:81:9e:7f:44:99:33:
+        db:9b:e1:89:89:d8:98:88:02:04:83:63:8b:c2:c5:cf:83:f0:
+        00:c3:81:47:e4:6d:c5:11:cb:13:6d:de:ca:37:f1:68:51:45:
+        ac:c2:8a:69:43:32:de:65:f1:10:09:e1:1c:60:6f:3b:49:39:
+        47:a8:bd:34:f2:a5:12:ec:dd:21:32:8f:41:9c:04:26:33:1a:
+        b3:1b:35:94:2b:69:fe:6c:b6:c2:6e:d0:76:89:09:51:27:e3:
+        f9:35:78:ee:e8:7a:28:3b:f6:60:42:55:82:bb:b0:9d:91:5c:
+        38:0b:9f:2e:02:c6:54:3c:55:0f:09:3c:37:81:52:48:12:1e:
+        42:6c:d7:e1:dd:e1:cc:13:60:fa:7a:e0:02:2d:7d:d9:4e:bb:
+        04:29:f8:8f:c3:73:17:f5:44:76:1d:cd:dc:12:39:0a:2b:a3:
+        fe:19:9d:4f:ed:c6:96:11:96:5a:b2:7e:88:f0:a4:5a:05:63:
+        d6:26:17:d2:98:22:59:f9:32:33:6e:a6:d8:ed:a6:b0:03:c4:
+        d7:18:1c:a9:67:b9:4c:d0:cc:23:f5:d3:36:f2:f4:93:4b:f2:
+        aa:06:91:27:c2:d8:a1:79:ea:88:58:e0:d3:6e:41:2a:2e:58:
+        48:7c:d1:a9:11:a0:c9:93:6b:9b:d1:60:e9:de:eb:a5:92:1c:
+        e3:e3:4b:96:01:6a:e8:21:01:c8:c7:cd:0b:fe:81:6b:8e:33:
+        4e:f0:05:17:c7:a0:de:9d:e6:78:88:7e:3c:1c:6d:51:04:49:
+        a4:4a:62:75:50:dc:55:a4:65:00:2d:c2:55:0b:16:39:24:e2:
+        f7:62:e0:75:d2:45:a2:60:94:36:84:ce:ed:70:9f:f0:eb:96:
+        b8:27:49:4f:a6:e7:52:49:7d:d1:8e:11:12:80:38:0c:92:ca:
+        3f:a2:2a:7b:87:a5:97:7e:a2:ba:b6:d7:ce:82:14:6d:42:52:
+        d5:17:b9:55:d2:a2:f6:0a
+-----BEGIN CERTIFICATE-----
+MIIF3zCCA8egAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMCUlUx
+EzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVyc2J1cmcxDTAL
+BgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRlc3RlcjEcMBoG
+CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wNzA1MjMwNzQxNDhaFw0yMTAx
+MjkwNzQxNDhaMGExCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRlMQ0w
+CwYDVQQKEwRUZXN0MQ0wCwYDVQQDEwRyb290MR8wHQYJKoZIhvcNAQkBFhByb290
+QGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7Zv
+BJ+BzESksfEMwXzXQZ2MEN/c8o+1E872A40Ls/C/uN3M9k0mjEGazQhgNmgNTVzD
+tSpxzDneGRlh+4BkXKxkuGbCLXcrjlyIgkFR+NiLNU+AZleMLqTLS0/DOGnsQ8cA
+ALAM2wFQ0ixusKWDityqCoSkTILzd+GposwgNoBlhmIrUEoshtDF8FJ7yloEqe0o
+V7wH5eOhp2ABaQnr5Sx1YVNK5wA7tuk84tKzmXbZX3CkfzzRRh6O6p2jwJXVpUW6
+aGT7klwjBS42UKoKaLojzZIpQvjW2BQJ5/JSxfGayJqqTrwmnxkAv0Y31D3zuHKq
+tQWllDEs3Jy746EeggPKKbXUZENRTPJ3BLtDl6Ol51YTR0wCNtvZUyDau6bZVDbc
+dhXg69dQnHJmbH1ZL91R1ny2LfMO+XrPD2NZOmuOP8d2sBEbEuW5kGYw0va6BN4z
+J1hhj+3tFLjfLlq1zP+jchzvRgk3HIHKE+lDnzj7abtkIZ7ktES+eWFSRs+lg3+1
+x5FbKJIX0l5FS62fx1P7HDznVpKnn+fjWRk2ZIyMeAAq95MteZCR4XMxJMpLTZse
+NWGKPwz4yMB2bwHYK8HgCvH7x6ArSDDUML9HK+W5jWTh1DqAcWAeAgLSGvIssojW
+IdongzZsUUXjcjmDFmVcWOYUhwKViAf5syBY2ZECAwEAAaN7MHkwCQYDVR0TBAIw
+ADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUw
+HQYDVR0OBBYEFEgH3NgQ2CBT9X4jUoSB5yzzFrzbMB8GA1UdIwQYMBaAFJM2YO7e
+LbLS0pKj+rBi7Dd+fizyMA0GCSqGSIb3DQEBBQUAA4ICAQB03uTjTFQHJe3VoeJ+
+i2Mdpyrpc4QI3AAzmRJgc8GnqAH6SDWHgZd6hvxtZ/yeznlSf36Gv8oIzC1YrJtJ
+0Je7dYJ5k1Lo8SbrGpDDHMMja9IYpNQRLKx6YM7od7FzGgoxQQJ2OHjvlcsOYDWe
+bR/cOvx34txnNGXyxXjClvfXGiRmYVz7uRQTfl2Bnn9EmTPbm+GJidiYiAIEg2OL
+wsXPg/AAw4FH5G3FEcsTbd7KN/FoUUWswoppQzLeZfEQCeEcYG87STlHqL008qUS
+7N0hMo9BnAQmMxqzGzWUK2n+bLbCbtB2iQlRJ+P5NXju6HooO/ZgQlWCu7CdkVw4
+C58uAsZUPFUPCTw3gVJIEh5CbNfh3eHME2D6euACLX3ZTrsEKfiPw3MX9UR2Hc3c
+EjkKK6P+GZ1P7caWEZZasn6I8KRaBWPWJhfSmCJZ+TIzbqbY7aawA8TXGBypZ7lM
+0Mwj9dM28vSTS/KqBpEnwtiheeqIWODTbkEqLlhIfNGpEaDJk2ub0WDp3uulkhzj
+40uWAWroIQHIx80L/oFrjjNO8AUXx6DeneZ4iH48HG1RBEmkSmJ1UNxVpGUALcJV
+CxY5JOL3YuB10kWiYJQ2hM7tcJ/w65a4J0lPpudSSX3RjhESgDgMkso/oip7h6WX
+fqK6ttfOghRtQlLVF7lV0qL2Cg==
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/recipient.csr b/t/data/smime/keys/recipient.csr
new file mode 100644
index 0000000..aeec080
--- /dev/null
+++ b/t/data/smime/keys/recipient.csr
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEvTCCAqUCAQAwYTELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+DTALBgNVBAoTBFRlc3QxDTALBgNVBAMTBHJvb3QxHzAdBgkqhkiG9w0BCQEWEHJv
+b3RAZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCv
+tm8En4HMRKSx8QzBfNdBnYwQ39zyj7UTzvYDjQuz8L+43cz2TSaMQZrNCGA2aA1N
+XMO1KnHMOd4ZGWH7gGRcrGS4ZsItdyuOXIiCQVH42Is1T4BmV4wupMtLT8M4aexD
+xwAAsAzbAVDSLG6wpYOK3KoKhKRMgvN34amizCA2gGWGYitQSiyG0MXwUnvKWgSp
+7ShXvAfl46GnYAFpCevlLHVhU0rnADu26Tzi0rOZdtlfcKR/PNFGHo7qnaPAldWl
+RbpoZPuSXCMFLjZQqgpouiPNkilC+NbYFAnn8lLF8ZrImqpOvCafGQC/RjfUPfO4
+cqq1BaWUMSzcnLvjoR6CA8optdRkQ1FM8ncEu0OXo6XnVhNHTAI229lTINq7ptlU
+Ntx2FeDr11CccmZsfVkv3VHWfLYt8w75es8PY1k6a44/x3awERsS5bmQZjDS9roE
+3jMnWGGP7e0UuN8uWrXM/6NyHO9GCTccgcoT6UOfOPtpu2QhnuS0RL55YVJGz6WD
+f7XHkVsokhfSXkVLrZ/HU/scPOdWkqef5+NZGTZkjIx4ACr3ky15kJHhczEkyktN
+mx41YYo/DPjIwHZvAdgrweAK8fvHoCtIMNQwv0cr5bmNZOHUOoBxYB4CAtIa8iyy
+iNYh2ieDNmxRReNyOYMWZVxY5hSHApWIB/mzIFjZkQIDAQABoBcwFQYJKoZIhvcN
+AQkHMQgTBjEyMzQ1NjANBgkqhkiG9w0BAQUFAAOCAgEAbD64fjpgPl4mN18HfZQP
+7EyK7cJew7iI0dLmP4rQmVLqWjl+xlvvZdYTbO+Y2OZMU7ihWb2Axcxd/oR7CxZe
+UL43yCWX5eXI0c07/7LGWSNUMdIE7GvOVkRt6o91TLBTLtr/pBoRj368S7SgpajW
+7cykGZroK84+zd2oNjt2aj6j82M+X5limFpEmqzD+wMlSjyNHVVky1RjWQs8wqvr
+Dqmr638aodc7N0KCI/5dfpIYDiHvhUaI8JjvUivFmS6KkYXF7q5uO7poSqs6LL0A
+3FBEnKprFVWkUs19PLfkEoRC7XPoqP5hZfBFPe4eKJvI0hoVlpNtgCf8O9mwMSM0
+AQNtbzZnNWx3ol4eFw3c0MYUCcIMCShtma0oCGbQkgd6NRkg81iMfqw+OevDde1J
+WarhQUsnejt1EUdjpUse/PwoyPSW02Vh4fu+1xrCL8oafPmLy2mxLRJ/qolNvNnG
+Fg3/CM8c7CRnbqStvOD1LdRxIiu0cuPvmsYFQ861EN7cACuQpjL+7nVAffAqamvp
+I7f5F4Qa4//6nfkbxJZZPd2qZyLruhFSdV4kSN8azUOu3ZW+OE/j7TbEdIpzfXZL
+XneNneGvM1jhgcSiqWGG7U6XmU+XT8WbBTtLM+TNOWdFNR/ex6wXsEgatDqesin3
+mJ4yWERo/b9OHNKXP70P/ks=
+-----END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/recipient.key b/t/data/smime/keys/recipient.key
new file mode 100644
index 0000000..1c6e0af
--- /dev/null
+++ b/t/data/smime/keys/recipient.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,5260BC3BD106CEDF
+
+9D9LKgdWDCOz+yoZ20XrIqI9OzEkEtf6OVlFLLFi9+3SwIATwbuNWgywPcVKn3BI
+xlwAw3zSsXaysWo+U4quXRZ8Dz0WyXtF9LB3EMuoaOUPwb8pJpuGg1CO+OLjrdq5
+0IHSsvwjRDPnbtmpDjsMHXP2NMsk3U1YW9YrbQSJNnxpxUqf9UhL9UwoIRYMFFjk
+v0QAe1vOtOTZfQA+kD5Gpunh+LLxQzmxmO5nvoN05JaCSN8zL77Gig3n18SP7uEW
+JPrBkNtEPUCIwA8TLzWXN1kkqr90GFkPFmM328FOSuK0JsMFagyBUZl6EpFJSRd+
+d+eKO1wUbPAMOIQsUOoXR9zysfcZqHHGAUxybefTT64U0MT60jdeiAeHl28bT8+c
+tNBG64CTwLov4WtwKUj/cE2Tux3TxBSNPitqwCA18bs/UaNeU6inVcO0msOAWPg6
+F+E7gLDHQWG9yGNCA9o8Uvh+Dplrc2pHOydUe6LSgOd4N4tKmXQl82HTMteb/Um0
+HJdb+U8lo3POKLy21REG6tD7QCPg1UNJ9CN08G4i41Tl1NdQRMWFuxnBYsdQRqKQ
+pqxXZeaE8Mpf+o/xCzS+D7qPon05sAxhvHoAc9ijFiSwL+ukvz1I7zoDa+MfoB9r
+vBFbrBaCewaT9Ff4kZ5ONe1h1Zie+rYA0yreXeN97ygGackgyHuStWRUFjVK7Q7K
+E7bEKZPzqg725GvYdKTnSbctGRNHYzE4PqFMY4aYmWYth2N6F4rMYymkEIzajypS
+GA9d6bkPgb9BuvjkUOSxETO2Mjcae4PrAUN5t6K1p6HHHuCjKObXHjpnExbPXlo1
+VWuNG8StMncSFaGKvS9IbV3l6S/UmjFN74zKvQQLUg/UCeWwAw7uv4sxp1+DdWfW
+J2kG/TSHgVb+01Wc6mEVmjBel8r2fBa8fkcLdEPWQi3NmeB2Nya2hXl4n/DNUm07
+g0hoWRNt2PVNdTfhsVWTGsCDYFipufxQ0jPsvuXHhN6PsQR6QlJHtPfftk1BMDiZ
+4wayHHejE+YPq5Pd1Yg9tWvWDrObrfEx3VbM1wuHKfJ3oqGL4KKsC87k0efg5aNP
+rMc11aqw/6mysyPQKWA7ZqJTF7aYKye+q02DFbsnpupPMjhpiBnMv+AcMHeMEVEy
+Y/ZyVntKS8djPckbpQZSm0YsURBTJbluQko21dXi71ammigKXmD0dF5ts79DauXb
+mz+Yfelq6EwETf12iHIOgb+ba1/5Hh8gj5GcNPqZwevOF1GWT1w303xUVjNR5eHO
+Tvn6T8YFh5+ffrodR1NaRHb9YTh5HA2NKvSKC0p8SOoorIUIOndhN1lIArEGfhEp
+/QQsn/N6OMqx8UDATV4zAXE47N1vwODoO4wG4dRGkVxVoJWCSbbuNi2ch7K1DuZb
+92SWIlnWKSNSdmj9uiiQcybebXj8DxjHzlOhmegk5N4eSVD4kUITMaEMgF/eSdTI
+p9qmi9F+6U3f6ZwXSFxhBWoSBq8pb3mDWHdXV8sZuH9mw7HEaKmbUyaSbOGJEtUj
+0CqtkneaBEhRofQY7VmzB0UmU43SgGa+0JRLsPadpP8YP+T66htGOQ3lzOtKd8Ur
+CnOg3uGvLPbL3H8d6RFk9bwWq3siwN1rMQIKCFprKNTjUjDAc6IonQH+J7RrT+dq
+QEPCV6ZjzSUOSQEB/0EaTrQILjaBRQfJ2bWg0rCAID9tdqa1Z0NvXjWNXV7My/TL
+EhEqEeP3/QC9BoKz0lr0HZrMeVxegSu2GT8oZ5mrWbVxb8ctE3zyHyCWih6NDfzC
+Mp2n93l44zNreuA2VXV5yH8Ap12G96R6p222Tv/nevURgsf3In0H6fqbzQgl9S3T
+A0mwGz/omosUQDILCJm+y3h1lCyePmNnBGInPFDfZ61oMHcsiHLZL5obxDSn5Qw/
+SX4kT38wa9uO5ExcFeskVD59ZZvt9C1aN0VsuNnQ5IeSjMqMLT7dHiidmhTu8lE5
+5ZJKGIhninECE/hgjMhsWWDnno+FX4Cfhut6tiS3KvqSbhwfx9zd+felMont8Zas
+mOWDlIKKIpMDNHgH2+ezTe7AGua7XBlcW8Wl/KEHupNdvqYABac3iMvrkYbBHRt6
+ouxG1x6S8e6WBbYBy6PtXHIgfd55PlH+2VYsMeWbSlxenlXxdMMRm/xyrDqtslrh
+R6z8T62bit5+QYGF+wChTQyWjjioEC9ksMUMPFCRPaOtxRLavmPFHATOYDeJwwZi
+13ZxIY0ikgUqW1XonCmertWo+C0Veqf1deI31lUpayRFYWzGQVW9SFmGddZpCDMN
+3zLy9cjYI09Ug0Ipo+biIdQZ/y56V62Tr8UBOQ491y3rqucWs9XD5h8xUGv4OqBi
+GM6zQgYN683zIKwThe9mc+VVsA1TlxDS6bjEvxWLS4jd+WL0eU9t1dAHGW0puc45
+fARmcr6+Qu7RGQG4foO0y87XBlMTiLDh2iAZ5OfD8OdSXT/7KbaJ9+3R64+isEqW
+jehgxzDJeu5JsXFOpKeoEgRsUrBIz990Bqia2CaiEfXy20KRcRXG2JuWpHgyYolO
+xzo2tE1hAlUxxWBxtlBSas++yRnF29BFfvKypp5rWSCkzthWKb4tZ6PFWgNuh4sH
+TSfISXmnfpz8M0W1vpB5IvxYBifXNW8iEWAhZopI6sSs20ZKYwHAHNKc8Jkt6UMY
+s7soruZrcf1waSzrEVOxzAGsg6HpO7pBOb52AjpMngecQujFNdtoyWLJj5GhkH93
+AbIXruRX8KonbNMDDHe3DbP7iOIwuKzv93hyDQpWffAQLTsTpPqMZBIXwU2g+ONT
+fGLzPcXQgSvMbYamoLCKRVy5W65dh/i+5OJXsjKQHuEny9Xol7xx20331AF26Qkw
+/dhxpdtlIlogroyfuc05V51fPunzlzg4Lykpq2mhMyknRCi1fyAZateo5dBF/Ec9
+wZCnxFwppbrtU1PGAfkWyX6na/6sNm9HDE4GXfgK+cwzAfF6Y0kQdA79YIUQPI5p
+eoTtuscrYLEVvPwrI4efNoPjuv8+TOtECg98L/XfJ74UUwva+DZf8XL6AOK7sReI
+EivgW8ZuaolZ4SljUxpdmj4soRsgkyoFJTbIDv1FnJe11xZT73BHTK80Q+rscg7M
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/sender.csr b/t/data/smime/keys/sender.csr
new file mode 100644
index 0000000..fe7c9ed
--- /dev/null
+++ b/t/data/smime/keys/sender.csr
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIE1jCCAr4CAQAwgZAxCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRl
+MRYwFAYDVQQHEw1TdC1QZXRlcnNidXJnMQ0wCwYDVQQKEwRUZXN0MRQwEgYDVQQL
+EwtTZW5kZXIgdW5pdDEPMA0GA1UEAxMGU2ViZGVyMR4wHAYJKoZIhvcNAQkBFg9z
+ZW5kZXJAdGVzdC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCX
+bJOzfVLNRcCJGqjHFDFKY4O9SftHAgTeU2Drtgp8ZDVWr3euNPc1vppZaljmQjXp
+j04FY67NXTsRVEKncNlJGnxDSrAlKOyv05RcG+w2CG0whdi7s+ods9UM8ir4o5fR
+3mOvlfHD89z3pOS5hejAmSOA1RjJ7iP1XIQ0HSZ/9CWxXJXiI6uYutmBKAqbAAOz
+WgDPtP8tBFpg2r7eZRJoNyf+GBuXaV+v/7Me6LFzQnJ6tyTFTm/mMSxWBHN0aun/
+Q0yQ6OYbSSmg63Bqm0STk9YtciZXjbbR+qGilu/KGrWZm1MwMHacjHDiWXMLVH5O
+ctJ7lIW//4d21h6gBLawFOFlKLS51j8RpQKOR4uLpHZlkoWCe5j1HY/x2ARwZANZ
+BroJVS0TiuRk3XAsbzNcJJdJGeGdPBuAtYqQ24Vdpa6X0jaq0A9Bgzq3k0ng9liD
+ot0a6Y9/bbCfjaAi5uytzWFi7SzIX7ZHK5EU1x7L6wRZdTQ1mAMX38KuaQ9Ok6eO
+AuMgTtiM9vbpw05KO0WQomBnabBXIrajc1l3ibwHExLM9aU0sAxTLnrlcJRFKqFj
+qNWpRaqHZ/Ru+TS/rV+zpzEt/cTwNV1sFEIX1IfpWYZTtq8ASk7Hlw2hqqOA3CfO
+uWyTZUULFA+HrM+ji5twOxBl9jRcnP1GKRs0Q/gc5wIDAQABoAAwDQYJKoZIhvcN
+AQEEBQADggIBABIeM0Jl08qSpBIEL/+MeiRqMJlexQ9awDBn5uB0KzFsISLX8rrP
+G7u1lCEhaFqPmdRDaeyYmN6naiFYNsbIazPHUXHVrbH+Bjax89wBvVw3Q2USdQ/D
+v9NHXVXNgdOxgz9AID7WYvy/xZLfEmbakfAMSLWbDyz6C+hCLVoPWpksO3CNKzsy
+0CS6hZVQS04EXKJeFQQEspr0hiKR0keb0sC3YcuNuYwyUFs/klFazdPIMk6esEkL
+udsgRPAoe2uX7yEx/abC54I62lsg1wBQ9PkWD9dpoW82jzbtvB/dVLqUWyYg57xq
+AU1IClcg8cQbmQaUbsjrAOrwuthR5xWH0Ybn1AwMxUn2z86b2FXYHZlruKlwFSlC
+v4Sz+Icgi9wQDKP4nZTvoc17XgUNjGIUN8xRovCdQ0pul5n6KKrNriRJcbNE9/NN
+b6dH5nUscF0xu4PEqVVt5QuzQioBVNS0jqedIhfyFwmnfdCMUEukMfrTt1DUnf8C
+89sYz66cfa9iNhM82Qgg8K9EdMyebOnxJOES6LZNGecB9n+co/WSATF/f9ih0ShN
+yjMLV05DEYBaitAxmfmZ/C92DZYagcwCUh6FMY9BfBWR0Ts780dl+iF1sn/uJzUd
+OAofD5wGxOIoC44McpnjDefdPMZIN5KFdogEFlKAhozDMC8hzpefeyy+
+-----END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/sender at example.com.crt b/t/data/smime/keys/sender at example.com.crt
new file mode 100644
index 0000000..0c00e95
--- /dev/null
+++ b/t/data/smime/keys/sender at example.com.crt
@@ -0,0 +1,38 @@
+-----BEGIN CERTIFICATE-----
+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
+fVryqg==
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/sender at example.com.key b/t/data/smime/keys/sender at example.com.key
new file mode 100644
index 0000000..a0a94ec
--- /dev/null
+++ b/t/data/smime/keys/sender at example.com.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,D9CF9FD45D1E5DB6
+
+zVl1wndooucgnGLNMqlyKel4sHt682eFMAu6nUipUgOAgDherDUF8lrQk0clhWd1
+UiE7nvXb5QURmJoQ3VVxVYVNQbUj78JBiofLHhGIZxF7vt1raKj676zFP4QPNqQj
+mFu6BLYfKFXjf6HR9QN9TxuTse748jPh0bWuoW3hWS0S4hihaXqQXviw5qNeHnY8
+UxjQ2jC72mIOgN7lz/5E/86XZTa02nIOK/Y7wCikWAxPGwQYcx6eeKxFM8JS3Kde
+ACyctnsRq/GQkFn3q/E+GmCQSQLHUdH5dd6sZ90f25PvERpsP39QPg6hsat1MC44
+CBhl//7fY6uBVjsy2PDkhHp16M+ENBQJENHhkxj49peTKuWzqIzA+yKCELXQ/j5f
+hBnseSFkca0RppPrnV79PTtHuRdeQzeJOWK2tlkIPEaUqlEW5UaQ6VViuqDgop7E
+1cSrNhsnUwLTNlHFymLIcCodL7rmbR+h4YLZ54wWAo16aOZnFDiHCB6EufAWGR9C
+P9tpC+1e2a1FQkVGCPAptyPymF6Cbvzf4rJIPq/oDat9OZQZTvpTI+owIhImQIXH
+SgfXkbYrhOaIURLudvt1lmRHh5mfmNGC6djJ1DUEenlhQNO0RWYKcqMMVa308Bab
+bZVrhPl0l+f00LiWcbR6H2VHEpPIOnycZBxmrMzXMe9buI9QrzlsjV9RRYSA83rW
+frXI5S61ryimXxUYIMoCk0XCO+Y69W6cZ8f9yJpYocQzXaIF3uYBLb3h7NNFGk0t
+qGR26+0M2+Oqm7asfJ4hRRHIAaUtPP78qCPxo2xY7RtAQYidgIhX73dQQjN4vYfa
+BRvV9S8qAyQoNdSIqhPOI4tMFeP/eKlSOjEGkQnoScY2vhCZkdEWlyoZej1gpf+Y
+q2PhXQDSrVzmqskEUNWmhN5LUDVTnT+phxwyQfOthdWRIYg88lg0hKmgS6IV7fU3
+/fcDpPiklITVmtQ6FQnCQzFnnaIcexL3UPr+KVQkwxsGTShf+vgi/VnPzVVVK39x
+hgWeO+vb0YPuABylKsEXdURlyXehOIM+sFLNzz8s5XAYpE0QthmxHtUGHnDp+Zwx
+1jiwH+pXGV/7mZfRiON69o3fZxkrVYojd8rYHcQl2oRdT5vT9cm52VX/plksWLnX
+TW/d25nrx7fP/cW9FDdJoWrrouGC6B2/Gllfgtd0Tf3uXMV4M7ocVIv16U8W8UDW
+3Cb7cKfHHpbDFR2MJWkDhMz83KYs9oLKBvU9AXdCuOMnzqdpiMDiHOWeX3qf5v1S
+Fz3gxvmClp6igdpAjdMCVdb7vI5DqPa+ir5Sd5/dskkTU06BnzvDoysnYlTcp25C
+a9zgif0+jSPW6npGY7wvII28Tulgq/hJqlulU7IiI5CxIYUp2na7l4whwmKZMf50
+hj8RLuY/lru5mDNSdJyBPaAm2/4/DTor7M3IGDGQi6uusoxPVQ5KEJ9RQ2h5MuqU
+2gg1hdwsk1+Y6I9wbBLLrKktSiXsvekbC4HGzAUfSBS3o5Fy/SdXfOrkdglSpXzZ
+lZF7W+wyyZE0tT6Tc52AGq6vZPhkcXDScFdniT200TEYSADp+SDFV1B8sxdLIEyK
+lh6bhEuT859oeT9lzTr/Djr84Y3HMjdXchEU6RY1bHLovOTdZVE3PbfVzgf93VWy
+LqraYR/1zpxmCLCWTrOdjSn0MMHnTQSViKmfJyLpa5p2r6098ImZDDhowzbpU3nn
+UnARcvQIcS6Ci5GyZ9PwYbnaQWeM/lJO3vU53Guk2OjWeVuP8TQOi2eRPIqh3TOw
+df9EkPh4g7rddbEipesIIBDcS2QB5Em7WZ4i5XJ9/4XqdMmcT4ptkHOQVonzLFRQ
+ssZps5Hot0doydbclaJ8h6b6T7YgiUOivjH/cT4vjkmwOD4JT8ZXOlNLe6SoIMfX
+gAZD69K4ZnfLezZ15fO6l0WSFcbAG84G+fJiBcmwC1cc7fo8M7LJTxd2Inzqge1d
+JPeNPFPTayEY99uF+1P6li2EEexleJS0q+KHMoLCldL5DHOotzEoCejQCni1/YZc
+mBJb7DS+qcVU/Mzn/JKpPuZEZIJJGCu/oV3wu4DvCktfL+6JIwiBNWTTPyBY+M8e
+kEKK2fAYtLuAGVtGbbSh35BJiGBgxFUc7RC1fuGHFGoRzun4LcGossMRzYrU1jP5
+GQkbkJvBN4shCgdYCckLwSL+dZK4k3gmYqvzwZvIp+E1+5BgbwlBruAKMMB+A5id
+T1aOG/llQxbpeVk9sNuUZm10FZhTv6buaqM7uAQf4vSekLoTLlUmUkzzJRErJQiQ
+97IKe5vwj3XumyDTq5nGwXklIpRdX3BNN6O8edPQf5xxpeOhh/BZkB1pJdVcKbf7
+qGf2xoStC4KgZ7sJn7sn2ypJjrgNAk9JUtpxJOYF9Kylq1h3Z1EIC4yNGadcn8l7
+SEUsYO7Bw/Vaqq8JsQWfAEFwCmz37aaZpQmpfB2/OyKp+IbmU3og3aek2S21QfFy
+u7gWk6LEvfzVhh8OPmGlgCNJOopkxv5bwlFTQNFGI8YY0kQrWgdNdtlQcPFSl6kt
+fu12/s0T5k56SKvM5QdVfUa3c8dDPq6H4aHlBZnSK74KBS1tvdxWsh6lY6sZdt7c
+c/sBXxaS5AgtTuwjc8HOSOQhv/+hvUnRbDZROBzk6Hp/QWpX13yD9r91eCYysQ80
+10uc7dsH9mRpkaGQA3QPZlD6Ld/2p8h/Hsf/rKQsyOzswdZokmExLnRgVUhVoJ/d
+SoLVfBghHWAD/hvUKhBBPEwp8m9OKm8BF8jF5HsbTL12sBiUeOLpyrJTkRHuiZQn
+Pte4hEimyi835bCop4YbzCDVpbuh1RCgHDpkSSGwqpbiaVJbQCNwGPu9WLezDGl8
+IBwdJDmeg8obAhD+PVETY/wZOaAaE1zrDyoB6Stp5u6bWmO8r5xhrEkjJg2waNpb
+3R/vN6O7G49ZZn+f2D8AI50Fu6LWDuBXKA5IhAH0FQhex48f5Idklvazug+Wxaqp
+zJA1jo+DsmjWuUzcB1PLQr+2zi7PyuhjWKcJuPIz3JD2SYdi/wkyJWNotmiB9heL
+gBMMRpteVNXi0MfCwE5RELU6LUPG4MTon8F+k/I7Y78v4vTiX6/B3ouK9RHCa1d+
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/sender at example.com.pem b/t/data/smime/keys/sender at example.com.pem
new file mode 100644
index 0000000..67e6f54
--- /dev/null
+++ b/t/data/smime/keys/sender at example.com.pem
@@ -0,0 +1,92 @@
+-----BEGIN CERTIFICATE-----
+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
+fVryqg==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,D9CF9FD45D1E5DB6
+
+zVl1wndooucgnGLNMqlyKel4sHt682eFMAu6nUipUgOAgDherDUF8lrQk0clhWd1
+UiE7nvXb5QURmJoQ3VVxVYVNQbUj78JBiofLHhGIZxF7vt1raKj676zFP4QPNqQj
+mFu6BLYfKFXjf6HR9QN9TxuTse748jPh0bWuoW3hWS0S4hihaXqQXviw5qNeHnY8
+UxjQ2jC72mIOgN7lz/5E/86XZTa02nIOK/Y7wCikWAxPGwQYcx6eeKxFM8JS3Kde
+ACyctnsRq/GQkFn3q/E+GmCQSQLHUdH5dd6sZ90f25PvERpsP39QPg6hsat1MC44
+CBhl//7fY6uBVjsy2PDkhHp16M+ENBQJENHhkxj49peTKuWzqIzA+yKCELXQ/j5f
+hBnseSFkca0RppPrnV79PTtHuRdeQzeJOWK2tlkIPEaUqlEW5UaQ6VViuqDgop7E
+1cSrNhsnUwLTNlHFymLIcCodL7rmbR+h4YLZ54wWAo16aOZnFDiHCB6EufAWGR9C
+P9tpC+1e2a1FQkVGCPAptyPymF6Cbvzf4rJIPq/oDat9OZQZTvpTI+owIhImQIXH
+SgfXkbYrhOaIURLudvt1lmRHh5mfmNGC6djJ1DUEenlhQNO0RWYKcqMMVa308Bab
+bZVrhPl0l+f00LiWcbR6H2VHEpPIOnycZBxmrMzXMe9buI9QrzlsjV9RRYSA83rW
+frXI5S61ryimXxUYIMoCk0XCO+Y69W6cZ8f9yJpYocQzXaIF3uYBLb3h7NNFGk0t
+qGR26+0M2+Oqm7asfJ4hRRHIAaUtPP78qCPxo2xY7RtAQYidgIhX73dQQjN4vYfa
+BRvV9S8qAyQoNdSIqhPOI4tMFeP/eKlSOjEGkQnoScY2vhCZkdEWlyoZej1gpf+Y
+q2PhXQDSrVzmqskEUNWmhN5LUDVTnT+phxwyQfOthdWRIYg88lg0hKmgS6IV7fU3
+/fcDpPiklITVmtQ6FQnCQzFnnaIcexL3UPr+KVQkwxsGTShf+vgi/VnPzVVVK39x
+hgWeO+vb0YPuABylKsEXdURlyXehOIM+sFLNzz8s5XAYpE0QthmxHtUGHnDp+Zwx
+1jiwH+pXGV/7mZfRiON69o3fZxkrVYojd8rYHcQl2oRdT5vT9cm52VX/plksWLnX
+TW/d25nrx7fP/cW9FDdJoWrrouGC6B2/Gllfgtd0Tf3uXMV4M7ocVIv16U8W8UDW
+3Cb7cKfHHpbDFR2MJWkDhMz83KYs9oLKBvU9AXdCuOMnzqdpiMDiHOWeX3qf5v1S
+Fz3gxvmClp6igdpAjdMCVdb7vI5DqPa+ir5Sd5/dskkTU06BnzvDoysnYlTcp25C
+a9zgif0+jSPW6npGY7wvII28Tulgq/hJqlulU7IiI5CxIYUp2na7l4whwmKZMf50
+hj8RLuY/lru5mDNSdJyBPaAm2/4/DTor7M3IGDGQi6uusoxPVQ5KEJ9RQ2h5MuqU
+2gg1hdwsk1+Y6I9wbBLLrKktSiXsvekbC4HGzAUfSBS3o5Fy/SdXfOrkdglSpXzZ
+lZF7W+wyyZE0tT6Tc52AGq6vZPhkcXDScFdniT200TEYSADp+SDFV1B8sxdLIEyK
+lh6bhEuT859oeT9lzTr/Djr84Y3HMjdXchEU6RY1bHLovOTdZVE3PbfVzgf93VWy
+LqraYR/1zpxmCLCWTrOdjSn0MMHnTQSViKmfJyLpa5p2r6098ImZDDhowzbpU3nn
+UnARcvQIcS6Ci5GyZ9PwYbnaQWeM/lJO3vU53Guk2OjWeVuP8TQOi2eRPIqh3TOw
+df9EkPh4g7rddbEipesIIBDcS2QB5Em7WZ4i5XJ9/4XqdMmcT4ptkHOQVonzLFRQ
+ssZps5Hot0doydbclaJ8h6b6T7YgiUOivjH/cT4vjkmwOD4JT8ZXOlNLe6SoIMfX
+gAZD69K4ZnfLezZ15fO6l0WSFcbAG84G+fJiBcmwC1cc7fo8M7LJTxd2Inzqge1d
+JPeNPFPTayEY99uF+1P6li2EEexleJS0q+KHMoLCldL5DHOotzEoCejQCni1/YZc
+mBJb7DS+qcVU/Mzn/JKpPuZEZIJJGCu/oV3wu4DvCktfL+6JIwiBNWTTPyBY+M8e
+kEKK2fAYtLuAGVtGbbSh35BJiGBgxFUc7RC1fuGHFGoRzun4LcGossMRzYrU1jP5
+GQkbkJvBN4shCgdYCckLwSL+dZK4k3gmYqvzwZvIp+E1+5BgbwlBruAKMMB+A5id
+T1aOG/llQxbpeVk9sNuUZm10FZhTv6buaqM7uAQf4vSekLoTLlUmUkzzJRErJQiQ
+97IKe5vwj3XumyDTq5nGwXklIpRdX3BNN6O8edPQf5xxpeOhh/BZkB1pJdVcKbf7
+qGf2xoStC4KgZ7sJn7sn2ypJjrgNAk9JUtpxJOYF9Kylq1h3Z1EIC4yNGadcn8l7
+SEUsYO7Bw/Vaqq8JsQWfAEFwCmz37aaZpQmpfB2/OyKp+IbmU3og3aek2S21QfFy
+u7gWk6LEvfzVhh8OPmGlgCNJOopkxv5bwlFTQNFGI8YY0kQrWgdNdtlQcPFSl6kt
+fu12/s0T5k56SKvM5QdVfUa3c8dDPq6H4aHlBZnSK74KBS1tvdxWsh6lY6sZdt7c
+c/sBXxaS5AgtTuwjc8HOSOQhv/+hvUnRbDZROBzk6Hp/QWpX13yD9r91eCYysQ80
+10uc7dsH9mRpkaGQA3QPZlD6Ld/2p8h/Hsf/rKQsyOzswdZokmExLnRgVUhVoJ/d
+SoLVfBghHWAD/hvUKhBBPEwp8m9OKm8BF8jF5HsbTL12sBiUeOLpyrJTkRHuiZQn
+Pte4hEimyi835bCop4YbzCDVpbuh1RCgHDpkSSGwqpbiaVJbQCNwGPu9WLezDGl8
+IBwdJDmeg8obAhD+PVETY/wZOaAaE1zrDyoB6Stp5u6bWmO8r5xhrEkjJg2waNpb
+3R/vN6O7G49ZZn+f2D8AI50Fu6LWDuBXKA5IhAH0FQhex48f5Idklvazug+Wxaqp
+zJA1jo+DsmjWuUzcB1PLQr+2zi7PyuhjWKcJuPIz3JD2SYdi/wkyJWNotmiB9heL
+gBMMRpteVNXi0MfCwE5RELU6LUPG4MTon8F+k/I7Y78v4vTiX6/B3ouK9RHCa1d+
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/mails/README b/t/data/smime/mails/README
new file mode 100644
index 0000000..6bb0ee9
--- /dev/null
+++ b/t/data/smime/mails/README
@@ -0,0 +1,16 @@
+Files with MIME entities generated and encryted with testkeys/sender.crt
+in real MUA(look into files for User-Agent)
+
+* simple-txt-enc.eml
+    simple plain/text message with 'test' content
+
+* with-text-attachment.eml
+    multipart message with a text part and a text attachment.
+    Content of the text is 'test'. Name of the attachment
+    is 'attachment.txt' and content is 'text attachment'.
+
+* with-bin-attachment.eml
+    multipart message with a text part and a binary attachment.
+    Content of the text is 'test'. Name of the attachment
+    is 'attachment.bin' and content is 32 random bytes.
+
diff --git a/t/data/smime/mails/simple-txt-enc.eml b/t/data/smime/mails/simple-txt-enc.eml
new file mode 100644
index 0000000..df38c4a
--- /dev/null
+++ b/t/data/smime/mails/simple-txt-enc.eml
@@ -0,0 +1,36 @@
+Date: Fri, 22 Dec 2006 05:01:04 +0300
+From: root at localhost
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
+User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
+MIME-Version: 1.0
+To: sender at test.com
+Subject: test
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggQ8MIIBjgIBADB2MGIxCzAJBgNVBAYTAlpBMSUwIwYD
+VQQKExxUaGF3dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVy
+c29uYWwgRnJlZW1haWwgSXNzdWluZyBDQQIQBgzQJigV55Om1dc1u4CuYzANBgkqhkiG9w0B
+AQEFAASCAQBEI9JKBQgIqLcRTiYCwDHR1dVTnlOoAAVHoM10dHxzszYBNV5GBnQgPVzV5EbU
+kkjKPJy1Ipv6Eixoqed2u54/68fmvGSEC+zH8nu7noMbvF7nuspPfwawf9GQNq3jt3qZRuuk
+Us2EB5GGz8p9gxgFnv/GtrUQ+7HxCVvJRwFuyXkqwfqo2kxnE4C1jS05xjZ3ioo5gQlncC5f
+ib63YtU6Gvlnh9zq/LV0bMUg6SygRNAHoO+BTKBBHlzNMg0ixUESzRmxF1hJA3rbhCx4xPfe
+OHZh6NtSHPSD+88nzK3qBv1Gosz9In6O5/aYreQgLT6Vrbb+jlCPs3BJGaRPNwVKMIICpgIB
+ADCBjTCBhzELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0
+LVBldGVyc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRl
+c3RlcjEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbQIBAzANBgkqhkiG9w0BAQEFAASC
+AgAqLoqQoTHSNgSyp+8XuylkokpE8/zE8mQ0TVnVrP8LrK8ppxBAkVr8/GGG2BNtwKXJEaEP
+7cDoHYABQS4xQD3CrEBtq180rDR0yGunRYuCTlBrAAZWy8Nq3/KGbwxJJBaqHUomaqiRUeiD
+j+V42pU1hVhBVUR+dNlDrtnLyh1OhbR1/ddhU0WCioAAdbyVCntgyHQU0Sr55xVINP69RUte
+OCHQj+s5b6HnfN2pQjdjZf5pHJdCajO5IpKn68LaBY2Q0YZVZYs3PLnRe0yHpZvHa7T5KRNs
+XL4fkJ5n6wrP48UH2eHyopHCww28qZupbnSkhZCERa7mJ3niExaRxnZnS28IpaU4AlbMLZGu
+fz7woFxcxrcwkGeGXCTxZkj6UkXXFZK/s04iWoQdcN59A56yGc0RTHg29AHQshJ2d3Ydcm+N
+/Uv9OwSkDOG/V6f6BvmWjBldF6SI2sOmEEK+SQy0OC81TYJfqVHRfT0lyUfz43JGGwOhgb1j
+hyxBgugzGq2+KkIdyONMdBZXe4HuZOm4MStB/5NOdb1lPttB5zgkcdq9I/rVl+QRSh3wjZWf
+2JL9HvlljweVNbVoCiwQjm17u14PnNlW0797YTXizHlsLUpupMgI8N2eDKv53cKTGyT/z+yc
+AmqCYdph3oA7VWlXBvQbZUeDxOarpfTnq+aOEjCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE
+CBUAZSknCBIhoIAEYHg/qMyTJn4qoHtG5PnCve9BA5m+02B8RFLe1EQ/4+S3r2tP8jSvCPvk
+jMrzK7wrza2xlXaOisFvakPMyjTqwCkup55n3LDELbXMe3eFt62L7mWSD1HI+bwCEM2d7v/5
+DAQIbHw1VJzs8tsAAAAAAAAAAAAA
diff --git a/t/data/smime/mails/with-bin-attachment.eml b/t/data/smime/mails/with-bin-attachment.eml
new file mode 100644
index 0000000..4f4f89d
--- /dev/null
+++ b/t/data/smime/mails/with-bin-attachment.eml
@@ -0,0 +1,45 @@
+Date: Fri, 22 Dec 2006 06:41:22 +0300
+From: root at localhost
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
+User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
+MIME-Version: 1.0
+To:  sender at test.com
+Subject: test
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+AA==
diff --git a/t/data/smime/mails/with-text-attachment.eml b/t/data/smime/mails/with-text-attachment.eml
new file mode 100644
index 0000000..e18c759
--- /dev/null
+++ b/t/data/smime/mails/with-text-attachment.eml
@@ -0,0 +1,44 @@
+Date: Fri, 22 Dec 2006 05:24:50 +0300
+From: root at localhost
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
+User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
+MIME-Version: 1.0
+To: sender at test.com
+Subject: test
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+qA5aPVUAAAAAAAAAAAAA

commit 2187e0bf4bc21d425f8978d24a909a6d7d572f35
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:50:26 2010 +0300

    tests for SMIME

diff --git a/t/mail/smime/smime-incoming.t b/t/mail/smime/smime-incoming.t
new file mode 100644
index 0000000..ed59ee9
--- /dev/null
+++ b/t/mail/smime/smime-incoming.t
@@ -0,0 +1,237 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test tests => 47;
+
+my $openssl = RT::Test->find_executable('openssl');
+plan skip_all => 'openssl executable is required.'
+    unless $openssl;
+
+use File::Temp;
+use IPC::Run3 'run3';
+use String::ShellQuote 'shell_quote';
+use RT::Tickets;
+use FindBin;
+use Cwd 'abs_path';
+
+# catch any outgoing emails
+RT::Test->set_mail_catcher;
+
+my $keys = RT::Test::get_abs_relocatable_dir(
+    (File::Spec->updir()) x 2,
+    qw(data smime keys),
+);
+
+my $keyring = RT::Test->new_temp_dir(
+    crypt => smime => 'smime_keyring'
+);
+
+RT->Config->Set( Crypt =>
+    Incoming => ['SMIME'],
+    Outgoing => 'SMIME',
+);
+RT->Config->Set( SMIME =>
+    Enable => 1,
+    OutgoingMessagesFormat => 'RFC',
+    Passphrase => {
+        'sender at example.com' => '123456',
+    },
+    OpenSSL => $openssl,
+    Keyring => $keyring,
+);
+RT->Config->Set( GnuPG => Enable => 0 );
+
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
+
+RT::Test->import_smime_key('sender at example.com');
+
+my $mails = RT::Test::find_relocatable_path( 'data', 'smime', 'mails' );
+
+my ($url, $m) = RT::Test->started_ok;
+# configure key for General queue
+$m->get( $url."?user=root;pass=password" );
+$m->content_like(qr/Logout/, 'we did log in');
+$m->get( $url.'/Admin/Queues/');
+$m->follow_link_ok( {text => 'General'} );
+$m->submit_form( form_number => 3,
+		 fields      => { CorrespondAddress => 'sender at example.com' } );
+
+my $mail = RT::Test->open_mailgate_ok($url);
+print $mail <<EOF;
+From: root\@localhost
+To: rt\@$RT::rtname
+Subject: This is a test of new ticket creation as root
+
+Blah!
+Foob!
+EOF
+RT::Test->close_mailgate_ok($mail);
+
+{
+    my $tick = RT::Test->last_ticket;
+    is( $tick->Subject,
+        'This is a test of new ticket creation as root',
+        "Created the ticket"
+    );
+    my $txn = $tick->Transactions->First;
+    like(
+        $txn->Attachments->First->Headers,
+        qr/^X-RT-Incoming-Encryption: Not encrypted/m,
+        'recorded incoming mail that is not encrypted'
+    );
+    like( $txn->Attachments->First->Content, qr'Blah');
+}
+
+{
+    # test for encrypted mail
+    my $buf = '';
+    run3(
+        shell_quote(
+            qw(openssl smime -encrypt  -des3),
+            -from    => 'root at localhost',
+            -to      => 'rt@' . $RT::rtname,
+            -subject => "Encrypted message for queue",
+            File::Spec->catfile( $keys, 'sender at example.com.crt' ),
+        ),
+        \"Subject: test\n\norzzzzzz",
+        \$buf,
+        \*STDERR
+    );
+
+    my ($status, $tid) = RT::Test->send_via_mailgate( $buf );
+    ok !$status, "executed gate";
+
+    my $tick = RT::Ticket->new( $RT::SystemUser );
+    $tick->Load( $tid );
+    is( $tick->Subject, 'Encrypted message for queue',
+        "Created the ticket"
+    );
+
+    my $txn = $tick->Transactions->First;
+    my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
+    is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+        'Success',
+        'recorded incoming mail that is encrypted'
+    );
+    is( $msg->GetHeader('X-RT-Privacy'),
+        'SMIME',
+        'recorded incoming mail that is encrypted'
+    );
+    like( $attach->Content, qr'orz');
+
+    is( $orig->GetHeader('Content-Type'), 'application/x-rt-original-message');
+}
+
+{
+    my $message = RT::Test->file_content([$mails, 'simple-txt-enc.eml']);
+    my ($status, $tid) = RT::Test->send_via_mailgate( $message );
+    ok !$status, "executed gate";
+
+    my $tick = RT::Ticket->new( $RT::SystemUser );
+    $tick->Load( $tid );
+    ok( $tick->Id, "found ticket " . $tick->Id );
+    is( $tick->Subject, 'test', 'Created the ticket' );
+
+    my $txn = $tick->Transactions->First;
+    my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
+    is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+        'Success',
+        'recorded incoming mail that is encrypted'
+    );
+    is( $msg->GetHeader('X-RT-Privacy'),
+        'SMIME',
+        'recorded incoming mail that is encrypted'
+    );
+    ok( $msg->GetHeader('User-Agent'), 'header is there');
+    like( $attach->Content, qr'test');
+}
+
+{
+    my $message = RT::Test->file_content([$mails, 'with-text-attachment.eml']);
+    my ($status, $tid) = RT::Test->send_via_mailgate( $message );
+    ok !$status, "executed gate";
+
+    my $tick = RT::Ticket->new( $RT::SystemUser );
+    $tick->Load( $tid );
+    ok( $tick->Id, "found ticket " . $tick->Id );
+    is( $tick->Subject, 'test', 'Created the ticket' );
+    my $txn = $tick->Transactions->First;
+    my @attachments = @{ $txn->Attachments->ItemsArrayRef };
+    is( @attachments, 4, '4 attachments: top, two parts and orig' );
+
+    is( $attachments[0]->GetHeader('X-RT-Incoming-Encryption'),
+        'Success',
+        'recorded incoming mail that is encrypted'
+    );
+    ok( $attachments[0]->GetHeader('User-Agent'), 'header is there' );
+    like( $attachments[1]->Content, qr'test' );
+    like( $attachments[2]->Content, qr'text attachment' );
+    is( $attachments[2]->Filename, 'attachment.txt' );
+}
+
+{
+    my $message = RT::Test->file_content([$mails, 'with-bin-attachment.eml']);
+    my ($status, $tid) = RT::Test->send_via_mailgate( $message );
+    ok !$status, "executed gate";
+
+    my $tick = RT::Ticket->new( $RT::SystemUser );
+    $tick->Load( $tid );
+    ok( $tick->Id, "found ticket " . $tick->Id );
+    is( $tick->Subject, 'test', 'Created the ticket' );
+    my $txn = $tick->Transactions->First;
+    my @attachments = @{ $txn->Attachments->ItemsArrayRef };
+    is( @attachments, 4, '4 attachments: top, two parts and orig' );
+
+    is( $attachments[0]->GetHeader('X-RT-Incoming-Encryption'),
+        'Success',
+        'recorded incoming mail that is encrypted'
+    );
+    ok( $attachments[0]->GetHeader('User-Agent'), 'header is there');
+    like( $attachments[1]->Content, qr'test');
+    is( $attachments[2]->Filename, 'attachment.bin' );
+}
+
+{
+    my $buf = '';
+
+    run3(
+        join(
+            ' ',
+            shell_quote(
+                RT->Config->Get('SMIME')->{'OpenSSL'},
+                qw( smime -sign -nodetach -passin pass:123456),
+                -signer => File::Spec->catfile( $keys, 'recipient.crt' ),
+                -inkey  => File::Spec->catfile( $keys, 'recipient.key' ),
+            ),
+            '|',
+            shell_quote(
+                qw(openssl smime -encrypt -des3),
+                -from    => 'root at localhost',
+                -to      => 'rt@' . RT->Config->Get('rtname'),
+                -subject => "Encrypted and signed message for queue",
+                File::Spec->catfile( $keys, 'sender at example.com.crt' ),
+            )),
+            \"Subject: test\n\norzzzzzz",
+            \$buf,
+            \*STDERR
+    );
+
+    my ($status, $tid) = RT::Test->send_via_mailgate( $buf );
+
+    my $tick = RT::Ticket->new( $RT::SystemUser );
+    $tick->Load( $tid );
+    ok( $tick->Id, "found ticket " . $tick->Id );
+    is( $tick->Subject, 'Encrypted and signed message for queue',
+        "Created the ticket"
+    );
+
+    my $txn = $tick->Transactions->First;
+    my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
+    is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+        'Success',
+        'recorded incoming mail that is encrypted'
+    );
+    like( $attach->Content, qr'orzzzz');
+}
+
diff --git a/t/mail/smime/smime-outgoing.t b/t/mail/smime/smime-outgoing.t
new file mode 100644
index 0000000..31e6b5b
--- /dev/null
+++ b/t/mail/smime/smime-outgoing.t
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More;
+eval 'use RT::Test; 1'
+    or plan skip_all => 'requires 3.7 to run tests.';
+
+plan tests => 10;
+
+use IPC::Run3 'run3';
+use Cwd 'abs_path';
+use RT::Interface::Email;
+
+use_ok('RT::Crypt::SMIME');
+
+RT::Config->Set( 'MailCommand' => 'sendmail'); # we intercept MIME::Entity::send
+
+RT->Config->Set( 'OpenSSLPath', '/usr/bin/openssl' );
+RT->Config->Set( 'SMIMEKeys', abs_path('testkeys') );
+RT->Config->Set( 'SMIMEPasswords', {'sender at example.com' => '123456'} );
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::SMIME' );
+
+RT::Handle->InsertData('etc/initialdata');
+
+my ($url, $m) = RT::Test->started_ok;
+# configure key for General queue
+$m->get( $url."?user=root;pass=password" );
+$m->content_like(qr/Logout/, 'we did log in');
+$m->get( $url.'/Admin/Queues/');
+$m->follow_link_ok( {text => 'General'} );
+$m->submit_form( form_number => 3,
+		 fields      => { CorrespondAddress => 'sender at example.com' } );
+
+my $user = RT::User->new($RT::SystemUser);
+ok($user->LoadByEmail('root at localhost'), "Loaded user 'root'");
+diag $user->Id;
+ok($user->Load('root'), "Loaded user 'root'");
+is( $user->EmailAddress, 'root at localhost' );
+my $val = $user->FirstCustomFieldValue('PublicKey');
+# XXX: delete if it's already there
+unless (defined $val) {
+    local $/;
+    open my $fh, 'testkeys/recipient.crt' or die $!;
+    $user->AddCustomFieldValue( Field => 'PublicKey', Value => <$fh> );
+    $val = $user->FirstCustomFieldValue('PublicKey');
+}
+
+no warnings 'once';
+local *MIME::Entity::send = sub {
+    my $mime_obj = shift;
+    my ($buf, $err);
+    ok(eval { run3([qw(openssl smime -decrypt -passin pass:123456 -inkey testkeys/recipient.key -recip testkeys/recipient.crt)],
+	 \$mime_obj->as_string, \$buf, \$err) }, 'can decrypt');
+    diag $err if $err;
+    diag "Error code: $?" if $?;
+    like($buf, qr'This message has been automatically generated in response');
+    # XXX: check signature as wel
+};
+
+
+RT::Interface::Email::Gateway( {queue => 1, action => 'correspond',
+			       message => 'From: root at localhost
+To: rt at example.com
+Subject: This is a test of new ticket creation as an unknown user
+
+Blah!
+Foob!'});
+
+my $tickets = RT::Tickets->new($RT::SystemUser);
+$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
+$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
+my $tick = $tickets->First();
+ok ($tick->Id, "found ticket ".$tick->Id);

commit e36a5d28a93e0d43c54654d4171a3b754f1f1406
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 1 23:51:11 2010 +0300

    SMIME crypt implementation

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
new file mode 100644
index 0000000..fd472fc
--- /dev/null
+++ b/lib/RT/Crypt/SMIME.pm
@@ -0,0 +1,470 @@
+
+use strict;
+use warnings;
+
+package RT::Crypt::SMIME;
+use base 'RT::Crypt::Base';
+
+use IPC::Run3 0.036 'run3';
+use String::ShellQuote 'shell_quote';
+use RT::Util 'safe_run_child';
+
+{ my $cache = shift;
+sub OpenSSLPath {
+    return $cache ||= RT->Config->Get('SMIME')->{'OpenSSL'};
+} }
+
+sub SignEncrypt {
+    my $self = shift;
+    my %args = (
+        Entity => undef,
+
+        Sign => 1,
+        Signer => undef,
+        Passphrase => undef,
+
+        Encrypt => 1,
+        Recipients => undef,
+
+        @_
+    );
+
+    my $entity = $args{'Entity'};
+
+    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
+    }
+
+    my %res = (exit_code => 0, status => []);
+
+    my @addresses =
+        map $_->address,
+        Email::Address->parse($_),
+        grep defined && length,
+        map $entity->head->get($_),
+        qw(To Cc Bcc);
+
+    my @keys;
+    foreach my $address ( @addresses ) {
+        $RT::Logger->debug( "Considering encrypting message to " . $address );
+        my $user = RT::User->new( $RT::SystemUser );
+        $user->LoadByEmail( $address );
+
+        my $key;
+        $key = $user->FirstCustomFieldValue('PublicKey') if $user->id;
+        unless ( $key ) {
+            $res{'exit_code'} = 1;
+            my $reason = 'Key not found';
+            push @{ $res{'status'} }, {
+                Operation  => 'RecipientsCheck',
+                Status     => 'ERROR',
+                Message    => "Recipient '$address' is unusable, the reason is '$reason'",
+                Recipient  => $address,
+                Reason     => $reason,
+            };
+            next;
+        }
+
+        my $expire = $self->GetKeyExpiration( $user );
+        unless ( $expire ) {
+            # we continue here as it's most probably a problem with the key,
+            # so later during encryption we'll get verbose errors
+            $RT::Logger->error(
+                "Trying to send an encrypted message to ". $address
+                .", but we couldn't get expiration date of the key."
+            );
+        }
+        elsif ( $expire->Diff( time ) < 0 ) {
+            $res{'exit_code'} = 1;
+            my $reason = 'Key expired';
+            push @{ $res{'status'} }, {
+                Operation  => 'RecipientsCheck',
+                Status     => 'ERROR',
+                Message    => "Recipient '$address' is unusable, the reason is '$reason'",
+                Recipient  => $address,
+                Reason     => $reason,
+            };
+            next;
+        }
+        push @keys, $key;
+    }
+    return %res if $res{'exit_code'};
+
+    foreach my $key ( @keys ) {
+        my $key_file = File::Temp->new;
+        print $key_file $key;
+        $key = $key_file;
+    }
+
+    my $opts = RT->Config->Get('SMIME');
+
+    $entity->make_multipart('mixed', Force => 1);
+    my ($buf, $err) = ('', '');
+    {
+        local $ENV{'SMIME_PASS'};
+        
+        my @command;
+        if ( $args{'Sign'} ) {
+            $ENV{'SMIME_PASS'} = $args{'Passphrase'};
+            push @command, join ' ', shell_quote(
+                $self->OpenSSLPath, qw(smime -sign -passin env:SMIME_PASS),
+                -signer => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
+                -inkey  => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
+            );
+        }
+        if ( $args{'Encrypt'} ) {
+            push @command, join ' ', shell_quote(
+                $self->OpenSSLPath, qw(smime -encrypt -des3),
+                map { $_->filename } @keys
+            );
+        }
+
+        local $SIG{'CHLD'} = 'DEFAULT';
+        safe_run_child { run3(
+            join( ' | ', @command ),
+            \$entity->parts(0)->stringify,
+            \$buf, \$err
+        ) };
+    }
+    $RT::Logger->debug( "openssl stderr: " . $err ) if length $err;
+
+    my $tmpdir = File::Temp::tempdir( TMPDIR => 1, CLEANUP => 1 );
+    my $parser = MIME::Parser->new();
+    $parser->output_dir($tmpdir);
+    my $newmime = $parser->parse_data($buf);
+
+    $entity->parts([$newmime]);
+    $entity->make_singlepart;
+
+    return %res;
+}
+
+sub VerifyDecrypt {
+    my $self = shift;
+    my %args = (
+        Info      => undef,
+        Detach    => 1,
+        SetStatus => 1,
+        AddStatus => 0,
+        @_
+    );
+
+    my %res;
+
+    my $item = $args{'Info'};
+    if ( $item->{'Type'} eq 'signed' ) {
+        my $status_on;
+        if ( $item->{'Format'} eq 'RFC3156' ) {
+            $status_on = $item->{'Top'};
+            %res = $self->VerifyRFC3156( %$item, SetStatus => $args{'SetStatus'} );
+            if ( $args{'Detach'} ) {
+                $item->{'Top'}->parts( [ $item->{'Data'} ] );
+                $item->{'Top'}->make_singlepart;
+            }
+        }
+        elsif ( $item->{'Format'} eq 'RFC3851' ) {
+            $status_on = $item->{'Data'};
+            %res = $self->VerifyRFC3851( %$item, SetStatus => $args{'SetStatus'} );
+        }
+        else {
+            die "Unknow signature format. Shouldn't ever happen.";
+        }
+        if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
+            my $method = $args{'AddStatus'} ? 'add' : 'set';
+            $status_on->head->$method(
+                'X-RT-SMIME-Status' => $res{'status'}
+            );
+        }
+    } elsif ( $item->{'Type'} eq 'encrypted' ) {
+        %res = $self->DecryptRFC3851( %args, %$item );
+        if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
+            my $method = $args{'AddStatus'} ? 'add' : 'set';
+            $item->{'Data'}->head->$method(
+                'X-RT-SMIME-Status' => $res{'status'}
+            );
+        }
+    } else {
+        die "Unknow type '". $item->{'Type'} ."' of protected item";
+    }
+    return %res;
+}
+
+sub VerifyRFC3851 {
+    my $self = shift;
+    my %args = (Data => undef, Queue => undef, @_ );
+
+    my $msg = $args{'Data'}->as_string;
+
+    my %res;
+    my $buf;
+    {
+        local $SIG{CHLD} = 'DEFAULT';
+        my $cmd = join( ' ', shell_quote(
+            $self->OpenSSLPath, qw(smime -verify -noverify),
+        ) );
+        safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
+        $res{'exit_code'} = $?;
+    }
+    if ( $res{'exit_code'} ) {
+        $res{'message'} = "openssl exitted with error code ". ($? >> 8)
+            ." and error: $res{stderr}";
+        return %res;
+    }
+
+    my $res_entity = _extract_msg_from_buf( \$buf );
+    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 );
+    $args{'Data'}->parts([ $res_entity->parts ]);
+    $args{'Data'}->make_singlepart;
+
+    $res{'status'} =
+        "Operation: Verify\nStatus: DONE\n"
+        ."Message: The signature is good\n"
+    ;
+
+    return %res;
+}
+
+sub DecryptRFC3851 {
+    my $self = shift;
+    my %args = (Data => undef, Queue => undef, @_ );
+
+    my $msg = $args{'Data'}->as_string;
+
+    my $action = 'correspond';
+    $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
+
+    my $address = $action eq 'correspond'
+        ? $args{'Queue'}->CorrespondAddress || RT->Config->Get('CorrespondAddress')
+        : $args{'Queue'}->CommentAddress    || RT->Config->Get('CommentAddress');
+
+    my %res;
+    my $buf;
+    {
+        local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
+        local $SIG{CHLD} = 'DEFAULT';
+        my $cmd = join( ' ', shell_quote(
+            $self->OpenSSLPath,
+            qw(smime -decrypt -passin env:SMIME_PASS),
+            -recip => RT->Config->Get('SMIME')->{'Keyring'} .'/'. $address .'.pem',
+        ) );
+        safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
+        $res{'exit_code'} = $?;
+    }
+    if ( $res{'exit_code'} ) {
+        $res{'message'} = "openssl exitted with error code ". ($? >> 8)
+            ." and error: $res{stderr}";
+        return %res;
+    }
+
+    my $res_entity = _extract_msg_from_buf( \$buf );
+    $res_entity->make_multipart( 'mixed', Force => 1 );
+
+    $args{'Data'}->make_multipart( 'mixed', Force => 1 );
+    $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";
+
+    return %res;
+}
+
+sub ParseStatus {
+    my $self = shift;
+    my $status = shift;
+    return () unless $status;
+
+    my @status = split /\n\n/, $status;
+    foreach my $block ( @status ) {
+        chomp $block;
+        $block = { map { s/^\s+//; s/\s+$//; $_ } map split(/:/, $_, 2), split /\n+/, $block };
+    }
+    foreach my $block ( grep $_->{'EncryptedTo'}, @status ) {
+        $block->{'EncryptedTo'} = [{
+            EmailAddress => $block->{'EncryptedTo'},  
+        }];
+    }
+
+    return @status;
+}
+
+sub _extract_msg_from_buf {
+    my $buf = shift;
+    my $rtparser = RT::EmailParser->new();
+    my $parser   = MIME::Parser->new();
+    $rtparser->_SetupMIMEParser($parser);
+    $parser->output_to_core(1);
+    unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) {
+        $RT::Logger->crit("Couldn't parse MIME stream and extract the submessages");
+
+        # Try again, this time without extracting nested messages
+        $parser->extract_nested_messages(0);
+        unless ( $rtparser->{'entity'} = $parser->parse_data($$buf) ) {
+            $RT::Logger->crit("couldn't parse MIME stream");
+            return (undef);
+        }
+    }
+    $rtparser->_PostProcessNewEntity;
+    return $rtparser->Entity;
+}
+
+
+sub CheckIfProtected {
+    my $self = shift;
+    my %args = ( Entity => undef, @_ );
+
+    my $entity = $args{'Entity'};
+
+    my $type = $entity->effective_type;
+    if ( $type =~ m{^application/(?:x-)?pkcs7-mime$} || $type eq 'application/octet-stream' ) {
+        # RFC3851 ch.3.9 variant 1 and 3
+
+        my $security_type;
+
+        my $smime_type = $entity->head->mime_attr('Content-Type.smime-type');
+        if ( $smime_type ) { # it's optional according to RFC3851
+            if ( $smime_type eq 'enveloped-data' ) {
+                $security_type = 'encrypted';
+            }
+            elsif ( $smime_type eq 'signed-data' ) {
+                $security_type = 'signed';
+            }
+            elsif ( $smime_type eq 'certs-only' ) {
+                $security_type = 'certificate management';
+            }
+            elsif ( $smime_type eq 'compressed-data' ) {
+                $security_type = 'compressed';
+            }
+            else {
+                $security_type = $smime_type;
+            }
+        }
+
+        unless ( $security_type ) {
+            my $fname = $entity->head->recommended_filename || '';
+            if ( $fname =~ /\.p7([czsm])$/ ) {
+                my $type_char = $1;
+                if ( $type_char eq 'm' ) {
+                    # RFC3851, ch3.4.2
+                    # it can be both encrypted and signed
+                    $security_type = 'encrypted';
+                }
+                elsif ( $type_char eq 's' ) {
+                    # RFC3851, ch3.4.3, multipart/signed, XXX we should never be here
+                    # unless message is changed by some gateway
+                    $security_type = 'signed';
+                }
+                elsif ( $type_char eq 'c' ) {
+                    # RFC3851, ch3.7
+                    $security_type = 'certificate management';
+                }
+                elsif ( $type_char eq 'z' ) {
+                    # RFC3851, ch3.5
+                    $security_type = 'compressed';
+                }
+            }
+        }
+        return () if !$security_type && $type eq 'application/octet-stream';
+
+        return (
+            Type   => $security_type,
+            Format => 'RFC3851',
+            Data   => $entity,
+        );
+    }
+    elsif ( $type eq 'multipart/signed' ) {
+        # RFC3156, multipart/signed
+        # RFC3851, ch.3.9 variant 2
+
+        unless ( $entity->parts == 2 ) {
+            $RT::Logger->error( "Encrypted or signed entity must has two subparts. Skipped" );
+            return ();
+        }
+
+        my $protocol = $entity->head->mime_attr( 'Content-Type.protocol' );
+        unless ( $protocol ) {
+            $RT::Logger->error( "Entity is '$type', but has no protocol defined. Skipped" );
+            return ();
+        }
+
+        unless ( $protocol eq 'application/pgp-signature' ) {
+            $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" );
+            return ();
+        }
+        $RT::Logger->debug("Found part signed according to RFC3156");
+        return (
+            Type      => 'signed',
+            Format    => 'RFC3156',
+            Top       => $entity,
+            Data      => $entity->parts(0),
+            Signature => $entity->parts(1),
+        );
+    }
+    return ();
+}
+
+sub KeyExpirationDate {
+    my $self = shift;
+    my %args = (@_);
+
+    my $user = $args{'User'};
+
+    my $key_obj = $user->CustomFieldValues('PublicKey')->First;
+    unless ( $key_obj ) {
+        $RT::Logger->warn('User #'. $user->id .' has no SMIME key');
+        return;
+    }
+
+    my $attr = $user->FirstAttribute('SMIMEKeyNotAfter');
+    if ( $attr and my $date_str = $attr->Content
+         and $key_obj->LastUpdatedObj->Unix < $attr->LastUpdatedObj->Unix )
+    {
+        my $date = RT::Date->new( $RT::SystemUser );
+        $date->Set( Format => 'unknown', Value => $attr->Content );
+        return $date;
+    }
+    $RT::Logger->debug('Expiration date of SMIME key is not up to date');
+
+    my $key = $key_obj->Content;
+
+    my ($buf, $err) = ('', '');
+    {
+        local $ENV{SMIME_PASS} = '123456';
+        safe_run3(
+            join( ' ', shell_quote( $self->OpenSSLPath, qw(x509 -noout -dates) ) ),
+            \$key, \$buf, \$err
+        );
+    }
+    $RT::Logger->debug( "openssl stderr: " . $err ) if length $err;
+
+    my ($date_str) = ($buf =~ /^notAfter=(.*)$/m);
+    return unless $date_str;
+
+    $RT::Logger->debug( "smime key expiration date is $date_str" );
+    $user->SetAttribute(
+        Name => 'SMIMEKeyNotAfter',
+        Description => 'SMIME key expiration date',
+        Content => $date_str,
+    );
+    my $date = RT::Date->new( $RT::SystemUser );
+    $date->Set( Format => 'unknown', Value => $date_str );
+    return $date;
+}
+
+sub GetPassphrase {
+    my $self = shift;
+    my %args = (Address => undef, @_);
+    $args{'Address'} = '' unless defined $args{'Address'};
+    return RT->Config->Get('SMIME')->{'Passphrase'}->{ $args{'Address'} };
+}
+
+1;

commit b5d4fd20b2b99045934f03e99988bc674d62bda0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 3 22:03:14 2010 +0300

    fix getting keys per protocol

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 610aab1..dbce26e 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -217,7 +217,7 @@ sub GetKeysForEncryption {
     my $self = shift;
     my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
     my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-    my %res = $self->LoadImplementation( $protocol )->GetKeysForEncryption( @_ );
+    my %res = $self->LoadImplementation( $protocol )->GetKeysForEncryption( %args );
     $res{'Protocol'} = $protocol;
     return %res;
 }
@@ -226,7 +226,7 @@ sub GetKeysForSigning {
     my $self = shift;
     my %args = @_%2? (Signer => @_) : (Protocol => undef, Signer => undef, @_);
     my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-    my %res = $self->LoadImplementation( $protocol )->GetKeysForSigning( @_ );
+    my %res = $self->LoadImplementation( $protocol )->GetKeysForSigning( %args );
     $res{'Protocol'} = $protocol;
     return %res;
 }
@@ -250,7 +250,7 @@ sub GetKeysInfo {
     my $self = shift;
     my %args = @_%2 ? (Key => @_) : ( Protocol => undef, Key => undef, @_ );
     my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-    my %res = $self->LoadImplementation( $protocol )->GetKeysInfo( @_ );
+    my %res = $self->LoadImplementation( $protocol )->GetKeysInfo( %args );
     $res{'Protocol'} = $protocol;
     return %res;
 }

commit 90615f0ecdf343ec8cb1ba19b4f17baad09a19ac
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:03:41 2010 +0300

    debug log in tmp dir may help debug things

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index cd0e790..f02fc7c 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -258,9 +258,6 @@ sub bootstrap_config {
     my $self = shift;
     my %args = @_;
 
-    $tmp{'config'}{'RT'} = File::Spec->catfile(
-        "$tmp{'directory'}", 'RT_SiteConfig.pm'
-    );
     my $config = $self->new_temp_file( config => RT => 'RT_SiteConfig.pm' );
     open my $config_fh, '>', $config
         or die "Couldn't open $config: $!";
@@ -269,6 +266,8 @@ sub bootstrap_config {
 Set( \$WebPort , $port);
 Set( \$WebBaseURL , "http://localhost:\$WebPort");
 Set( \$LogToSyslog , undef);
+Set( \$LogDir,     '$tmp{directory}');
+Set( \$LogToFile , "debug");
 Set( \$LogToScreen , "warning");
 Set( \$RTAddressRegexp , qr/^bad_re_that_doesnt_match\$/);
 Set( \$MailCommand, 'testfile');

commit 7f35a5a1462c95c18c364602c63c8226d6ee75c1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:08:16 2010 +0300

    SignEncrypt should use enabled outgoing protocol

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index dbce26e..46a0887 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -129,10 +129,8 @@ sub SignEncrypt {
         ];
     }
 
-    my $protocol = delete $args{'Protocol'} || 'GnuPG';
-    my $class = $self->LoadImplementation( $protocol );
-
-    my %res = $class->SignEncrypt( %args );
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my %res = $self->LoadImplementation( $protocol )->SignEncrypt( %args );
     $res{'Protocol'} = $protocol;
     return %res;
 }

commit 8281588770c94bb912484684911c42d3e37260b0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:10:57 2010 +0300

    move CheckRecipients into RT::Crypt

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 46a0887..62e5352 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -211,6 +211,72 @@ sub UseKeyForEncryption {
     return ();
 } }
 
+sub CheckRecipients {
+    my $self = shift;
+    my @recipients = (@_);
+
+    my ($status, @issues) = (1, ());
+
+    my %seen;
+    foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
+        my %res = $self->GetKeysForEncryption( Recipient => $address );
+        if ( $res{'info'} && @{ $res{'info'} } == 1 && $res{'info'}[0]{'TrustLevel'} > 0 ) {
+            # good, one suitable and trusted key 
+            next;
+        }
+        my $user = RT::User->new( $RT::SystemUser );
+        $user->LoadByEmail( $address );
+        # it's possible that we have no User record with the email
+        $user = undef unless $user->id;
+
+        if ( my $fpr = RT::Crypt->UseKeyForEncryption( $address ) ) {
+            if ( $res{'info'} && @{ $res{'info'} } ) {
+                next if
+                    grep lc $_->{'Fingerprint'} eq lc $fpr,
+                    grep $_->{'TrustLevel'} > 0,
+                    @{ $res{'info'} };
+            }
+
+            $status = 0;
+            my %issue = (
+                EmailAddress => $address,
+                $user? (User => $user) : (),
+                Keys => undef,
+            );
+            $issue{'Message'} = "Selected key either is not trusted or doesn't exist anymore."; #loc
+            push @issues, \%issue;
+            next;
+        }
+
+        my $prefered_key;
+        $prefered_key = $user->PreferredKey if $user;
+        #XXX: prefered key is not yet implemented...
+
+        # classify errors
+        $status = 0;
+        my %issue = (
+            EmailAddress => $address,
+            $user? (User => $user) : (),
+            Keys => undef,
+        );
+
+        unless ( $res{'info'} && @{ $res{'info'} } ) {
+            # no key
+            $issue{'Message'} = "There is no key suitable for encryption."; #loc
+        }
+        elsif ( @{ $res{'info'} } == 1 && !$res{'info'}[0]{'TrustLevel'} ) {
+            # trust is not set
+            $issue{'Message'} = "There is one suitable key, but trust level is not set."; #loc
+        }
+        else {
+            # multiple keys
+            $issue{'Message'} = "There are several keys suitable for encryption."; #loc
+        }
+        push @issues, \%issue;
+    }
+    return ($status, @issues);
+}
+
 sub GetKeysForEncryption {
     my $self = shift;
     my %args = @_%2? (Recipient => @_) : (Protocol => undef, For => undef, @_ );
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 1349949..219a3ba 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2020,72 +2020,6 @@ sub GetKeysForSigning {
     return $self->GetKeysInfo( Key => delete $args{'Signer'}, %args, Type => 'private' );
 }
 
-sub CheckRecipients {
-    my $self = shift;
-    my @recipients = (@_);
-
-    my ($status, @issues) = (1, ());
-
-    my %seen;
-    foreach my $address ( grep !$seen{ lc $_ }++, map $_->address, @recipients ) {
-        my %res = $self->GetKeysForEncryption( Recipient => $address );
-        if ( $res{'info'} && @{ $res{'info'} } == 1 && $res{'info'}[0]{'TrustLevel'} > 0 ) {
-            # good, one suitable and trusted key 
-            next;
-        }
-        my $user = RT::User->new( $RT::SystemUser );
-        $user->LoadByEmail( $address );
-        # it's possible that we have no User record with the email
-        $user = undef unless $user->id;
-
-        if ( my $fpr = RT::Crypt->UseKeyForEncryption( $address ) ) {
-            if ( $res{'info'} && @{ $res{'info'} } ) {
-                next if
-                    grep lc $_->{'Fingerprint'} eq lc $fpr,
-                    grep $_->{'TrustLevel'} > 0,
-                    @{ $res{'info'} };
-            }
-
-            $status = 0;
-            my %issue = (
-                EmailAddress => $address,
-                $user? (User => $user) : (),
-                Keys => undef,
-            );
-            $issue{'Message'} = "Selected key either is not trusted or doesn't exist anymore."; #loc
-            push @issues, \%issue;
-            next;
-        }
-
-        my $prefered_key;
-        $prefered_key = $user->PreferredKey if $user;
-        #XXX: prefered key is not yet implemented...
-
-        # classify errors
-        $status = 0;
-        my %issue = (
-            EmailAddress => $address,
-            $user? (User => $user) : (),
-            Keys => undef,
-        );
-
-        unless ( $res{'info'} && @{ $res{'info'} } ) {
-            # no key
-            $issue{'Message'} = "There is no key suitable for encryption."; #loc
-        }
-        elsif ( @{ $res{'info'} } == 1 && !$res{'info'}[0]{'TrustLevel'} ) {
-            # trust is not set
-            $issue{'Message'} = "There is one suitable key, but trust level is not set."; #loc
-        }
-        else {
-            # multiple keys
-            $issue{'Message'} = "There are several keys suitable for encryption."; #loc
-        }
-        push @issues, \%issue;
-    }
-    return ($status, @issues);
-}
-
 sub GetKeysInfo {
     my $self = shift;
     my %args = (

commit 677b976f1deb0048a35044ec60865832e11f2480
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:13:24 2010 +0300

    represent basic GetKeysForEncryption in RT::Crypt::Base

diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
index 940d907..31f4f30 100644
--- a/lib/RT/Crypt/Base.pm
+++ b/lib/RT/Crypt/Base.pm
@@ -15,6 +15,16 @@ sub CheckIfProtected { return () }
 
 sub FindScatteredParts { return () }
 
+sub GetKeysForEncryption {
+    my $self = shift;
+    my %args = (Recipient => undef, @_);
+    return $self->GetKeysInfo(
+        Key => delete $args{'Recipient'},
+        %args,
+        Type => 'public'
+    );
+}
+
 sub GetKeysInfo {
     return (
         exit_code => 1,
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 219a3ba..d7de6a5 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1994,8 +1994,7 @@ also listed.
 
 sub GetKeysForEncryption {
     my $self = shift;
-    my %args = (Recipient => undef, @_);
-    my %res = $self->GetKeysInfo( Key => delete $args{'Recipient'}, %args, Type => 'public' );
+    my %res = $self->SUPER::GetKeysForEncryption( @_ );
     return %res if $res{'exit_code'};
     return %res unless $res{'info'};
 

commit 03e80fff31a45977975382b259ae2226e0baf5d7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:15:09 2010 +0300

    move ParseDate method upper so SMIME can re-use it

diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
index 31f4f30..7a26aa0 100644
--- a/lib/RT/Crypt/Base.pm
+++ b/lib/RT/Crypt/Base.pm
@@ -32,4 +32,22 @@ sub GetKeysInfo {
     );
 }
 
+sub ParseDate {
+    my $self = shift;
+    my $value = shift;
+
+    # never
+    return $value unless $value;
+
+    require RT::Date;
+    my $obj = RT::Date->new( $RT::SystemUser );
+    # unix time
+    if ( $value =~ /^\d+$/ ) {
+        $obj->Set( Value => $value );
+    } else {
+        $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
+    }
+    return $obj;
+}
+
 1;
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index d7de6a5..558989f 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2116,7 +2116,7 @@ sub ParseKeysInfo {
 
             @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = 
                 _ConvertTrustChar( $info{'OwnerTrustChar'} );
-            $info{ $_ } = _ParseDate( $info{ $_ } )
+            $info{ $_ } = $self->ParseDate( $info{ $_ } )
                 foreach qw(Created Expire);
             push @res, \%info;
         }
@@ -2129,7 +2129,7 @@ sub ParseKeysInfo {
             ) } = split /:/, $line, 12;
             @info{qw(OwnerTrust OwnerTrustTerse OwnerTrustLevel)} = 
                 _ConvertTrustChar( $info{'OwnerTrustChar'} );
-            $info{ $_ } = _ParseDate( $info{ $_ } )
+            $info{ $_ } = $self->ParseDate( $info{ $_ } )
                 foreach qw(Created Expire);
             push @res, \%info;
         }
@@ -2137,7 +2137,7 @@ sub ParseKeysInfo {
             my %info;
             @info{ qw(Trust Created Expire String) }
                 = (split /:/, $line)[0,4,5,8];
-            $info{ $_ } = _ParseDate( $info{ $_ } )
+            $info{'Validity'}{ $_ } = $self->ParseDate( $info{ $_ } )
                 foreach qw(Created Expire);
             push @{ $res[-1]{'User'} ||= [] }, \%info;
         }
@@ -2215,22 +2215,6 @@ sub ParseKeysInfo {
     }
 }
 
-sub _ParseDate {
-    my $value = shift;
-    # never
-    return $value unless $value;
-
-    require RT::Date;
-    my $obj = RT::Date->new( $RT::SystemUser );
-    # unix time
-    if ( $value =~ /^\d+$/ ) {
-        $obj->Set( Value => $value );
-    } else {
-        $obj->Set( Format => 'unknown', Value => $value, Timezone => 'utc' );
-    }
-    return $obj;
-}
-
 sub DeleteKey {
     my $self = shift;
     my $key = shift;

commit 9de01b0f4dae4f871b1345e77c82d189f3a1b56a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:19:19 2010 +0300

    per UID expiration dates make sense only for GnuPG

diff --git a/share/html/Admin/Elements/ShowKeyInfo b/share/html/Admin/Elements/ShowKeyInfo
index 4f99fb5..6ba6d2b 100644
--- a/share/html/Admin/Elements/ShowKeyInfo
+++ b/share/html/Admin/Elements/ShowKeyInfo
@@ -71,10 +71,12 @@
 <td><% $res{'info'}{'Expire'}? $res{'info'}{'Expire'}->AsString( Time => 0 ): loc('never') %></td></tr>
 
 % foreach my $uinfo( @{ $res{'info'}{'User'} } ) {
-<tr><th><% loc('User (created - expire)') %>:</th>
+<tr><th><% loc('User') %>:</th>
 <td><% $uinfo->{'String'} %>\
-(<% $uinfo->{'Created'}? $uinfo->{'Created'}->AsString( Time => 0 ): loc('never') %> - \
-<% $uinfo->{'Expire'}? $uinfo->{'Expire'}->AsString( Time => 0 ): loc('never') %>)
+% if ( my $validity = $uinfo->{'Validity'} ) {
+(<% $validity->{'Created'}? $validity->{'Created'}->AsString( Time => 0 ): loc('never') %> - \
+<% $validity->{'Expire'}? $validity->{'Expire'}->AsString( Time => 0 ): loc('never') %>)
+% }
 </td></tr>
 % }
 

commit b32a54fa720063c50179727d89e42fa7954893a6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:22:11 2010 +0300

    mass Crypt::SMIME improvement
    
    * full certificate info to fetch all data
    * simple keyring implementation
    * encryption and signing should start working

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index fd472fc..d68f73b 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -5,6 +5,7 @@ use warnings;
 package RT::Crypt::SMIME;
 use base 'RT::Crypt::Base';
 
+use RT::Crypt;
 use IPC::Run3 0.036 'run3';
 use String::ShellQuote 'shell_quote';
 use RT::Util 'safe_run_child';
@@ -35,11 +36,11 @@ sub SignEncrypt {
         $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
     }
 
-    my %res = (exit_code => 0, status => []);
+    my %res = (exit_code => 0, status => '');
 
     my @addresses =
         map $_->address,
-        Email::Address->parse($_),
+        map Email::Address->parse($_),
         grep defined && length,
         map $entity->head->get($_),
         qw(To Cc Bcc);
@@ -47,26 +48,21 @@ sub SignEncrypt {
     my @keys;
     foreach my $address ( @addresses ) {
         $RT::Logger->debug( "Considering encrypting message to " . $address );
-        my $user = RT::User->new( $RT::SystemUser );
-        $user->LoadByEmail( $address );
 
-        my $key;
-        $key = $user->FirstCustomFieldValue('PublicKey') if $user->id;
-        unless ( $key ) {
+        my %key_info = $self->GetKeysInfo( Key => $address );
+        unless ( %key_info ) {
             $res{'exit_code'} = 1;
             my $reason = 'Key not found';
-            push @{ $res{'status'} }, {
-                Operation  => 'RecipientsCheck',
-                Status     => 'ERROR',
-                Message    => "Recipient '$address' is unusable, the reason is '$reason'",
-                Recipient  => $address,
-                Reason     => $reason,
-            };
+            $res{'status'} .=
+                "Operation: RecipientsCheck\nStatus: ERROR\n"
+                ."Message: Recipient '$address' is unusable, the reason is '$reason'\n"
+                ."Recipient: $address\n"
+                ."Reason: $reason\n\n",
+            ;
             next;
         }
 
-        my $expire = $self->GetKeyExpiration( $user );
-        unless ( $expire ) {
+        unless ( $key_info{'info'}[0]{'Expire'} ) {
             # we continue here as it's most probably a problem with the key,
             # so later during encryption we'll get verbose errors
             $RT::Logger->error(
@@ -74,19 +70,18 @@ sub SignEncrypt {
                 .", but we couldn't get expiration date of the key."
             );
         }
-        elsif ( $expire->Diff( time ) < 0 ) {
+        elsif ( $key_info{'info'}[0]{'Expire'}->Diff( time ) < 0 ) {
             $res{'exit_code'} = 1;
             my $reason = 'Key expired';
-            push @{ $res{'status'} }, {
-                Operation  => 'RecipientsCheck',
-                Status     => 'ERROR',
-                Message    => "Recipient '$address' is unusable, the reason is '$reason'",
-                Recipient  => $address,
-                Reason     => $reason,
-            };
+            $res{'status'} .=
+                "Operation: RecipientsCheck\nStatus: ERROR\n"
+                ."Message: Recipient '$address' is unusable, the reason is '$reason'\n"
+                ."Recipient: $address\n"
+                ."Reason: $reason\n\n",
+            ;
             next;
         }
-        push @keys, $key;
+        push @keys, $key_info{'info'}[0]{'Content'};
     }
     return %res if $res{'exit_code'};
 
@@ -284,7 +279,7 @@ sub ParseStatus {
     return () unless $status;
 
     my @status = split /\n\n/, $status;
-    foreach my $block ( @status ) {
+    foreach my $block ( grep length, @status ) {
         chomp $block;
         $block = { map { s/^\s+//; s/\s+$//; $_ } map split(/:/, $_, 2), split /\n+/, $block };
     }
@@ -412,59 +407,185 @@ sub CheckIfProtected {
     return ();
 }
 
-sub KeyExpirationDate {
+sub GetPassphrase {
     my $self = shift;
-    my %args = (@_);
+    my %args = (Address => undef, @_);
+    $args{'Address'} = '' unless defined $args{'Address'};
+    return RT->Config->Get('SMIME')->{'Passphrase'}->{ $args{'Address'} };
+}
 
-    my $user = $args{'User'};
+sub GetKeysInfo {
+    my $self = shift;
+    my %args = (
+        Key   => undef,
+        Type  => 'public',
+        Force => 0,
+        @_
+    );
 
-    my $key_obj = $user->CustomFieldValues('PublicKey')->First;
-    unless ( $key_obj ) {
-        $RT::Logger->warn('User #'. $user->id .' has no SMIME key');
-        return;
+    my $email = $args{'Key'};
+    unless ( $email ) {
+        return (exit_code => 0); # unless $args{'Force'};
     }
 
-    my $attr = $user->FirstAttribute('SMIMEKeyNotAfter');
-    if ( $attr and my $date_str = $attr->Content
-         and $key_obj->LastUpdatedObj->Unix < $attr->LastUpdatedObj->Unix )
-    {
-        my $date = RT::Date->new( $RT::SystemUser );
-        $date->Set( Format => 'unknown', Value => $attr->Content );
-        return $date;
+    my $key = $self->GetKeyContent( %args );
+    return (exit_code => 0) unless $key;
+
+    return $self->GetCertificateInfo( Certificate => $key );
+}
+
+sub GetKeyContent {
+    my $self = shift;
+    my %args = ( Key => undef, @_ );
+
+    my $key;
+    if ( my $file = $self->CheckKeyring( %args ) ) {
+        open my $fh, '<:raw', $file
+            or die "Couldn't open file '$file': $!";
+        $key = do { local $/; readline $fh };
+        close $fh;
+    }
+    else {
+        # XXX: should we use different user??
+        my $user = RT::User->new( $RT::SystemUser );
+        $user->LoadByEmail( $args{'Key'} );
+        unless ( $user->id ) {
+            return (exit_code => 0);
+        }
+
+        $key = $user->FirstCustomFieldValue('SMIME Key');
     }
-    $RT::Logger->debug('Expiration date of SMIME key is not up to date');
+    return $key;
+}
 
-    my $key = $key_obj->Content;
+sub CheckKeyring {
+    my $self = shift;
+    my %args = (
+        Key => undef,
+        @_,
+    );
+    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
+    return undef unless $keyring;
 
-    my ($buf, $err) = ('', '');
+    my $file = File::Spec->catfile( $keyring, $args{'Key'} .'.pem' );
+    return undef unless -f $file;
+
+    return $file;
+}
+
+sub GetCertificateInfo {
+    my $self = shift;
+    my %args = (
+        Certificate => undef,
+        @_,
+    );
+
+    my %res;
+    my $buf;
     {
-        local $ENV{SMIME_PASS} = '123456';
-        safe_run3(
-            join( ' ', shell_quote( $self->OpenSSLPath, qw(x509 -noout -dates) ) ),
-            \$key, \$buf, \$err
+        local $SIG{CHLD} = 'DEFAULT';
+        my $cmd = join ' ', shell_quote(
+            $self->OpenSSLPath, 'x509',
+            # everything
+            '-text',
+            # plus fingerprint
+            '-fingerprint',
+            # don't print cert itself
+            '-noout',
+            # don't dump signature and pubkey info, header is useless too
+            '-certopt', 'no_pubkey,no_sigdump,no_extensions',
+            # subject and issuer are multiline, long prop names, utf8
+            '-nameopt', 'sep_multiline,lname,utf8',
         );
+        safe_run_child { run3( $cmd, \$args{'Certificate'}, \$buf, \$res{'stderr'} ) };
+        $res{'exit_code'} = $?;
+    }
+    if ( $res{'exit_code'} ) {
+        $res{'message'} = "openssl exitted with error code ". ($? >> 8)
+            ." and error: $res{stderr}";
+        return %res;
     }
-    $RT::Logger->debug( "openssl stderr: " . $err ) if length $err;
 
-    my ($date_str) = ($buf =~ /^notAfter=(.*)$/m);
-    return unless $date_str;
+    my %info = $self->CanonicalizeInfo( $self->ParseCertificateInfo( $buf ) );
+    $info{'Content'} = $args{'Certificate'};
+    $res{'info'} = [\%info];
+    return %res;
+}
+
+sub CanonicalizeInfo {
+    my $self = shift;
+    my %info = @_;
 
-    $RT::Logger->debug( "smime key expiration date is $date_str" );
-    $user->SetAttribute(
-        Name => 'SMIMEKeyNotAfter',
-        Description => 'SMIME key expiration date',
-        Content => $date_str,
+    my %res = (
+        # XXX: trust is not implmented for SMIME
+        TrustLevel => 1,
     );
-    my $date = RT::Date->new( $RT::SystemUser );
-    $date->Set( Format => 'unknown', Value => $date_str );
-    return $date;
+    {
+        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;
+    }
+    {
+        my $validity = delete $info{'Certificate'}{'Data'}{'Validity'};
+        $res{'Created'} = $self->ParseDate( $validity->{'Not Before'} );
+        $res{'Expire'} = $self->ParseDate( $validity->{'Not After'} );
+    }
+    {
+        $res{'Fingerprint'} = delete $info{'SHA1 Fingerprint'};
+    }
+    %res = (%{$info{'Certificate'}{'Data'}}, %res);
+    return %res;
 }
 
-sub GetPassphrase {
+sub ParseCertificateInfo {
     my $self = shift;
-    my %args = (Address => undef, @_);
-    $args{'Address'} = '' unless defined $args{'Address'};
-    return RT->Config->Get('SMIME')->{'Passphrase'}->{ $args{'Address'} };
+    my $info = shift;
+
+    my @lines = split /\n/, $info;
+
+    my %res;
+    my %prefix = ();
+    my $first_line = 1;
+    my $prev_prefix = '';
+    my $prev_key = '';
+
+    foreach my $line ( @lines ) {
+        # some examples:
+        # Validity # no trailing ':'
+        # Not After : XXXXXX # space before ':'
+        # countryName=RU # '=' as separator
+        my ($prefix, $key, $value) = ($line =~ /^(\s*)(.*?)\s*(?:[:=]\s*(.*?)|)\s*$/);
+        if ( $first_line ) {
+            $prefix{$prefix} = \%res;
+            $first_line = 0;
+        }
+
+        my $put_into = ($prefix{$prefix} ||= $prefix{$prev_prefix}{$prev_key});
+        unless ( $put_into ) {
+            die "Couldn't parse key info: $info";
+        }
+
+        if ( defined $value && length $value ) {
+            $put_into->{$key} = $value;
+        }
+        else {
+            $put_into->{$key} = {};
+        }
+        delete $prefix{$_} foreach
+            grep length($_) > length($prefix),
+            keys %prefix;
+
+        ($prev_prefix, $prev_key) = ($prefix, $key);
+    }
+
+    return %res;
 }
 
+
 1;

commit 0491919aadc3b714940cfb0f30a383a0d0678fff
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:25:13 2010 +0300

    check proper config option

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 55bf028..ef4dd5c 100755
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -364,7 +364,7 @@ sub SendEmail {
         $TicketObj = $TransactionObj->Object;
     }
 
-    if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+    if ( RT->Config->Get('Crypt')->{'Enable'} ) {
         my %crypt;
 
         my $attachment;

commit 1701182553fc5b58309fb649d23d591a3bb4ca07
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:26:50 2010 +0300

    switch to new APIs

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 62e5352..347b6cd 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -172,7 +172,8 @@ sub ParseStatus {
         Status   => '',
         @_
     );
-    return $self->LoadImplementation( $args{'Protocol'} )->ParseStatus( $args{'Status'} );
+    return $self->LoadImplementation( $args{'Protocol'} )
+        ->ParseStatus( $args{'Status'} );
 }
 
 =head2 UseKeyForSigning
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
index f3c2b1d..e51ec1d 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -172,7 +172,7 @@ if ( $self->{'Encrypt'} ) {
         keys %$self
     );
 
-    my ($status, @issues) = RT::Crypt::GnuPG->CheckRecipients( @recipients );
+    my ($status, @issues) = RT::Crypt->CheckRecipients( @recipients );
     push @{ $self->{'GnuPGRecipientsKeyIssues'} ||= [] }, @issues;
     $checks_failure = 1 unless $status;
 }
diff --git a/share/html/Ticket/Elements/ShowGnuPGStatus b/share/html/Ticket/Elements/ShowGnuPGStatus
index 7a81de0..ef74632 100644
--- a/share/html/Ticket/Elements/ShowGnuPGStatus
+++ b/share/html/Ticket/Elements/ShowGnuPGStatus
@@ -116,7 +116,7 @@ my $reverify_cb = sub {
 
         $top->DelHeader('X-RT-GnuPG-Status');
         $top->AddHeader(map { ('X-RT-GnuPG-Status' => $_->{'status'} ) } @res);
-        $top->SetHeader('X-RT-Privacy' => 'PGP' );
+        $top->SetHeader('X-RT-Privacy' => 'GnuPG' );
         $top->DelHeader('X-RT-Incoming-Signature');
 
         my @status = RT::Crypt->ParseStatus(

commit 58612620d0da5f7db9f0a3cd1d785751de9e8457
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:28:32 2010 +0300

    update tests

diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
index 44f48df..2ed01fd 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -201,7 +201,7 @@ RT::Test->close_mailgate_ok($mail);
         'recorded incoming mail that is encrypted'
     );
     is( $msg->GetHeader('X-RT-Privacy'),
-        'PGP',
+        'GnuPG',
         'recorded incoming mail that is encrypted'
     );
     like( $attach->Content, qr'orz');
diff --git a/t/mail/gnupg-realmail.t b/t/mail/gnupg-realmail.t
index de1d958..07a256c 100644
--- a/t/mail/gnupg-realmail.t
+++ b/t/mail/gnupg-realmail.t
@@ -96,7 +96,7 @@ sub email_ok {
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
     is( $msg->GetHeader('X-RT-Privacy'),
-        'PGP',
+        'GnuPG',
         "$eid: recorded incoming mail that is encrypted"
     );
 
diff --git a/t/mail/smime/smime-incoming.t b/t/mail/smime/smime-incoming.t
index ed59ee9..2b3c404 100644
--- a/t/mail/smime/smime-incoming.t
+++ b/t/mail/smime/smime-incoming.t
@@ -8,12 +8,9 @@ my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
     unless $openssl;
 
-use File::Temp;
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
 use RT::Tickets;
-use FindBin;
-use Cwd 'abs_path';
 
 # catch any outgoing emails
 RT::Test->set_mail_catcher;
@@ -31,6 +28,7 @@ RT->Config->Set( Crypt =>
     Incoming => ['SMIME'],
     Outgoing => 'SMIME',
 );
+RT->Config->Set( GnuPG => Enable => 0 );
 RT->Config->Set( SMIME =>
     Enable => 1,
     OutgoingMessagesFormat => 'RFC',
@@ -40,22 +38,22 @@ RT->Config->Set( SMIME =>
     OpenSSL => $openssl,
     Keyring => $keyring,
 );
-RT->Config->Set( GnuPG => Enable => 0 );
 
 RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
 
-RT::Test->import_smime_key('sender at example.com');
-
 my $mails = RT::Test::find_relocatable_path( 'data', 'smime', 'mails' );
 
 my ($url, $m) = RT::Test->started_ok;
+ok $m->login, "logged in";
+
 # configure key for General queue
-$m->get( $url."?user=root;pass=password" );
-$m->content_like(qr/Logout/, 'we did log in');
-$m->get( $url.'/Admin/Queues/');
-$m->follow_link_ok( {text => 'General'} );
-$m->submit_form( form_number => 3,
-		 fields      => { CorrespondAddress => 'sender at example.com' } );
+RT::Test->import_smime_key('sender at example.com');
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'General',
+    CorrespondAddress => 'sender at example.com',
+    CommentAddress    => 'sender at example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
 
 my $mail = RT::Test->open_mailgate_ok($url);
 print $mail <<EOF;
diff --git a/t/mail/smime/smime-outgoing.t b/t/mail/smime/smime-outgoing.t
index 31e6b5b..96c7a9d 100644
--- a/t/mail/smime/smime-outgoing.t
+++ b/t/mail/smime/smime-outgoing.t
@@ -2,73 +2,135 @@
 use strict;
 use warnings;
 
-use Test::More;
-eval 'use RT::Test; 1'
-    or plan skip_all => 'requires 3.7 to run tests.';
+use RT::Test tests => 14;
+
+my $openssl = RT::Test->find_executable('openssl');
+plan skip_all => 'openssl executable is required.'
+    unless $openssl;
 
-plan tests => 10;
 
 use IPC::Run3 'run3';
-use Cwd 'abs_path';
 use RT::Interface::Email;
 
-use_ok('RT::Crypt::SMIME');
+# catch any outgoing emails
+RT::Test->set_mail_catcher;
+
+my $keys = RT::Test::get_abs_relocatable_dir(
+    (File::Spec->updir()) x 2,
+    qw(data smime keys),
+);
 
-RT::Config->Set( 'MailCommand' => 'sendmail'); # we intercept MIME::Entity::send
+my $keyring = RT::Test->new_temp_dir(
+    crypt => smime => 'smime_keyring'
+);
 
-RT->Config->Set( 'OpenSSLPath', '/usr/bin/openssl' );
-RT->Config->Set( 'SMIMEKeys', abs_path('testkeys') );
-RT->Config->Set( 'SMIMEPasswords', {'sender at example.com' => '123456'} );
-RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::SMIME' );
+RT->Config->Set( Crypt =>
+    Enable   => 1,
+    Incoming => ['SMIME'],
+    Outgoing => 'SMIME',
+);
+RT->Config->Set( GnuPG => Enable => 0 );
+RT->Config->Set( SMIME =>
+    Enable => 1,
+    OutgoingMessagesFormat => 'RFC',
+    Passphrase => {
+        'sender at example.com' => '123456',
+    },
+    OpenSSL => $openssl,
+    Keyring => $keyring,
+);
 
-RT::Handle->InsertData('etc/initialdata');
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
 
 my ($url, $m) = RT::Test->started_ok;
-# configure key for General queue
-$m->get( $url."?user=root;pass=password" );
-$m->content_like(qr/Logout/, 'we did log in');
-$m->get( $url.'/Admin/Queues/');
-$m->follow_link_ok( {text => 'General'} );
-$m->submit_form( form_number => 3,
-		 fields      => { CorrespondAddress => 'sender at example.com' } );
-
-my $user = RT::User->new($RT::SystemUser);
-ok($user->LoadByEmail('root at localhost'), "Loaded user 'root'");
-diag $user->Id;
-ok($user->Load('root'), "Loaded user 'root'");
-is( $user->EmailAddress, 'root at localhost' );
-my $val = $user->FirstCustomFieldValue('PublicKey');
-# XXX: delete if it's already there
-unless (defined $val) {
-    local $/;
-    open my $fh, 'testkeys/recipient.crt' or die $!;
-    $user->AddCustomFieldValue( Field => 'PublicKey', Value => <$fh> );
-    $val = $user->FirstCustomFieldValue('PublicKey');
+ok $m->login, "logged in";
+
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'General',
+    CorrespondAddress => 'sender at example.com',
+    CommentAddress    => 'sender at example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+{
+    my ($status, $msg) = $queue->SetEncrypt(1);
+    ok $status, "turn on encyption by default"
+        or diag "error: $msg";
 }
 
-no warnings 'once';
-local *MIME::Entity::send = sub {
-    my $mime_obj = shift;
-    my ($buf, $err);
-    ok(eval { run3([qw(openssl smime -decrypt -passin pass:123456 -inkey testkeys/recipient.key -recip testkeys/recipient.crt)],
-	 \$mime_obj->as_string, \$buf, \$err) }, 'can decrypt');
-    diag $err if $err;
-    diag "Error code: $?" if $?;
-    like($buf, qr'This message has been automatically generated in response');
-    # XXX: check signature as wel
-};
+{
+    my $cf = RT::CustomField->new( $RT::SystemUser );
+    my ($ret, $msg) = $cf->Create(
+        Name       => 'SMIME Key',
+        LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
+        Type       => 'TextSingle',
+    );
+    ok($ret, "Custom Field created");
+
+    my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
+    $OCF->Create(
+        CustomField => $cf->id,
+        ObjectId    => 0,
+    );
+}
+
+my $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');
+
+    open my $fh, '<:raw', File::Spec->catfile($keys, 'recipient.crt')
+        or die $!;
+    my ($status, $msg) = $user->AddCustomFieldValue(
+        Field => 'SMIME Key',
+        Value => do { local $/; <$fh> },
+    );
+    ok $status, "added user's key" or diag "error: $msg";
+}
 
+RT::Test->clean_caught_mails;
 
-RT::Interface::Email::Gateway( {queue => 1, action => 'correspond',
-			       message => 'From: root at localhost
-To: rt at example.com
+{
+    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!'});
+Foob!
+
+END
+
+    my ($status, $id) = RT::Test->send_via_mailgate(
+        $mail, queue => $queue->Name,
+    );
+    is $status, 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([
+            $openssl, qw(smime -decrypt -passin pass:123456),
+            '-inkey', File::Spec->catfile($keys, 'recipient.key'),
+            '-recip', File::Spec->catfile($keys, 'recipient.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');
+}
+
 
-my $tickets = RT::Tickets->new($RT::SystemUser);
-$tickets->OrderBy(FIELD => 'id', ORDER => 'DESC');
-$tickets->Limit(FIELD => 'id' ,OPERATOR => '>', VALUE => '0');
-my $tick = $tickets->First();
-ok ($tick->Id, "found ticket ".$tick->Id);
diff --git a/t/web/crypt-gnupg.t b/t/web/crypt-gnupg.t
index fb28c88..0297d4f 100644
--- a/t/web/crypt-gnupg.t
+++ b/t/web/crypt-gnupg.t
@@ -126,7 +126,7 @@ MAIL
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
     is( $msg->GetHeader('X-RT-Privacy'),
-        'PGP',
+        'GnuPG',
         "RT's outgoing mail has crypto"
     );
     is( $msg->GetHeader('X-RT-Incoming-Encryption'),
@@ -194,7 +194,7 @@ MAIL
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
     is( $msg->GetHeader('X-RT-Privacy'),
-        'PGP',
+        'GnuPG',
         "RT's outgoing mail has crypto"
     );
     is( $msg->GetHeader('X-RT-Incoming-Encryption'),
@@ -266,7 +266,7 @@ MAIL
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
     is( $msg->GetHeader('X-RT-Privacy'),
-        'PGP',
+        'GnuPG',
         "RT's outgoing mail has crypto"
     );
     is( $msg->GetHeader('X-RT-Incoming-Encryption'),
@@ -332,7 +332,7 @@ MAIL
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
     is( $msg->GetHeader('X-RT-Privacy'),
-        'PGP',
+        'GnuPG',
         "RT's outgoing mail has crypto"
     );
     is( $msg->GetHeader('X-RT-Incoming-Encryption'),
diff --git a/t/web/gnupg-outgoing.t b/t/web/gnupg-outgoing.t
index a46833c..8768ec7 100644
--- a/t/web/gnupg-outgoing.t
+++ b/t/web/gnupg-outgoing.t
@@ -188,7 +188,7 @@ foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed'} } ) {
     my $txn = $tick->Transactions->First;
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
-    is $msg->GetHeader('X-RT-Privacy'), 'PGP',
+    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";
@@ -212,7 +212,7 @@ foreach my $mail ( map cleanup_headers($_), @{ $mail{'encrypted'} } ) {
     my $txn = $tick->Transactions->First;
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
-    is $msg->GetHeader('X-RT-Privacy'), 'PGP',
+    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";
@@ -235,7 +235,7 @@ foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed_encrypted'} } ) {
     my $txn = $tick->Transactions->First;
     my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
 
-    is $msg->GetHeader('X-RT-Privacy'), 'PGP',
+    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";

commit f968e30370308db3f400dee76adbec94dfda2de9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Feb 6 16:32:19 2010 +0300

    rename test files

diff --git a/t/mail/smime/smime-incoming.t b/t/mail/smime/incoming.t
similarity index 100%
rename from t/mail/smime/smime-incoming.t
rename to t/mail/smime/incoming.t
diff --git a/t/mail/smime/smime-outgoing.t b/t/mail/smime/outgoing.t
similarity index 100%
rename from t/mail/smime/smime-outgoing.t
rename to t/mail/smime/outgoing.t

commit 9174ac12f8820a4132f80085a38e1cf9417108e0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 8 14:25:58 2010 +0300

    move DrySign into Crypt::Base

diff --git a/lib/RT/Crypt/Base.pm b/lib/RT/Crypt/Base.pm
index 7a26aa0..61b3ae2 100644
--- a/lib/RT/Crypt/Base.pm
+++ b/lib/RT/Crypt/Base.pm
@@ -11,6 +11,29 @@ sub VerifyDecrypt {
     return (exit_code => 1, status => []);
 }
 
+sub DrySign {
+    my $self = shift;
+    my %args = ( Signer => undef, @_ );
+    my $from = $args{'Signer'};
+
+    my $mime = MIME::Entity->build(
+        Type    => "text/plain",
+        From    => 'nobody at localhost',
+        To      => 'nobody at localhost',
+        Subject => "dry sign",
+        Data    => ['t'],
+    );
+
+    my %res = $self->SignEncrypt(
+        Sign    => 1,
+        Encrypt => 0,
+        Entity  => $mime,
+        Signer  => $from,
+    );
+
+    return $res{exit_code} == 0;
+}
+
 sub CheckIfProtected { return () }
 
 sub FindScatteredParts { return () }
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 558989f..ac57894 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2318,31 +2318,6 @@ Returns a true value if all went well.
 
 =cut
 
-sub DrySign {
-    my $self = shift;
-    my %args = ( Signer => undef, @_ );
-    my $from = $args{'Signer'};
-
-    my $mime = MIME::Entity->build(
-        Type    => "text/plain",
-        From    => 'nobody at localhost',
-        To      => 'nobody at localhost',
-        Subject => "dry sign",
-        Data    => ['t'],
-    );
-
-    my %res = $self->SignEncrypt(
-        Sign    => 1,
-        Encrypt => 0,
-        Entity  => $mime,
-        Signer  => $from,
-    );
-
-    return $res{exit_code} == 0;
-}
-
-1;
-
 =head2 Probe
 
 This routine returns true if RT's GnuPG support is configured and working 
@@ -2420,6 +2395,8 @@ if ($@ && $@ !~ qr{^Can't locate RT/Crypt/GnuPG_Local.pm}) {
     die $@;
 };
 
+1;
+
 # helper package to avoid using temp file
 package IO::Handle::CRLF;
 

commit 4946b49484ccdfe69e20db1d51cdc09514e0ab9a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 8 14:26:31 2010 +0300

    rename key files for smime

diff --git a/t/data/smime/keys/recipient.crt b/t/data/smime/keys/root at example.com.crt
similarity index 100%
rename from t/data/smime/keys/recipient.crt
rename to t/data/smime/keys/root at example.com.crt
diff --git a/t/data/smime/keys/recipient.csr b/t/data/smime/keys/root at example.com.csr
similarity index 100%
rename from t/data/smime/keys/recipient.csr
rename to t/data/smime/keys/root at example.com.csr
diff --git a/t/data/smime/keys/recipient.key b/t/data/smime/keys/root at example.com.key
similarity index 100%
rename from t/data/smime/keys/recipient.key
rename to t/data/smime/keys/root at example.com.key

commit 23f7322dfc7c987ea96609121fb8a48228b65f38
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Feb 8 14:31:07 2010 +0300

    tests changes

diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index 2b3c404..bfdf429 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 47;
+use RT::Test tests => 42;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
@@ -199,8 +199,8 @@ RT::Test->close_mailgate_ok($mail);
             shell_quote(
                 RT->Config->Get('SMIME')->{'OpenSSL'},
                 qw( smime -sign -nodetach -passin pass:123456),
-                -signer => File::Spec->catfile( $keys, 'recipient.crt' ),
-                -inkey  => File::Spec->catfile( $keys, 'recipient.key' ),
+                -signer => File::Spec->catfile( $keys, 'root at example.com.crt' ),
+                -inkey  => File::Spec->catfile( $keys, 'root at example.com.key' ),
             ),
             '|',
             shell_quote(
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index 96c7a9d..3d0bdc0 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -81,7 +81,7 @@ my $user;
     ok($user->Load('root'), "Loaded user 'root'");
     is($user->EmailAddress, 'root at localhost');
 
-    open my $fh, '<:raw', File::Spec->catfile($keys, 'recipient.crt')
+    open my $fh, '<:raw', File::Spec->catfile($keys, 'root at example.com.crt')
         or die $!;
     my ($status, $msg) = $user->AddCustomFieldValue(
         Field => 'SMIME Key',
diff --git a/t/web/gnupg-outgoing.t b/t/web/gnupg-outgoing.t
index 8768ec7..a956678 100644
--- a/t/web/gnupg-outgoing.t
+++ b/t/web/gnupg-outgoing.t
@@ -31,7 +31,8 @@ RT->Config->Set( GnuPGOptions =>
 RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
 
 RT::Test->import_gnupg_key('rt-recipient at example.com');
-RT::Test->import_gnupg_key('rt-test at example.com', 'public');
+my $user_email = 'root at example.com';
+RT::Test->import_gnupg_key($user_email, 'public');
 
 my $queue = RT::Test->load_or_create_queue(
     Name              => 'Regression',
@@ -88,7 +89,7 @@ diag "check in read-only mode that queue's props influence create/update ticket
     my ($id) = $ticket->Create(
         Subject   => 'test',
         Queue     => $queue->id,
-        Requestor => 'rt-test at example.com',
+        Requestor => $user_email,
     );
     ok $id, 'ticket created';
 
@@ -124,7 +125,7 @@ my $tid;
     ($tid) = $ticket->Create(
         Subject   => 'test',
         Queue     => $queue->id,
-        Requestor => 'rt-test at example.com',
+        Requestor => $user_email,
     );
     ok $tid, 'ticket created';
 }
@@ -146,12 +147,12 @@ foreach my $queue_set ( @variants ) {
 
 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');
+RT::Test->import_gnupg_key($user_email);
 
 $queue = RT::Test->load_or_create_queue(
     Name              => 'Regression',
-    CorrespondAddress => 'rt-test at example.com',
-    CommentAddress    => 'rt-test at example.com',
+    CorrespondAddress => $user_email,
+    CommentAddress    => $user_email,
 );
 ok $queue && $queue->id, 'changed props of the queue';
 
@@ -255,7 +256,7 @@ sub create_a_ticket {
     $m->goto_create_ticket( $queue );
     $m->form_name('TicketCreate');
     $m->field( Subject    => 'test' );
-    $m->field( Requestors => 'rt-test at example.com' );
+    $m->field( Requestors => $user_email );
     $m->field( Content    => 'Some content' );
 
     foreach ( qw(Sign Encrypt) ) {

commit 5c87d49e3d243cfb53efb86c25f07bff28415acd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 15:03:11 2010 +0300

    create our CA and generate keys from scratch

diff --git a/t/data/smime/keys/ca.crt b/t/data/smime/keys/ca.crt
deleted file mode 100644
index 9cacaa7..0000000
--- a/t/data/smime/keys/ca.crt
+++ /dev/null
@@ -1,37 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIGczCCBFugAwIBAgIBADANBgkqhkiG9w0BAQQFADCBhzELMAkGA1UEBhMCUlUx
-EzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVyc2J1cmcxDTAL
-BgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRlc3RlcjEcMBoG
-CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wMzAyMTAxNDA2MDBaFw0wNzAy
-MDkxNDA2MDBaMIGHMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEW
-MBQGA1UEBxMNU3QtUGV0ZXJzYnVyZzENMAsGA1UEChMEVGVzdDENMAsGA1UECxME
-dGVzdDEPMA0GA1UEAxMGVGVzdGVyMRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3Qu
-Y29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAm6o4DVBUf0CzcwW/
-psHnERPPLuZYXGY+ItoGtdFd0FYFTLFWF7wZJH3kJj3TIRYJ6tF/QXaLFcizqnCV
-VhrdIBOWlyz7oOZ0z+SKkVn6f0rg6NX8RKnWKeVSstn2wy3/EEiRnpnAxH6/4Ra4
-dKWcldvqRzgmrbQL5rWfwN+TOSFUiNC8HL18zPkwoAmq/D8nGvGO6W/5KUFiMRoH
-ZonmcO29rPBf/o1nCABQHeTH0MZoad4c453U8bpYy5iBx5TX7tdMCnVAcWMaU9qE
-N701rlp73n08veX40pVSSPm43caXYco+AZ82sT2MKbhPw/IJauaSflYdUXSKV0PN
-71yCYhQ8/d1L04jiuR+1LnAGeLh1nKmhK8uYA8B6f25/sjZN1rDjriRLa7+xFTTY
-kxnY8T+z/+LylHgEjOi4yfDibjyMoNyJSg/DT3BW04/aZuUAsTrAwAN/JwwW1QN1
-pZu6m0STOINFcdmP+NHpxQ/z1Q8aPwu4eB74vNCitJNrSe90Ow8+ufxaZVsi4+Eq
-HHScKLtdQxPs5rHshIXI+6WZhqoQEBPNkhSRyFkUXTYG07ro1/jeJnKdD/Lz7hYZ
-GSt5/km/NdeYYhRoHVF6z6yhAdX5VUyeCiDQqd7EA9oO3BsYn11G9qJBYSJd7gFQ
-UB8pTZhbINhhZxr2byomHobU9pkCAwEAAaOB5zCB5DAdBgNVHQ4EFgQUkzZg7t4t
-stLSkqP6sGLsN35+LPIwgbQGA1UdIwSBrDCBqYAUkzZg7t4tstLSkqP6sGLsN35+
-LPKhgY2kgYowgYcxCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRlMRYw
-FAYDVQQHEw1TdC1QZXRlcnNidXJnMQ0wCwYDVQQKEwRUZXN0MQ0wCwYDVQQLEwR0
-ZXN0MQ8wDQYDVQQDEwZUZXN0ZXIxHDAaBgkqhkiG9w0BCQEWDXRlc3RAdGVzdC5j
-b22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQQFAAOCAgEAmkPTDqkAixy0
-kNlTdsKdtMQDV+mc2OMNc1s2clYiAqhDx4AznTy6IYg4wEOaxbnjxQ8TnbOLC9cs
-M0lAGh15kPxeEe8yESJgVtj4IP41TPVkR/6+rKD7hP4y5JOSRPhYh9IkTH9byeJl
-6Q6WvQp6psO+eDQZGwR2ceqb6/NbImKfNW73sRm9OHBwz4S2NtmIx4LoqaQjcPck
-z+81gctY16MFs4ILRfa+lydLIyqa97KcMIzntWl2YgkWX9QvlPIF9ornLlAy1rvN
-cRaNdkmA9vOcCmXxmTFttZ5DHCxRYPx4pV8oJMjqW4xLQk9Ob60oUWfBLxwScrTP
-MuzgybzTWrAY5bJakperd35Qat56bgUDvRscgmXKuCeQZ6m1DyeYAxtJfkrOTyTw
-GzUg9+IQX+tcq0UJ77Rx2hUYyINTOuwEEBUF36l0QDO8QWlS9XkF/iif7fQMHqej
-if8umdWWzdEpHWiZMFvTDnjvlF/2htX2PVnRcOXRyLemcPsutum0RJJyCNjgGmJN
-4WcM6kS8lbBgJGJUz3Cs2ILoj6e8XXqgxVF4Cpb3troYuFzPUuWlNOyN8FkAgztU
-cycqvtAUOvAYKwlGbT9oiYyGgraVmmS4Cc/2OZ8BsG5gxk7HfFXRkt8l7U/0DWxq
-++kpxBnI+Tz1RGNNIcfC6M5XB39b+bY=
------END CERTIFICATE-----
diff --git a/t/data/smime/keys/ca.key b/t/data/smime/keys/ca.key
deleted file mode 100644
index 0f58a24..0000000
--- a/t/data/smime/keys/ca.key
+++ /dev/null
@@ -1,54 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,B51FD7E25F477D16
-
-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
------END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/demoCA/cacert.pem b/t/data/smime/keys/demoCA/cacert.pem
new file mode 100644
index 0000000..dba7b13
--- /dev/null
+++ b/t/data/smime/keys/demoCA/cacert.pem
@@ -0,0 +1,64 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:14
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:20:53 2010 GMT
+            Not After : Feb  7 16:20:53 2013 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:be:cc:62:70:bf:42:ee:9d:f0:05:04:2b:05:46:
+                    4e:c9:60:6a:b4:31:8c:a5:60:25:79:05:61:88:fe:
+                    36:9e:63:24:bf:33:91:6f:6a:90:27:81:47:5e:2f:
+                    49:54:19:c7:02:51:37:d9:ff:0b:9b:8a:cd:ed:7f:
+                    b7:6b:bc:0a:de:e5:c8:32:f7:a4:16:51:d1:3f:a4:
+                    02:96:98:09:83:e2:ed:81:19:bb:e3:d4:2b:f1:87:
+                    97:03:08:05:e6:f7:65:c6:90:48:9d:75:07:31:93:
+                    04:6d:09:b7:0f:df:fa:f2:b3:ff:e1:44:f4:18:03:
+                    4f:59:b6:ba:d2:36:8b:0e:b3
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+                DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=CA Owner/emailAddress=ca.owner at example.com
+                serial:8A:6A:CD:51:BE:94:A0:14
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha1WithRSAEncryption
+        bd:d5:97:ad:b6:1e:cf:e2:4c:be:02:a7:da:d4:ea:ff:2b:0d:
+        dd:f5:23:ef:0a:9b:4d:45:ad:8b:67:52:e6:16:13:56:03:18:
+        c8:2e:8d:de:b6:99:fd:0f:f0:fd:c3:6b:e2:db:5d:97:45:bb:
+        12:bd:ef:eb:b7:2b:a2:68:46:96:30:c7:00:66:b6:f2:92:ba:
+        24:0f:e7:ff:d7:3c:e2:9f:f3:db:44:14:62:54:56:db:43:f0:
+        f1:b5:3a:ce:dc:17:ad:52:44:be:c4:d8:7c:3f:a6:fa:e8:4f:
+        2c:ed:88:ad:a7:2d:0f:14:a4:76:70:67:9e:8b:45:51:3d:8b:
+        4d:6b
+-----BEGIN CERTIFICATE-----
+MIIDXDCCAsWgAwIBAgIJAIpqzVG+lKAUMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjIwNTNaFw0xMzAyMDcx
+NjIwNTNaMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVy
+MSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAvsxicL9C7p3wBQQrBUZOyWBqtDGMpWAleQVhiP42
+nmMkvzORb2qQJ4FHXi9JVBnHAlE32f8Lm4rN7X+3a7wK3uXIMvekFlHRP6QClpgJ
+g+LtgRm749Qr8YeXAwgF5vdlxpBInXUHMZMEbQm3D9/68rP/4UT0GANPWba60jaL
+DrMCAwEAAaOB4zCB4DAdBgNVHQ4EFgQUjRstvb0k6BlirkzJKliQCBzRBSswgbAG
+A1UdIwSBqDCBpYAUjRstvb0k6BlirkzJKliQCBzRBSuhgYGkfzB9MQswCQYDVQQG
+EwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lk
+Z2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYU
+Y2Eub3duZXJAZXhhbXBsZS5jb22CCQCKas1RvpSgFDAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4GBAL3Vl622Hs/iTL4Cp9rU6v8rDd31I+8Km01FrYtnUuYW
+E1YDGMgujd62mf0P8P3Da+LbXZdFuxK97+u3K6JoRpYwxwBmtvKSuiQP5//XPOKf
+89tEFGJUVttD8PG1Os7cF61SRL7E2Hw/pvroTyztiK2nLQ8UpHZwZ56LRVE9i01r
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/demoCA/careq.pem b/t/data/smime/keys/demoCA/careq.pem
new file mode 100644
index 0000000..4d90509
--- /dev/null
+++ b/t/data/smime/keys/demoCA/careq.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBvTCCASYCAQAwfTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+ITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAxMIQ0Eg
+T3duZXIxIzAhBgkqhkiG9w0BCQEWFGNhLm93bmVyQGV4YW1wbGUuY29tMIGfMA0G
+CSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+zGJwv0LunfAFBCsFRk7JYGq0MYylYCV5
+BWGI/jaeYyS/M5FvapAngUdeL0lUGccCUTfZ/wubis3tf7drvAre5cgy96QWUdE/
+pAKWmAmD4u2BGbvj1Cvxh5cDCAXm92XGkEiddQcxkwRtCbcP3/rys//hRPQYA09Z
+trrSNosOswIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAqEbpFo5e5r0+bCXMsx8o
+oTzD+DQbBmrZWC4yIyQae15Jsrj/pxEU8NRh8jRZI1yT5SaFfkARZO6Z3UfvpPSD
+AKB4LyRK4Ff/G2PDMH8rh+Y4iZFowAfxyW15JE2ZZmpACoBGIkEf2xYDzbFM1Drt
+PUDXYchCz5O7SZgK1DDXk4M=
+-----END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/demoCA/crlnumber b/t/data/smime/keys/demoCA/crlnumber
new file mode 100644
index 0000000..8a0f05e
--- /dev/null
+++ b/t/data/smime/keys/demoCA/crlnumber
@@ -0,0 +1 @@
+01
diff --git a/t/data/smime/keys/demoCA/index.txt b/t/data/smime/keys/demoCA/index.txt
new file mode 100644
index 0000000..0afe6c7
--- /dev/null
+++ b/t/data/smime/keys/demoCA/index.txt
@@ -0,0 +1,3 @@
+V	130207162053Z		8A6ACD51BE94A014	unknown	/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=CA Owner/emailAddress=ca.owner at example.com
+V	110208162542Z		8A6ACD51BE94A015	unknown	/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=sender/emailAddress=sender at example.com
+V	110208163008Z		8A6ACD51BE94A016	unknown	/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=Enoch Root/emailAddress=root at example.com
diff --git a/t/data/smime/keys/demoCA/index.txt.attr b/t/data/smime/keys/demoCA/index.txt.attr
new file mode 100644
index 0000000..8f7e63a
--- /dev/null
+++ b/t/data/smime/keys/demoCA/index.txt.attr
@@ -0,0 +1 @@
+unique_subject = yes
diff --git a/t/data/smime/keys/demoCA/index.txt.attr.old b/t/data/smime/keys/demoCA/index.txt.attr.old
new file mode 100644
index 0000000..8f7e63a
--- /dev/null
+++ b/t/data/smime/keys/demoCA/index.txt.attr.old
@@ -0,0 +1 @@
+unique_subject = yes
diff --git a/t/data/smime/keys/demoCA/index.txt.old b/t/data/smime/keys/demoCA/index.txt.old
new file mode 100644
index 0000000..1433378
--- /dev/null
+++ b/t/data/smime/keys/demoCA/index.txt.old
@@ -0,0 +1,2 @@
+V	130207162053Z		8A6ACD51BE94A014	unknown	/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=CA Owner/emailAddress=ca.owner at example.com
+V	110208162542Z		8A6ACD51BE94A015	unknown	/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=sender/emailAddress=sender at example.com
diff --git a/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A014.pem b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A014.pem
new file mode 100644
index 0000000..dba7b13
--- /dev/null
+++ b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A014.pem
@@ -0,0 +1,64 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:14
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:20:53 2010 GMT
+            Not After : Feb  7 16:20:53 2013 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (1024 bit)
+                Modulus (1024 bit):
+                    00:be:cc:62:70:bf:42:ee:9d:f0:05:04:2b:05:46:
+                    4e:c9:60:6a:b4:31:8c:a5:60:25:79:05:61:88:fe:
+                    36:9e:63:24:bf:33:91:6f:6a:90:27:81:47:5e:2f:
+                    49:54:19:c7:02:51:37:d9:ff:0b:9b:8a:cd:ed:7f:
+                    b7:6b:bc:0a:de:e5:c8:32:f7:a4:16:51:d1:3f:a4:
+                    02:96:98:09:83:e2:ed:81:19:bb:e3:d4:2b:f1:87:
+                    97:03:08:05:e6:f7:65:c6:90:48:9d:75:07:31:93:
+                    04:6d:09:b7:0f:df:fa:f2:b3:ff:e1:44:f4:18:03:
+                    4f:59:b6:ba:d2:36:8b:0e:b3
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Subject Key Identifier: 
+                8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+                DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=CA Owner/emailAddress=ca.owner at example.com
+                serial:8A:6A:CD:51:BE:94:A0:14
+
+            X509v3 Basic Constraints: 
+                CA:TRUE
+    Signature Algorithm: sha1WithRSAEncryption
+        bd:d5:97:ad:b6:1e:cf:e2:4c:be:02:a7:da:d4:ea:ff:2b:0d:
+        dd:f5:23:ef:0a:9b:4d:45:ad:8b:67:52:e6:16:13:56:03:18:
+        c8:2e:8d:de:b6:99:fd:0f:f0:fd:c3:6b:e2:db:5d:97:45:bb:
+        12:bd:ef:eb:b7:2b:a2:68:46:96:30:c7:00:66:b6:f2:92:ba:
+        24:0f:e7:ff:d7:3c:e2:9f:f3:db:44:14:62:54:56:db:43:f0:
+        f1:b5:3a:ce:dc:17:ad:52:44:be:c4:d8:7c:3f:a6:fa:e8:4f:
+        2c:ed:88:ad:a7:2d:0f:14:a4:76:70:67:9e:8b:45:51:3d:8b:
+        4d:6b
+-----BEGIN CERTIFICATE-----
+MIIDXDCCAsWgAwIBAgIJAIpqzVG+lKAUMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjIwNTNaFw0xMzAyMDcx
+NjIwNTNaMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVy
+MSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAvsxicL9C7p3wBQQrBUZOyWBqtDGMpWAleQVhiP42
+nmMkvzORb2qQJ4FHXi9JVBnHAlE32f8Lm4rN7X+3a7wK3uXIMvekFlHRP6QClpgJ
+g+LtgRm749Qr8YeXAwgF5vdlxpBInXUHMZMEbQm3D9/68rP/4UT0GANPWba60jaL
+DrMCAwEAAaOB4zCB4DAdBgNVHQ4EFgQUjRstvb0k6BlirkzJKliQCBzRBSswgbAG
+A1UdIwSBqDCBpYAUjRstvb0k6BlirkzJKliQCBzRBSuhgYGkfzB9MQswCQYDVQQG
+EwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lk
+Z2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYU
+Y2Eub3duZXJAZXhhbXBsZS5jb22CCQCKas1RvpSgFDAMBgNVHRMEBTADAQH/MA0G
+CSqGSIb3DQEBBQUAA4GBAL3Vl622Hs/iTL4Cp9rU6v8rDd31I+8Km01FrYtnUuYW
+E1YDGMgujd62mf0P8P3Da+LbXZdFuxK97+u3K6JoRpYwxwBmtvKSuiQP5//XPOKf
+89tEFGJUVttD8PG1Os7cF61SRL7E2Hw/pvroTyztiK2nLQ8UpHZwZ56LRVE9i01r
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A015.pem b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A015.pem
new file mode 100644
index 0000000..657491f
--- /dev/null
+++ b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A015.pem
@@ -0,0 +1,57 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:15
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:25:42 2010 GMT
+            Not After : Feb  8 16:25:42 2011 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=sender/emailAddress=sender at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:a8:38:41:90:1d:e7:cd:2b:cb:62:cf:ad:ff:70:
+                    f6:44:5d:f3:4b:7e:21:75:b6:5c:e1:7e:c2:27:3b:
+                    85:eb:72:9b:5a:94:0a:69:1d:83:ca:c5:91:b2:3f:
+                    04:72:61:e4:b8:eb:5b:ce:b5:10:77:d8:a7:df:8b:
+                    c9:5a:14:15:61
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                F3:34:E6:C3:51:78:92:9B:93:49:8E:4E:30:BC:18:88:24:69:34:09
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+
+    Signature Algorithm: sha1WithRSAEncryption
+        53:90:db:6d:d0:aa:0f:cb:32:94:2e:a5:bc:d1:0d:27:7c:85:
+        d8:08:6c:52:fe:b1:4d:18:94:c2:10:ff:4f:8f:71:2b:81:bf:
+        a3:aa:11:3e:6e:72:5f:4a:1a:38:a0:3f:5f:2c:89:d6:af:c5:
+        6c:ff:59:63:13:2d:fb:5a:2f:58:b2:77:3e:52:83:07:01:c6:
+        4e:cf:19:9c:9a:f3:17:ac:63:c2:00:f3:18:b9:27:a6:d5:1a:
+        14:11:d6:f0:db:9b:de:e4:ad:cd:29:01:fd:38:c1:21:9c:fa:
+        a2:0b:5e:6d:5d:14:54:ce:0a:3c:f7:af:0f:e6:7f:c7:39:00:
+        b3:33
+-----BEGIN CERTIFICATE-----
+MIICqzCCAhSgAwIBAgIJAIpqzVG+lKAVMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjI1NDJaFw0xMTAyMDgx
+NjI1NDJaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnNlbmRlcjEh
+MB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKg4QZAd580ry2LPrf9w9kRd80t+IXW2XOF+wic7hetym1qUCmkd
+g8rFkbI/BHJh5LjrW861EHfYp9+LyVoUFWECAwEAAaN7MHkwCQYDVR0TBAIwADAs
+BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD
+VR0OBBYEFPM05sNReJKbk0mOTjC8GIgkaTQJMB8GA1UdIwQYMBaAFI0bLb29JOgZ
+Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAFOQ223Qqg/LMpQupbzRDSd8
+hdgIbFL+sU0YlMIQ/0+PcSuBv6OqET5ucl9KGjigP18sidavxWz/WWMTLftaL1iy
+dz5SgwcBxk7PGZya8xesY8IA8xi5J6bVGhQR1vDbm97krc0pAf04wSGc+qILXm1d
+FFTOCjz3rw/mf8c5ALMz
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem
new file mode 100644
index 0000000..3085e41
--- /dev/null
+++ b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem
@@ -0,0 +1,57 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:16
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:30:08 2010 GMT
+            Not After : Feb  8 16:30:08 2011 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Enoch Root/emailAddress=root at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:b2:77:b9:bc:09:7d:14:8e:6b:6f:7e:33:a9:95:
+                    21:5d:f3:3c:91:61:f1:bc:5c:1d:7e:e7:54:25:e8:
+                    cb:5f:b7:18:0e:23:26:00:42:09:bd:89:da:5c:06:
+                    cb:52:08:43:f6:4e:fe:dd:f8:0a:8a:95:35:8f:4a:
+                    25:16:da:e6:bf
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                97:D7:80:52:CA:BB:E1:02:60:93:53:42:A2:14:12:E8:ED:1F:C0:7A
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+
+    Signature Algorithm: sha1WithRSAEncryption
+        95:5d:21:4f:cc:62:44:49:ad:f7:7f:75:ee:b0:e8:ec:0c:25:
+        39:72:4d:9d:98:86:99:81:81:02:a0:d1:2b:3e:2b:5c:01:14:
+        77:81:aa:be:a7:ff:9a:db:b4:b6:9c:b2:2e:d7:eb:4e:32:b8:
+        69:dd:7a:3f:3c:38:50:22:46:e5:21:1c:fc:d0:bf:04:79:5a:
+        5b:ac:53:77:33:be:27:63:6e:d7:47:e7:2f:75:15:84:db:9a:
+        08:c0:0a:b5:00:77:51:fe:64:df:97:95:ee:7f:2a:f8:83:72:
+        67:4a:c9:75:7e:a6:dd:e5:d6:83:c5:a6:b9:0d:0e:24:39:ca:
+        06:d7
+-----BEGIN CERTIFICATE-----
+MIICrTCCAhagAwIBAgIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjMwMDhaFw0xMTAyMDgx
+NjMwMDhaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMTCkVub2NoIFJv
+b3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
+AQEFAANLADBIAkEAsne5vAl9FI5rb34zqZUhXfM8kWHxvFwdfudUJejLX7cYDiMm
+AEIJvYnaXAbLUghD9k7+3fgKipU1j0olFtrmvwIDAQABo3sweTAJBgNVHRMEAjAA
+MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
+BgNVHQ4EFgQUl9eAUsq74QJgk1NCohQS6O0fwHowHwYDVR0jBBgwFoAUjRstvb0k
+6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlV0hT8xiREmt93917rDo
+7AwlOXJNnZiGmYGBAqDRKz4rXAEUd4Gqvqf/mtu0tpyyLtfrTjK4ad16Pzw4UCJG
+5SEc/NC/BHlaW6xTdzO+J2Nu10fnL3UVhNuaCMAKtQB3Uf5k35eV7n8q+INyZ0rJ
+dX6m3eXWg8WmuQ0OJDnKBtc=
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/demoCA/private/cakey.pem b/t/data/smime/keys/demoCA/private/cakey.pem
new file mode 100644
index 0000000..ad95c8d
--- /dev/null
+++ b/t/data/smime/keys/demoCA/private/cakey.pem
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,8580147E208C5674
+
+GTz9b2WFdP7gNjUWQnhWqq2o8bpYPbmPTLSyefUfI2UxL0bW96VBKyLpx/FO7Zxr
+itfItZA4A7hG+CJLa6pz5C4/9onzHeihhLLDov3pE1hjZwwPFs1IHM/q1KLU4tK4
+yb/Xx1pw/3L1nlvWy4CQ/F1pmHG+akQNopy2Ru0XWLVw/gysmff8GW94Awx5MyZd
+81tvuFu2U2BYdPbC/Zc+hrlTdqG2btgdll39gjRoNvLbA4tifLNy264yOS71lxF/
+rOtavqzCULo/cTTumcZzbMnowjpdrPliuGg6rox3xc3zFjNfogu7okH53XtOZClQ
+n3/jjqI1LEUhOC0omUck4q3XbaCWGg6X/MUL8Fae+jDUs5NISt75xVs1uJdU2DuB
+xUwtgzJCbt5eovbczmoKm44nY3TqsITG+vuI7qim3wds8WPbM4lnz7fx0AbHYOIK
+ceCxDJirQRmblImJybPHJL6uuCo91Ahx7NmLcGw35QhhQf/EfKPJyh4Ih7+Cn2il
+EGW9RWS7hl9JSCOZs30YwPQz1bgCHIt0+31WSK4hbZ/IyPnDrMY4XNVCeWxX2xcF
+y2VjpoW305Glu2D522n0jUe/YJGHBaA7ijQkLpw2nL0qstlkq/2RoGZaDm0gUCUG
+dNbmeQrOF7dJtSKKjxy/DqMPw+ymn/YCXVaCPvIEuqHyFKnUNJ/ak4vnAeV7Jrhz
+0OlyqNR4O/FKjf4pgsTHqodTQrxHA2d/n/Evnes/TevnIp6sa8HpkMcJc2DL9hKB
+aIWFQxGynI/S9juZXSKdTOMcUbSsicVELzzk+spHlZ9xKpuBvJvWxQ==
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/demoCA/serial b/t/data/smime/keys/demoCA/serial
new file mode 100644
index 0000000..7c39862
--- /dev/null
+++ b/t/data/smime/keys/demoCA/serial
@@ -0,0 +1 @@
+8A6ACD51BE94A017
diff --git a/t/data/smime/keys/demoCA/serial.old b/t/data/smime/keys/demoCA/serial.old
new file mode 100644
index 0000000..7c4c23b
--- /dev/null
+++ b/t/data/smime/keys/demoCA/serial.old
@@ -0,0 +1 @@
+8A6ACD51BE94A016
diff --git a/t/data/smime/keys/root at example.com.crt b/t/data/smime/keys/root at example.com.crt
index 19768ac..3085e41 100644
--- a/t/data/smime/keys/root at example.com.crt
+++ b/t/data/smime/keys/root at example.com.crt
@@ -1,52 +1,23 @@
 Certificate:
     Data:
         Version: 3 (0x2)
-        Serial Number: 2 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:16
         Signature Algorithm: sha1WithRSAEncryption
-        Issuer: C=RU, ST=Some-State, L=St-Petersburg, O=Test, OU=test, CN=Tester/emailAddress=test at test.com
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
         Validity
-            Not Before: May 23 07:41:48 2007 GMT
-            Not After : Jan 29 07:41:48 2021 GMT
-        Subject: C=RU, ST=Some-State, O=Test, CN=root/emailAddress=root at example.com
+            Not Before: Feb  8 16:30:08 2010 GMT
+            Not After : Feb  8 16:30:08 2011 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Enoch Root/emailAddress=root at example.com
         Subject Public Key Info:
             Public Key Algorithm: rsaEncryption
-            RSA Public Key: (4096 bit)
-                Modulus (4096 bit):
-                    00:af:b6:6f:04:9f:81:cc:44:a4:b1:f1:0c:c1:7c:
-                    d7:41:9d:8c:10:df:dc:f2:8f:b5:13:ce:f6:03:8d:
-                    0b:b3:f0:bf:b8:dd:cc:f6:4d:26:8c:41:9a:cd:08:
-                    60:36:68:0d:4d:5c:c3:b5:2a:71:cc:39:de:19:19:
-                    61:fb:80:64:5c:ac:64:b8:66:c2:2d:77:2b:8e:5c:
-                    88:82:41:51:f8:d8:8b:35:4f:80:66:57:8c:2e:a4:
-                    cb:4b:4f:c3:38:69:ec:43:c7:00:00:b0:0c:db:01:
-                    50:d2:2c:6e:b0:a5:83:8a:dc:aa:0a:84:a4:4c:82:
-                    f3:77:e1:a9:a2:cc:20:36:80:65:86:62:2b:50:4a:
-                    2c:86:d0:c5:f0:52:7b:ca:5a:04:a9:ed:28:57:bc:
-                    07:e5:e3:a1:a7:60:01:69:09:eb:e5:2c:75:61:53:
-                    4a:e7:00:3b:b6:e9:3c:e2:d2:b3:99:76:d9:5f:70:
-                    a4:7f:3c:d1:46:1e:8e:ea:9d:a3:c0:95:d5:a5:45:
-                    ba:68:64:fb:92:5c:23:05:2e:36:50:aa:0a:68:ba:
-                    23:cd:92:29:42:f8:d6:d8:14:09:e7:f2:52:c5:f1:
-                    9a:c8:9a:aa:4e:bc:26:9f:19:00:bf:46:37:d4:3d:
-                    f3:b8:72:aa:b5:05:a5:94:31:2c:dc:9c:bb:e3:a1:
-                    1e:82:03:ca:29:b5:d4:64:43:51:4c:f2:77:04:bb:
-                    43:97:a3:a5:e7:56:13:47:4c:02:36:db:d9:53:20:
-                    da:bb:a6:d9:54:36:dc:76:15:e0:eb:d7:50:9c:72:
-                    66:6c:7d:59:2f:dd:51:d6:7c:b6:2d:f3:0e:f9:7a:
-                    cf:0f:63:59:3a:6b:8e:3f:c7:76:b0:11:1b:12:e5:
-                    b9:90:66:30:d2:f6:ba:04:de:33:27:58:61:8f:ed:
-                    ed:14:b8:df:2e:5a:b5:cc:ff:a3:72:1c:ef:46:09:
-                    37:1c:81:ca:13:e9:43:9f:38:fb:69:bb:64:21:9e:
-                    e4:b4:44:be:79:61:52:46:cf:a5:83:7f:b5:c7:91:
-                    5b:28:92:17:d2:5e:45:4b:ad:9f:c7:53:fb:1c:3c:
-                    e7:56:92:a7:9f:e7:e3:59:19:36:64:8c:8c:78:00:
-                    2a:f7:93:2d:79:90:91:e1:73:31:24:ca:4b:4d:9b:
-                    1e:35:61:8a:3f:0c:f8:c8:c0:76:6f:01:d8:2b:c1:
-                    e0:0a:f1:fb:c7:a0:2b:48:30:d4:30:bf:47:2b:e5:
-                    b9:8d:64:e1:d4:3a:80:71:60:1e:02:02:d2:1a:f2:
-                    2c:b2:88:d6:21:da:27:83:36:6c:51:45:e3:72:39:
-                    83:16:65:5c:58:e6:14:87:02:95:88:07:f9:b3:20:
-                    58:d9:91
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:b2:77:b9:bc:09:7d:14:8e:6b:6f:7e:33:a9:95:
+                    21:5d:f3:3c:91:61:f1:bc:5c:1d:7e:e7:54:25:e8:
+                    cb:5f:b7:18:0e:23:26:00:42:09:bd:89:da:5c:06:
+                    cb:52:08:43:f6:4e:fe:dd:f8:0a:8a:95:35:8f:4a:
+                    25:16:da:e6:bf
                 Exponent: 65537 (0x10001)
         X509v3 extensions:
             X509v3 Basic Constraints: 
@@ -54,71 +25,33 @@ Certificate:
             Netscape Comment: 
                 OpenSSL Generated Certificate
             X509v3 Subject Key Identifier: 
-                48:07:DC:D8:10:D8:20:53:F5:7E:23:52:84:81:E7:2C:F3:16:BC:DB
+                97:D7:80:52:CA:BB:E1:02:60:93:53:42:A2:14:12:E8:ED:1F:C0:7A
             X509v3 Authority Key Identifier: 
-                keyid:93:36:60:EE:DE:2D:B2:D2:D2:92:A3:FA:B0:62:EC:37:7E:7E:2C:F2
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
 
     Signature Algorithm: sha1WithRSAEncryption
-        74:de:e4:e3:4c:54:07:25:ed:d5:a1:e2:7e:8b:63:1d:a7:2a:
-        e9:73:84:08:dc:00:33:99:12:60:73:c1:a7:a8:01:fa:48:35:
-        87:81:97:7a:86:fc:6d:67:fc:9e:ce:79:52:7f:7e:86:bf:ca:
-        08:cc:2d:58:ac:9b:49:d0:97:bb:75:82:79:93:52:e8:f1:26:
-        eb:1a:90:c3:1c:c3:23:6b:d2:18:a4:d4:11:2c:ac:7a:60:ce:
-        e8:77:b1:73:1a:0a:31:41:02:76:38:78:ef:95:cb:0e:60:35:
-        9e:6d:1f:dc:3a:fc:77:e2:dc:67:34:65:f2:c5:78:c2:96:f7:
-        d7:1a:24:66:61:5c:fb:b9:14:13:7e:5d:81:9e:7f:44:99:33:
-        db:9b:e1:89:89:d8:98:88:02:04:83:63:8b:c2:c5:cf:83:f0:
-        00:c3:81:47:e4:6d:c5:11:cb:13:6d:de:ca:37:f1:68:51:45:
-        ac:c2:8a:69:43:32:de:65:f1:10:09:e1:1c:60:6f:3b:49:39:
-        47:a8:bd:34:f2:a5:12:ec:dd:21:32:8f:41:9c:04:26:33:1a:
-        b3:1b:35:94:2b:69:fe:6c:b6:c2:6e:d0:76:89:09:51:27:e3:
-        f9:35:78:ee:e8:7a:28:3b:f6:60:42:55:82:bb:b0:9d:91:5c:
-        38:0b:9f:2e:02:c6:54:3c:55:0f:09:3c:37:81:52:48:12:1e:
-        42:6c:d7:e1:dd:e1:cc:13:60:fa:7a:e0:02:2d:7d:d9:4e:bb:
-        04:29:f8:8f:c3:73:17:f5:44:76:1d:cd:dc:12:39:0a:2b:a3:
-        fe:19:9d:4f:ed:c6:96:11:96:5a:b2:7e:88:f0:a4:5a:05:63:
-        d6:26:17:d2:98:22:59:f9:32:33:6e:a6:d8:ed:a6:b0:03:c4:
-        d7:18:1c:a9:67:b9:4c:d0:cc:23:f5:d3:36:f2:f4:93:4b:f2:
-        aa:06:91:27:c2:d8:a1:79:ea:88:58:e0:d3:6e:41:2a:2e:58:
-        48:7c:d1:a9:11:a0:c9:93:6b:9b:d1:60:e9:de:eb:a5:92:1c:
-        e3:e3:4b:96:01:6a:e8:21:01:c8:c7:cd:0b:fe:81:6b:8e:33:
-        4e:f0:05:17:c7:a0:de:9d:e6:78:88:7e:3c:1c:6d:51:04:49:
-        a4:4a:62:75:50:dc:55:a4:65:00:2d:c2:55:0b:16:39:24:e2:
-        f7:62:e0:75:d2:45:a2:60:94:36:84:ce:ed:70:9f:f0:eb:96:
-        b8:27:49:4f:a6:e7:52:49:7d:d1:8e:11:12:80:38:0c:92:ca:
-        3f:a2:2a:7b:87:a5:97:7e:a2:ba:b6:d7:ce:82:14:6d:42:52:
-        d5:17:b9:55:d2:a2:f6:0a
+        95:5d:21:4f:cc:62:44:49:ad:f7:7f:75:ee:b0:e8:ec:0c:25:
+        39:72:4d:9d:98:86:99:81:81:02:a0:d1:2b:3e:2b:5c:01:14:
+        77:81:aa:be:a7:ff:9a:db:b4:b6:9c:b2:2e:d7:eb:4e:32:b8:
+        69:dd:7a:3f:3c:38:50:22:46:e5:21:1c:fc:d0:bf:04:79:5a:
+        5b:ac:53:77:33:be:27:63:6e:d7:47:e7:2f:75:15:84:db:9a:
+        08:c0:0a:b5:00:77:51:fe:64:df:97:95:ee:7f:2a:f8:83:72:
+        67:4a:c9:75:7e:a6:dd:e5:d6:83:c5:a6:b9:0d:0e:24:39:ca:
+        06:d7
 -----BEGIN CERTIFICATE-----
-MIIF3zCCA8egAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMCUlUx
-EzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVyc2J1cmcxDTAL
-BgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRlc3RlcjEcMBoG
-CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wNzA1MjMwNzQxNDhaFw0yMTAx
-MjkwNzQxNDhaMGExCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRlMQ0w
-CwYDVQQKEwRUZXN0MQ0wCwYDVQQDEwRyb290MR8wHQYJKoZIhvcNAQkBFhByb290
-QGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7Zv
-BJ+BzESksfEMwXzXQZ2MEN/c8o+1E872A40Ls/C/uN3M9k0mjEGazQhgNmgNTVzD
-tSpxzDneGRlh+4BkXKxkuGbCLXcrjlyIgkFR+NiLNU+AZleMLqTLS0/DOGnsQ8cA
-ALAM2wFQ0ixusKWDityqCoSkTILzd+GposwgNoBlhmIrUEoshtDF8FJ7yloEqe0o
-V7wH5eOhp2ABaQnr5Sx1YVNK5wA7tuk84tKzmXbZX3CkfzzRRh6O6p2jwJXVpUW6
-aGT7klwjBS42UKoKaLojzZIpQvjW2BQJ5/JSxfGayJqqTrwmnxkAv0Y31D3zuHKq
-tQWllDEs3Jy746EeggPKKbXUZENRTPJ3BLtDl6Ol51YTR0wCNtvZUyDau6bZVDbc
-dhXg69dQnHJmbH1ZL91R1ny2LfMO+XrPD2NZOmuOP8d2sBEbEuW5kGYw0va6BN4z
-J1hhj+3tFLjfLlq1zP+jchzvRgk3HIHKE+lDnzj7abtkIZ7ktES+eWFSRs+lg3+1
-x5FbKJIX0l5FS62fx1P7HDznVpKnn+fjWRk2ZIyMeAAq95MteZCR4XMxJMpLTZse
-NWGKPwz4yMB2bwHYK8HgCvH7x6ArSDDUML9HK+W5jWTh1DqAcWAeAgLSGvIssojW
-IdongzZsUUXjcjmDFmVcWOYUhwKViAf5syBY2ZECAwEAAaN7MHkwCQYDVR0TBAIw
-ADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUw
-HQYDVR0OBBYEFEgH3NgQ2CBT9X4jUoSB5yzzFrzbMB8GA1UdIwQYMBaAFJM2YO7e
-LbLS0pKj+rBi7Dd+fizyMA0GCSqGSIb3DQEBBQUAA4ICAQB03uTjTFQHJe3VoeJ+
-i2Mdpyrpc4QI3AAzmRJgc8GnqAH6SDWHgZd6hvxtZ/yeznlSf36Gv8oIzC1YrJtJ
-0Je7dYJ5k1Lo8SbrGpDDHMMja9IYpNQRLKx6YM7od7FzGgoxQQJ2OHjvlcsOYDWe
-bR/cOvx34txnNGXyxXjClvfXGiRmYVz7uRQTfl2Bnn9EmTPbm+GJidiYiAIEg2OL
-wsXPg/AAw4FH5G3FEcsTbd7KN/FoUUWswoppQzLeZfEQCeEcYG87STlHqL008qUS
-7N0hMo9BnAQmMxqzGzWUK2n+bLbCbtB2iQlRJ+P5NXju6HooO/ZgQlWCu7CdkVw4
-C58uAsZUPFUPCTw3gVJIEh5CbNfh3eHME2D6euACLX3ZTrsEKfiPw3MX9UR2Hc3c
-EjkKK6P+GZ1P7caWEZZasn6I8KRaBWPWJhfSmCJZ+TIzbqbY7aawA8TXGBypZ7lM
-0Mwj9dM28vSTS/KqBpEnwtiheeqIWODTbkEqLlhIfNGpEaDJk2ub0WDp3uulkhzj
-40uWAWroIQHIx80L/oFrjjNO8AUXx6DeneZ4iH48HG1RBEmkSmJ1UNxVpGUALcJV
-CxY5JOL3YuB10kWiYJQ2hM7tcJ/w65a4J0lPpudSSX3RjhESgDgMkso/oip7h6WX
-fqK6ttfOghRtQlLVF7lV0qL2Cg==
+MIICrTCCAhagAwIBAgIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjMwMDhaFw0xMTAyMDgx
+NjMwMDhaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMTCkVub2NoIFJv
+b3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
+AQEFAANLADBIAkEAsne5vAl9FI5rb34zqZUhXfM8kWHxvFwdfudUJejLX7cYDiMm
+AEIJvYnaXAbLUghD9k7+3fgKipU1j0olFtrmvwIDAQABo3sweTAJBgNVHRMEAjAA
+MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
+BgNVHQ4EFgQUl9eAUsq74QJgk1NCohQS6O0fwHowHwYDVR0jBBgwFoAUjRstvb0k
+6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlV0hT8xiREmt93917rDo
+7AwlOXJNnZiGmYGBAqDRKz4rXAEUd4Gqvqf/mtu0tpyyLtfrTjK4ad16Pzw4UCJG
+5SEc/NC/BHlaW6xTdzO+J2Nu10fnL3UVhNuaCMAKtQB3Uf5k35eV7n8q+INyZ0rJ
+dX6m3eXWg8WmuQ0OJDnKBtc=
 -----END CERTIFICATE-----
diff --git a/t/data/smime/keys/root at example.com.csr b/t/data/smime/keys/root at example.com.csr
index aeec080..a72677a 100644
--- a/t/data/smime/keys/root at example.com.csr
+++ b/t/data/smime/keys/root at example.com.csr
@@ -1,28 +1,9 @@
 -----BEGIN CERTIFICATE REQUEST-----
-MIIEvTCCAqUCAQAwYTELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUx
-DTALBgNVBAoTBFRlc3QxDTALBgNVBAMTBHJvb3QxHzAdBgkqhkiG9w0BCQEWEHJv
-b3RAZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCv
-tm8En4HMRKSx8QzBfNdBnYwQ39zyj7UTzvYDjQuz8L+43cz2TSaMQZrNCGA2aA1N
-XMO1KnHMOd4ZGWH7gGRcrGS4ZsItdyuOXIiCQVH42Is1T4BmV4wupMtLT8M4aexD
-xwAAsAzbAVDSLG6wpYOK3KoKhKRMgvN34amizCA2gGWGYitQSiyG0MXwUnvKWgSp
-7ShXvAfl46GnYAFpCevlLHVhU0rnADu26Tzi0rOZdtlfcKR/PNFGHo7qnaPAldWl
-RbpoZPuSXCMFLjZQqgpouiPNkilC+NbYFAnn8lLF8ZrImqpOvCafGQC/RjfUPfO4
-cqq1BaWUMSzcnLvjoR6CA8optdRkQ1FM8ncEu0OXo6XnVhNHTAI229lTINq7ptlU
-Ntx2FeDr11CccmZsfVkv3VHWfLYt8w75es8PY1k6a44/x3awERsS5bmQZjDS9roE
-3jMnWGGP7e0UuN8uWrXM/6NyHO9GCTccgcoT6UOfOPtpu2QhnuS0RL55YVJGz6WD
-f7XHkVsokhfSXkVLrZ/HU/scPOdWkqef5+NZGTZkjIx4ACr3ky15kJHhczEkyktN
-mx41YYo/DPjIwHZvAdgrweAK8fvHoCtIMNQwv0cr5bmNZOHUOoBxYB4CAtIa8iyy
-iNYh2ieDNmxRReNyOYMWZVxY5hSHApWIB/mzIFjZkQIDAQABoBcwFQYJKoZIhvcN
-AQkHMQgTBjEyMzQ1NjANBgkqhkiG9w0BAQUFAAOCAgEAbD64fjpgPl4mN18HfZQP
-7EyK7cJew7iI0dLmP4rQmVLqWjl+xlvvZdYTbO+Y2OZMU7ihWb2Axcxd/oR7CxZe
-UL43yCWX5eXI0c07/7LGWSNUMdIE7GvOVkRt6o91TLBTLtr/pBoRj368S7SgpajW
-7cykGZroK84+zd2oNjt2aj6j82M+X5limFpEmqzD+wMlSjyNHVVky1RjWQs8wqvr
-Dqmr638aodc7N0KCI/5dfpIYDiHvhUaI8JjvUivFmS6KkYXF7q5uO7poSqs6LL0A
-3FBEnKprFVWkUs19PLfkEoRC7XPoqP5hZfBFPe4eKJvI0hoVlpNtgCf8O9mwMSM0
-AQNtbzZnNWx3ol4eFw3c0MYUCcIMCShtma0oCGbQkgd6NRkg81iMfqw+OevDde1J
-WarhQUsnejt1EUdjpUse/PwoyPSW02Vh4fu+1xrCL8oafPmLy2mxLRJ/qolNvNnG
-Fg3/CM8c7CRnbqStvOD1LdRxIiu0cuPvmsYFQ861EN7cACuQpjL+7nVAffAqamvp
-I7f5F4Qa4//6nfkbxJZZPd2qZyLruhFSdV4kSN8azUOu3ZW+OE/j7TbEdIpzfXZL
-XneNneGvM1jhgcSiqWGG7U6XmU+XT8WbBTtLM+TNOWdFNR/ex6wXsEgatDqesin3
-mJ4yWERo/b9OHNKXP70P/ks=
+MIIBNTCB4AIBADB7MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh
+MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRMwEQYDVQQDEwpFbm9j
+aCBSb290MR8wHQYJKoZIhvcNAQkBFhByb290QGV4YW1wbGUuY29tMFwwDQYJKoZI
+hvcNAQEBBQADSwAwSAJBALJ3ubwJfRSOa29+M6mVIV3zPJFh8bxcHX7nVCXoy1+3
+GA4jJgBCCb2J2lwGy1IIQ/ZO/t34CoqVNY9KJRba5r8CAwEAAaAAMA0GCSqGSIb3
+DQEBBQUAA0EABuN/lyQxMY6DNb9XZ7H+UZLJrNYei1HRvfIXig7EvkSDEnArSwfZ
+uzAeLo3mnIp7WiDk3M7e19LQFkERs2xvHw==
 -----END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/root at example.com.key b/t/data/smime/keys/root at example.com.key
index 1c6e0af..7b24e4e 100644
--- a/t/data/smime/keys/root at example.com.key
+++ b/t/data/smime/keys/root at example.com.key
@@ -1,54 +1,12 @@
 -----BEGIN RSA PRIVATE KEY-----
 Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,5260BC3BD106CEDF
+DEK-Info: DES-EDE3-CBC,6356CE6012402B9B
 
-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
+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
 -----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/root at example.com.pem b/t/data/smime/keys/root at example.com.pem
new file mode 100644
index 0000000..9c1591b
--- /dev/null
+++ b/t/data/smime/keys/root at example.com.pem
@@ -0,0 +1,69 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:16
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:30:08 2010 GMT
+            Not After : Feb  8 16:30:08 2011 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=Enoch Root/emailAddress=root at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:b2:77:b9:bc:09:7d:14:8e:6b:6f:7e:33:a9:95:
+                    21:5d:f3:3c:91:61:f1:bc:5c:1d:7e:e7:54:25:e8:
+                    cb:5f:b7:18:0e:23:26:00:42:09:bd:89:da:5c:06:
+                    cb:52:08:43:f6:4e:fe:dd:f8:0a:8a:95:35:8f:4a:
+                    25:16:da:e6:bf
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                97:D7:80:52:CA:BB:E1:02:60:93:53:42:A2:14:12:E8:ED:1F:C0:7A
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+
+    Signature Algorithm: sha1WithRSAEncryption
+        95:5d:21:4f:cc:62:44:49:ad:f7:7f:75:ee:b0:e8:ec:0c:25:
+        39:72:4d:9d:98:86:99:81:81:02:a0:d1:2b:3e:2b:5c:01:14:
+        77:81:aa:be:a7:ff:9a:db:b4:b6:9c:b2:2e:d7:eb:4e:32:b8:
+        69:dd:7a:3f:3c:38:50:22:46:e5:21:1c:fc:d0:bf:04:79:5a:
+        5b:ac:53:77:33:be:27:63:6e:d7:47:e7:2f:75:15:84:db:9a:
+        08:c0:0a:b5:00:77:51:fe:64:df:97:95:ee:7f:2a:f8:83:72:
+        67:4a:c9:75:7e:a6:dd:e5:d6:83:c5:a6:b9:0d:0e:24:39:ca:
+        06:d7
+-----BEGIN CERTIFICATE-----
+MIICrTCCAhagAwIBAgIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjMwMDhaFw0xMTAyMDgx
+NjMwMDhaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMTCkVub2NoIFJv
+b3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
+AQEFAANLADBIAkEAsne5vAl9FI5rb34zqZUhXfM8kWHxvFwdfudUJejLX7cYDiMm
+AEIJvYnaXAbLUghD9k7+3fgKipU1j0olFtrmvwIDAQABo3sweTAJBgNVHRMEAjAA
+MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
+BgNVHQ4EFgQUl9eAUsq74QJgk1NCohQS6O0fwHowHwYDVR0jBBgwFoAUjRstvb0k
+6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlV0hT8xiREmt93917rDo
+7AwlOXJNnZiGmYGBAqDRKz4rXAEUd4Gqvqf/mtu0tpyyLtfrTjK4ad16Pzw4UCJG
+5SEc/NC/BHlaW6xTdzO+J2Nu10fnL3UVhNuaCMAKtQB3Uf5k35eV7n8q+INyZ0rJ
+dX6m3eXWg8WmuQ0OJDnKBtc=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,6356CE6012402B9B
+
+Lco5rf3/rHlShktH/o6NHF1mVH00k+pZ3bWodejMaHW1ofZXe9/yjzPM2jqqi+Dj
+xmzZ9R/MijO07vpxWHqdvhXeFf0TW67gW413M/bwiRd/rV0mUFz81nowFe9e15tm
+Itku1sePFvvL/UUxBGeYhplHAP6e76JqQcJTkBaG04KitH9GHtj1HFQR8P9/8h6d
+f0ZtU8wqnhkZvtzb72ZLwsw0YZ7R9YLIqCmOn1twW0CC77deACy+deJOC0N4CxW6
++jEGbJKMN5rOPsFiieDzZXAaTlGd6qXVWaxUPYH89yWedYoAZgbi6zxGGwNGbc/Q
+2Y7g+qHi3L30uJvgJEGihIM+9iAKUJSazyGYl9Xl2FwTpNFOMJAYFyNKNv5FHwdm
+deoslrbEXVtqurOQYr955cyqs2NN+JYLsz5nNnfBpGo=
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/sender.csr b/t/data/smime/keys/sender.csr
deleted file mode 100644
index fe7c9ed..0000000
--- a/t/data/smime/keys/sender.csr
+++ /dev/null
@@ -1,28 +0,0 @@
------BEGIN CERTIFICATE REQUEST-----
-MIIE1jCCAr4CAQAwgZAxCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRl
-MRYwFAYDVQQHEw1TdC1QZXRlcnNidXJnMQ0wCwYDVQQKEwRUZXN0MRQwEgYDVQQL
-EwtTZW5kZXIgdW5pdDEPMA0GA1UEAxMGU2ViZGVyMR4wHAYJKoZIhvcNAQkBFg9z
-ZW5kZXJAdGVzdC5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCX
-bJOzfVLNRcCJGqjHFDFKY4O9SftHAgTeU2Drtgp8ZDVWr3euNPc1vppZaljmQjXp
-j04FY67NXTsRVEKncNlJGnxDSrAlKOyv05RcG+w2CG0whdi7s+ods9UM8ir4o5fR
-3mOvlfHD89z3pOS5hejAmSOA1RjJ7iP1XIQ0HSZ/9CWxXJXiI6uYutmBKAqbAAOz
-WgDPtP8tBFpg2r7eZRJoNyf+GBuXaV+v/7Me6LFzQnJ6tyTFTm/mMSxWBHN0aun/
-Q0yQ6OYbSSmg63Bqm0STk9YtciZXjbbR+qGilu/KGrWZm1MwMHacjHDiWXMLVH5O
-ctJ7lIW//4d21h6gBLawFOFlKLS51j8RpQKOR4uLpHZlkoWCe5j1HY/x2ARwZANZ
-BroJVS0TiuRk3XAsbzNcJJdJGeGdPBuAtYqQ24Vdpa6X0jaq0A9Bgzq3k0ng9liD
-ot0a6Y9/bbCfjaAi5uytzWFi7SzIX7ZHK5EU1x7L6wRZdTQ1mAMX38KuaQ9Ok6eO
-AuMgTtiM9vbpw05KO0WQomBnabBXIrajc1l3ibwHExLM9aU0sAxTLnrlcJRFKqFj
-qNWpRaqHZ/Ru+TS/rV+zpzEt/cTwNV1sFEIX1IfpWYZTtq8ASk7Hlw2hqqOA3CfO
-uWyTZUULFA+HrM+ji5twOxBl9jRcnP1GKRs0Q/gc5wIDAQABoAAwDQYJKoZIhvcN
-AQEEBQADggIBABIeM0Jl08qSpBIEL/+MeiRqMJlexQ9awDBn5uB0KzFsISLX8rrP
-G7u1lCEhaFqPmdRDaeyYmN6naiFYNsbIazPHUXHVrbH+Bjax89wBvVw3Q2USdQ/D
-v9NHXVXNgdOxgz9AID7WYvy/xZLfEmbakfAMSLWbDyz6C+hCLVoPWpksO3CNKzsy
-0CS6hZVQS04EXKJeFQQEspr0hiKR0keb0sC3YcuNuYwyUFs/klFazdPIMk6esEkL
-udsgRPAoe2uX7yEx/abC54I62lsg1wBQ9PkWD9dpoW82jzbtvB/dVLqUWyYg57xq
-AU1IClcg8cQbmQaUbsjrAOrwuthR5xWH0Ybn1AwMxUn2z86b2FXYHZlruKlwFSlC
-v4Sz+Icgi9wQDKP4nZTvoc17XgUNjGIUN8xRovCdQ0pul5n6KKrNriRJcbNE9/NN
-b6dH5nUscF0xu4PEqVVt5QuzQioBVNS0jqedIhfyFwmnfdCMUEukMfrTt1DUnf8C
-89sYz66cfa9iNhM82Qgg8K9EdMyebOnxJOES6LZNGecB9n+co/WSATF/f9ih0ShN
-yjMLV05DEYBaitAxmfmZ/C92DZYagcwCUh6FMY9BfBWR0Ts780dl+iF1sn/uJzUd
-OAofD5wGxOIoC44McpnjDefdPMZIN5KFdogEFlKAhozDMC8hzpefeyy+
------END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/sender at example.com.crt b/t/data/smime/keys/sender at example.com.crt
index 0c00e95..657491f 100644
--- a/t/data/smime/keys/sender at example.com.crt
+++ b/t/data/smime/keys/sender at example.com.crt
@@ -1,38 +1,57 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:15
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:25:42 2010 GMT
+            Not After : Feb  8 16:25:42 2011 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=sender/emailAddress=sender at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:a8:38:41:90:1d:e7:cd:2b:cb:62:cf:ad:ff:70:
+                    f6:44:5d:f3:4b:7e:21:75:b6:5c:e1:7e:c2:27:3b:
+                    85:eb:72:9b:5a:94:0a:69:1d:83:ca:c5:91:b2:3f:
+                    04:72:61:e4:b8:eb:5b:ce:b5:10:77:d8:a7:df:8b:
+                    c9:5a:14:15:61
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                F3:34:E6:C3:51:78:92:9B:93:49:8E:4E:30:BC:18:88:24:69:34:09
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+
+    Signature Algorithm: sha1WithRSAEncryption
+        53:90:db:6d:d0:aa:0f:cb:32:94:2e:a5:bc:d1:0d:27:7c:85:
+        d8:08:6c:52:fe:b1:4d:18:94:c2:10:ff:4f:8f:71:2b:81:bf:
+        a3:aa:11:3e:6e:72:5f:4a:1a:38:a0:3f:5f:2c:89:d6:af:c5:
+        6c:ff:59:63:13:2d:fb:5a:2f:58:b2:77:3e:52:83:07:01:c6:
+        4e:cf:19:9c:9a:f3:17:ac:63:c2:00:f3:18:b9:27:a6:d5:1a:
+        14:11:d6:f0:db:9b:de:e4:ad:cd:29:01:fd:38:c1:21:9c:fa:
+        a2:0b:5e:6d:5d:14:54:ce:0a:3c:f7:af:0f:e6:7f:c7:39:00:
+        b3:33
 -----BEGIN CERTIFICATE-----
-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
-fVryqg==
+MIICqzCCAhSgAwIBAgIJAIpqzVG+lKAVMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjI1NDJaFw0xMTAyMDgx
+NjI1NDJaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnNlbmRlcjEh
+MB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKg4QZAd580ry2LPrf9w9kRd80t+IXW2XOF+wic7hetym1qUCmkd
+g8rFkbI/BHJh5LjrW861EHfYp9+LyVoUFWECAwEAAaN7MHkwCQYDVR0TBAIwADAs
+BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD
+VR0OBBYEFPM05sNReJKbk0mOTjC8GIgkaTQJMB8GA1UdIwQYMBaAFI0bLb29JOgZ
+Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAFOQ223Qqg/LMpQupbzRDSd8
+hdgIbFL+sU0YlMIQ/0+PcSuBv6OqET5ucl9KGjigP18sidavxWz/WWMTLftaL1iy
+dz5SgwcBxk7PGZya8xesY8IA8xi5J6bVGhQR1vDbm97krc0pAf04wSGc+qILXm1d
+FFTOCjz3rw/mf8c5ALMz
 -----END CERTIFICATE-----
diff --git a/t/data/smime/keys/sender at example.com.csr b/t/data/smime/keys/sender at example.com.csr
new file mode 100644
index 0000000..18fa799
--- /dev/null
+++ b/t/data/smime/keys/sender at example.com.csr
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBMzCB3gIBADB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEh
+MB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5k
+ZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3
+DQEBAQUAA0sAMEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4Xrcpta
+lAppHYPKxZGyPwRyYeS461vOtRB32Kffi8laFBVhAgMBAAGgADANBgkqhkiG9w0B
+AQUFAANBAFoi5bepEWsl0cQiO7k314NAuHenXaVrsWt3kPWfwgWn0aLp3aH86aZ5
+g4MYNjJzTqnkU1apyY8MV+BUZaXfnII=
+-----END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/sender at example.com.key b/t/data/smime/keys/sender at example.com.key
index a0a94ec..26ed850 100644
--- a/t/data/smime/keys/sender at example.com.key
+++ b/t/data/smime/keys/sender at example.com.key
@@ -1,54 +1,12 @@
 -----BEGIN RSA PRIVATE KEY-----
 Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,D9CF9FD45D1E5DB6
+DEK-Info: DES-EDE3-CBC,605762440BC8261C
 
-zVl1wndooucgnGLNMqlyKel4sHt682eFMAu6nUipUgOAgDherDUF8lrQk0clhWd1
-UiE7nvXb5QURmJoQ3VVxVYVNQbUj78JBiofLHhGIZxF7vt1raKj676zFP4QPNqQj
-mFu6BLYfKFXjf6HR9QN9TxuTse748jPh0bWuoW3hWS0S4hihaXqQXviw5qNeHnY8
-UxjQ2jC72mIOgN7lz/5E/86XZTa02nIOK/Y7wCikWAxPGwQYcx6eeKxFM8JS3Kde
-ACyctnsRq/GQkFn3q/E+GmCQSQLHUdH5dd6sZ90f25PvERpsP39QPg6hsat1MC44
-CBhl//7fY6uBVjsy2PDkhHp16M+ENBQJENHhkxj49peTKuWzqIzA+yKCELXQ/j5f
-hBnseSFkca0RppPrnV79PTtHuRdeQzeJOWK2tlkIPEaUqlEW5UaQ6VViuqDgop7E
-1cSrNhsnUwLTNlHFymLIcCodL7rmbR+h4YLZ54wWAo16aOZnFDiHCB6EufAWGR9C
-P9tpC+1e2a1FQkVGCPAptyPymF6Cbvzf4rJIPq/oDat9OZQZTvpTI+owIhImQIXH
-SgfXkbYrhOaIURLudvt1lmRHh5mfmNGC6djJ1DUEenlhQNO0RWYKcqMMVa308Bab
-bZVrhPl0l+f00LiWcbR6H2VHEpPIOnycZBxmrMzXMe9buI9QrzlsjV9RRYSA83rW
-frXI5S61ryimXxUYIMoCk0XCO+Y69W6cZ8f9yJpYocQzXaIF3uYBLb3h7NNFGk0t
-qGR26+0M2+Oqm7asfJ4hRRHIAaUtPP78qCPxo2xY7RtAQYidgIhX73dQQjN4vYfa
-BRvV9S8qAyQoNdSIqhPOI4tMFeP/eKlSOjEGkQnoScY2vhCZkdEWlyoZej1gpf+Y
-q2PhXQDSrVzmqskEUNWmhN5LUDVTnT+phxwyQfOthdWRIYg88lg0hKmgS6IV7fU3
-/fcDpPiklITVmtQ6FQnCQzFnnaIcexL3UPr+KVQkwxsGTShf+vgi/VnPzVVVK39x
-hgWeO+vb0YPuABylKsEXdURlyXehOIM+sFLNzz8s5XAYpE0QthmxHtUGHnDp+Zwx
-1jiwH+pXGV/7mZfRiON69o3fZxkrVYojd8rYHcQl2oRdT5vT9cm52VX/plksWLnX
-TW/d25nrx7fP/cW9FDdJoWrrouGC6B2/Gllfgtd0Tf3uXMV4M7ocVIv16U8W8UDW
-3Cb7cKfHHpbDFR2MJWkDhMz83KYs9oLKBvU9AXdCuOMnzqdpiMDiHOWeX3qf5v1S
-Fz3gxvmClp6igdpAjdMCVdb7vI5DqPa+ir5Sd5/dskkTU06BnzvDoysnYlTcp25C
-a9zgif0+jSPW6npGY7wvII28Tulgq/hJqlulU7IiI5CxIYUp2na7l4whwmKZMf50
-hj8RLuY/lru5mDNSdJyBPaAm2/4/DTor7M3IGDGQi6uusoxPVQ5KEJ9RQ2h5MuqU
-2gg1hdwsk1+Y6I9wbBLLrKktSiXsvekbC4HGzAUfSBS3o5Fy/SdXfOrkdglSpXzZ
-lZF7W+wyyZE0tT6Tc52AGq6vZPhkcXDScFdniT200TEYSADp+SDFV1B8sxdLIEyK
-lh6bhEuT859oeT9lzTr/Djr84Y3HMjdXchEU6RY1bHLovOTdZVE3PbfVzgf93VWy
-LqraYR/1zpxmCLCWTrOdjSn0MMHnTQSViKmfJyLpa5p2r6098ImZDDhowzbpU3nn
-UnARcvQIcS6Ci5GyZ9PwYbnaQWeM/lJO3vU53Guk2OjWeVuP8TQOi2eRPIqh3TOw
-df9EkPh4g7rddbEipesIIBDcS2QB5Em7WZ4i5XJ9/4XqdMmcT4ptkHOQVonzLFRQ
-ssZps5Hot0doydbclaJ8h6b6T7YgiUOivjH/cT4vjkmwOD4JT8ZXOlNLe6SoIMfX
-gAZD69K4ZnfLezZ15fO6l0WSFcbAG84G+fJiBcmwC1cc7fo8M7LJTxd2Inzqge1d
-JPeNPFPTayEY99uF+1P6li2EEexleJS0q+KHMoLCldL5DHOotzEoCejQCni1/YZc
-mBJb7DS+qcVU/Mzn/JKpPuZEZIJJGCu/oV3wu4DvCktfL+6JIwiBNWTTPyBY+M8e
-kEKK2fAYtLuAGVtGbbSh35BJiGBgxFUc7RC1fuGHFGoRzun4LcGossMRzYrU1jP5
-GQkbkJvBN4shCgdYCckLwSL+dZK4k3gmYqvzwZvIp+E1+5BgbwlBruAKMMB+A5id
-T1aOG/llQxbpeVk9sNuUZm10FZhTv6buaqM7uAQf4vSekLoTLlUmUkzzJRErJQiQ
-97IKe5vwj3XumyDTq5nGwXklIpRdX3BNN6O8edPQf5xxpeOhh/BZkB1pJdVcKbf7
-qGf2xoStC4KgZ7sJn7sn2ypJjrgNAk9JUtpxJOYF9Kylq1h3Z1EIC4yNGadcn8l7
-SEUsYO7Bw/Vaqq8JsQWfAEFwCmz37aaZpQmpfB2/OyKp+IbmU3og3aek2S21QfFy
-u7gWk6LEvfzVhh8OPmGlgCNJOopkxv5bwlFTQNFGI8YY0kQrWgdNdtlQcPFSl6kt
-fu12/s0T5k56SKvM5QdVfUa3c8dDPq6H4aHlBZnSK74KBS1tvdxWsh6lY6sZdt7c
-c/sBXxaS5AgtTuwjc8HOSOQhv/+hvUnRbDZROBzk6Hp/QWpX13yD9r91eCYysQ80
-10uc7dsH9mRpkaGQA3QPZlD6Ld/2p8h/Hsf/rKQsyOzswdZokmExLnRgVUhVoJ/d
-SoLVfBghHWAD/hvUKhBBPEwp8m9OKm8BF8jF5HsbTL12sBiUeOLpyrJTkRHuiZQn
-Pte4hEimyi835bCop4YbzCDVpbuh1RCgHDpkSSGwqpbiaVJbQCNwGPu9WLezDGl8
-IBwdJDmeg8obAhD+PVETY/wZOaAaE1zrDyoB6Stp5u6bWmO8r5xhrEkjJg2waNpb
-3R/vN6O7G49ZZn+f2D8AI50Fu6LWDuBXKA5IhAH0FQhex48f5Idklvazug+Wxaqp
-zJA1jo+DsmjWuUzcB1PLQr+2zi7PyuhjWKcJuPIz3JD2SYdi/wkyJWNotmiB9heL
-gBMMRpteVNXi0MfCwE5RELU6LUPG4MTon8F+k/I7Y78v4vTiX6/B3ouK9RHCa1d+
+MpUs66ILz2ePX4NKQ408LOAwvmpLLLnSwDX/Zmr/LG4SyZ7AnY6dY06XB6suev3m
+AS+xm/LM44lvUaDvPnl4gO8jnCw3D1yktcfeHc6XqcFx2U9AiUTawmoSTKwrT4P+
+tnpSrrBJY3WghElbckK3vbZboX9Eld+dJjGPf9YqMrkixObp0ul1zW7Wt+aSEV5B
+ngP3VmQinB1EjSUhGF/gsFzhJsutsX4Z1SE/U4K1A1OPl3Oz4e+9VLGgUN4ao84y
+pcNYdXO/BCax4Uk8l0r0DcMd73P9WZs9+bcSgmkqduWCXkNXDbfi4RTOEn19Ehpu
+MyKc3JrskRhNRN1vfMSRFUsrmppxBdPfkrGrTCJNBuL7zdbQh9k9XMaNzfw5Tt2R
+oCWay5shBGEEKXRLIEqzO+Jx1BWVlWwxUwDLr73ItHA=
 -----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/sender at example.com.pem b/t/data/smime/keys/sender at example.com.pem
index 67e6f54..1001c9c 100644
--- a/t/data/smime/keys/sender at example.com.pem
+++ b/t/data/smime/keys/sender at example.com.pem
@@ -1,92 +1,69 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:15
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=CA Owner/emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Feb  8 16:25:42 2010 GMT
+            Not After : Feb  8 16:25:42 2011 GMT
+        Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=sender/emailAddress=sender at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (512 bit)
+                Modulus (512 bit):
+                    00:a8:38:41:90:1d:e7:cd:2b:cb:62:cf:ad:ff:70:
+                    f6:44:5d:f3:4b:7e:21:75:b6:5c:e1:7e:c2:27:3b:
+                    85:eb:72:9b:5a:94:0a:69:1d:83:ca:c5:91:b2:3f:
+                    04:72:61:e4:b8:eb:5b:ce:b5:10:77:d8:a7:df:8b:
+                    c9:5a:14:15:61
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                F3:34:E6:C3:51:78:92:9B:93:49:8E:4E:30:BC:18:88:24:69:34:09
+            X509v3 Authority Key Identifier: 
+                keyid:8D:1B:2D:BD:BD:24:E8:19:62:AE:4C:C9:2A:58:90:08:1C:D1:05:2B
+
+    Signature Algorithm: sha1WithRSAEncryption
+        53:90:db:6d:d0:aa:0f:cb:32:94:2e:a5:bc:d1:0d:27:7c:85:
+        d8:08:6c:52:fe:b1:4d:18:94:c2:10:ff:4f:8f:71:2b:81:bf:
+        a3:aa:11:3e:6e:72:5f:4a:1a:38:a0:3f:5f:2c:89:d6:af:c5:
+        6c:ff:59:63:13:2d:fb:5a:2f:58:b2:77:3e:52:83:07:01:c6:
+        4e:cf:19:9c:9a:f3:17:ac:63:c2:00:f3:18:b9:27:a6:d5:1a:
+        14:11:d6:f0:db:9b:de:e4:ad:cd:29:01:fd:38:c1:21:9c:fa:
+        a2:0b:5e:6d:5d:14:54:ce:0a:3c:f7:af:0f:e6:7f:c7:39:00:
+        b3:33
 -----BEGIN CERTIFICATE-----
-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
-fVryqg==
+MIICqzCCAhSgAwIBAgIJAIpqzVG+lKAVMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjI1NDJaFw0xMTAyMDgx
+NjI1NDJaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnNlbmRlcjEh
+MB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKg4QZAd580ry2LPrf9w9kRd80t+IXW2XOF+wic7hetym1qUCmkd
+g8rFkbI/BHJh5LjrW861EHfYp9+LyVoUFWECAwEAAaN7MHkwCQYDVR0TBAIwADAs
+BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD
+VR0OBBYEFPM05sNReJKbk0mOTjC8GIgkaTQJMB8GA1UdIwQYMBaAFI0bLb29JOgZ
+Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAFOQ223Qqg/LMpQupbzRDSd8
+hdgIbFL+sU0YlMIQ/0+PcSuBv6OqET5ucl9KGjigP18sidavxWz/WWMTLftaL1iy
+dz5SgwcBxk7PGZya8xesY8IA8xi5J6bVGhQR1vDbm97krc0pAf04wSGc+qILXm1d
+FFTOCjz3rw/mf8c5ALMz
 -----END CERTIFICATE-----
 -----BEGIN RSA PRIVATE KEY-----
 Proc-Type: 4,ENCRYPTED
-DEK-Info: DES-EDE3-CBC,D9CF9FD45D1E5DB6
+DEK-Info: DES-EDE3-CBC,605762440BC8261C
 
-zVl1wndooucgnGLNMqlyKel4sHt682eFMAu6nUipUgOAgDherDUF8lrQk0clhWd1
-UiE7nvXb5QURmJoQ3VVxVYVNQbUj78JBiofLHhGIZxF7vt1raKj676zFP4QPNqQj
-mFu6BLYfKFXjf6HR9QN9TxuTse748jPh0bWuoW3hWS0S4hihaXqQXviw5qNeHnY8
-UxjQ2jC72mIOgN7lz/5E/86XZTa02nIOK/Y7wCikWAxPGwQYcx6eeKxFM8JS3Kde
-ACyctnsRq/GQkFn3q/E+GmCQSQLHUdH5dd6sZ90f25PvERpsP39QPg6hsat1MC44
-CBhl//7fY6uBVjsy2PDkhHp16M+ENBQJENHhkxj49peTKuWzqIzA+yKCELXQ/j5f
-hBnseSFkca0RppPrnV79PTtHuRdeQzeJOWK2tlkIPEaUqlEW5UaQ6VViuqDgop7E
-1cSrNhsnUwLTNlHFymLIcCodL7rmbR+h4YLZ54wWAo16aOZnFDiHCB6EufAWGR9C
-P9tpC+1e2a1FQkVGCPAptyPymF6Cbvzf4rJIPq/oDat9OZQZTvpTI+owIhImQIXH
-SgfXkbYrhOaIURLudvt1lmRHh5mfmNGC6djJ1DUEenlhQNO0RWYKcqMMVa308Bab
-bZVrhPl0l+f00LiWcbR6H2VHEpPIOnycZBxmrMzXMe9buI9QrzlsjV9RRYSA83rW
-frXI5S61ryimXxUYIMoCk0XCO+Y69W6cZ8f9yJpYocQzXaIF3uYBLb3h7NNFGk0t
-qGR26+0M2+Oqm7asfJ4hRRHIAaUtPP78qCPxo2xY7RtAQYidgIhX73dQQjN4vYfa
-BRvV9S8qAyQoNdSIqhPOI4tMFeP/eKlSOjEGkQnoScY2vhCZkdEWlyoZej1gpf+Y
-q2PhXQDSrVzmqskEUNWmhN5LUDVTnT+phxwyQfOthdWRIYg88lg0hKmgS6IV7fU3
-/fcDpPiklITVmtQ6FQnCQzFnnaIcexL3UPr+KVQkwxsGTShf+vgi/VnPzVVVK39x
-hgWeO+vb0YPuABylKsEXdURlyXehOIM+sFLNzz8s5XAYpE0QthmxHtUGHnDp+Zwx
-1jiwH+pXGV/7mZfRiON69o3fZxkrVYojd8rYHcQl2oRdT5vT9cm52VX/plksWLnX
-TW/d25nrx7fP/cW9FDdJoWrrouGC6B2/Gllfgtd0Tf3uXMV4M7ocVIv16U8W8UDW
-3Cb7cKfHHpbDFR2MJWkDhMz83KYs9oLKBvU9AXdCuOMnzqdpiMDiHOWeX3qf5v1S
-Fz3gxvmClp6igdpAjdMCVdb7vI5DqPa+ir5Sd5/dskkTU06BnzvDoysnYlTcp25C
-a9zgif0+jSPW6npGY7wvII28Tulgq/hJqlulU7IiI5CxIYUp2na7l4whwmKZMf50
-hj8RLuY/lru5mDNSdJyBPaAm2/4/DTor7M3IGDGQi6uusoxPVQ5KEJ9RQ2h5MuqU
-2gg1hdwsk1+Y6I9wbBLLrKktSiXsvekbC4HGzAUfSBS3o5Fy/SdXfOrkdglSpXzZ
-lZF7W+wyyZE0tT6Tc52AGq6vZPhkcXDScFdniT200TEYSADp+SDFV1B8sxdLIEyK
-lh6bhEuT859oeT9lzTr/Djr84Y3HMjdXchEU6RY1bHLovOTdZVE3PbfVzgf93VWy
-LqraYR/1zpxmCLCWTrOdjSn0MMHnTQSViKmfJyLpa5p2r6098ImZDDhowzbpU3nn
-UnARcvQIcS6Ci5GyZ9PwYbnaQWeM/lJO3vU53Guk2OjWeVuP8TQOi2eRPIqh3TOw
-df9EkPh4g7rddbEipesIIBDcS2QB5Em7WZ4i5XJ9/4XqdMmcT4ptkHOQVonzLFRQ
-ssZps5Hot0doydbclaJ8h6b6T7YgiUOivjH/cT4vjkmwOD4JT8ZXOlNLe6SoIMfX
-gAZD69K4ZnfLezZ15fO6l0WSFcbAG84G+fJiBcmwC1cc7fo8M7LJTxd2Inzqge1d
-JPeNPFPTayEY99uF+1P6li2EEexleJS0q+KHMoLCldL5DHOotzEoCejQCni1/YZc
-mBJb7DS+qcVU/Mzn/JKpPuZEZIJJGCu/oV3wu4DvCktfL+6JIwiBNWTTPyBY+M8e
-kEKK2fAYtLuAGVtGbbSh35BJiGBgxFUc7RC1fuGHFGoRzun4LcGossMRzYrU1jP5
-GQkbkJvBN4shCgdYCckLwSL+dZK4k3gmYqvzwZvIp+E1+5BgbwlBruAKMMB+A5id
-T1aOG/llQxbpeVk9sNuUZm10FZhTv6buaqM7uAQf4vSekLoTLlUmUkzzJRErJQiQ
-97IKe5vwj3XumyDTq5nGwXklIpRdX3BNN6O8edPQf5xxpeOhh/BZkB1pJdVcKbf7
-qGf2xoStC4KgZ7sJn7sn2ypJjrgNAk9JUtpxJOYF9Kylq1h3Z1EIC4yNGadcn8l7
-SEUsYO7Bw/Vaqq8JsQWfAEFwCmz37aaZpQmpfB2/OyKp+IbmU3og3aek2S21QfFy
-u7gWk6LEvfzVhh8OPmGlgCNJOopkxv5bwlFTQNFGI8YY0kQrWgdNdtlQcPFSl6kt
-fu12/s0T5k56SKvM5QdVfUa3c8dDPq6H4aHlBZnSK74KBS1tvdxWsh6lY6sZdt7c
-c/sBXxaS5AgtTuwjc8HOSOQhv/+hvUnRbDZROBzk6Hp/QWpX13yD9r91eCYysQ80
-10uc7dsH9mRpkaGQA3QPZlD6Ld/2p8h/Hsf/rKQsyOzswdZokmExLnRgVUhVoJ/d
-SoLVfBghHWAD/hvUKhBBPEwp8m9OKm8BF8jF5HsbTL12sBiUeOLpyrJTkRHuiZQn
-Pte4hEimyi835bCop4YbzCDVpbuh1RCgHDpkSSGwqpbiaVJbQCNwGPu9WLezDGl8
-IBwdJDmeg8obAhD+PVETY/wZOaAaE1zrDyoB6Stp5u6bWmO8r5xhrEkjJg2waNpb
-3R/vN6O7G49ZZn+f2D8AI50Fu6LWDuBXKA5IhAH0FQhex48f5Idklvazug+Wxaqp
-zJA1jo+DsmjWuUzcB1PLQr+2zi7PyuhjWKcJuPIz3JD2SYdi/wkyJWNotmiB9heL
-gBMMRpteVNXi0MfCwE5RELU6LUPG4MTon8F+k/I7Y78v4vTiX6/B3ouK9RHCa1d+
+MpUs66ILz2ePX4NKQ408LOAwvmpLLLnSwDX/Zmr/LG4SyZ7AnY6dY06XB6suev3m
+AS+xm/LM44lvUaDvPnl4gO8jnCw3D1yktcfeHc6XqcFx2U9AiUTawmoSTKwrT4P+
+tnpSrrBJY3WghElbckK3vbZboX9Eld+dJjGPf9YqMrkixObp0ul1zW7Wt+aSEV5B
+ngP3VmQinB1EjSUhGF/gsFzhJsutsX4Z1SE/U4K1A1OPl3Oz4e+9VLGgUN4ao84y
+pcNYdXO/BCax4Uk8l0r0DcMd73P9WZs9+bcSgmkqduWCXkNXDbfi4RTOEn19Ehpu
+MyKc3JrskRhNRN1vfMSRFUsrmppxBdPfkrGrTCJNBuL7zdbQh9k9XMaNzfw5Tt2R
+oCWay5shBGEEKXRLIEqzO+Jx1BWVlWwxUwDLr73ItHA=
 -----END RSA PRIVATE KEY-----

commit 103f738e24418bb71707ef4ee986860170373a3c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 15:04:53 2010 +0300

    add set of emails generated by thunderbird

diff --git a/t/data/smime/mails/1-signed.eml b/t/data/smime/mails/1-signed.eml
new file mode 100644
index 0000000..57c09b7
--- /dev/null
+++ b/t/data/smime/mails/1-signed.eml
@@ -0,0 +1,74 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709B50.6040609 at example.com>
+Date: Tue, 09 Feb 2010 02:16:32 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:1
+Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="------------ms010504020705000203070202"
+
+This is a cryptographically signed message in MIME format.
+
+--------------ms010504020705000203070202
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+This is a test email with detached signature.
+ID:1
+
+
+--------------ms010504020705000203070202
+Content-Type: application/pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+Content-Description: S/MIME Cryptographic Signature
+
+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
+SMKKtZ91LwT/wfT8cqCADfh0LaTIFAAAAAAAAA==
+--------------ms010504020705000203070202--
diff --git a/t/data/smime/mails/2-signed-attachment.eml b/t/data/smime/mails/2-signed-attachment.eml
new file mode 100644
index 0000000..5c8ab27
--- /dev/null
+++ b/t/data/smime/mails/2-signed-attachment.eml
@@ -0,0 +1,90 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709C48.4030908 at example.com>
+Date: Tue, 09 Feb 2010 02:20:40 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:2
+Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="------------ms090206030705090204050109"
+
+This is a cryptographically signed message in MIME format.
+
+--------------ms090206030705090204050109
+Content-Type: multipart/mixed;
+ boundary="------------090009090000030005040209"
+
+This is a multi-part message in MIME format.
+--------------090009090000030005040209
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+This is a test email with a text attachment.
+ID:2
+
+
+--------------090009090000030005040209
+Content-Type: text/plain; x-mac-type="0"; x-mac-creator="0";
+ name="text-attachment"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="text-attachment"
+
+VGhpcyBpcyBhIHRlc3QgYXR0YWNobWVudC4gIFRoZSBtYWdpYyB3b3JkIGlzOiAgemFuemli
+YXIuCg==
+--------------090009090000030005040209--
+
+--------------ms090206030705090204050109
+Content-Type: application/pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+Content-Description: S/MIME Cryptographic Signature
+
+MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIFXjCC
+AqswggIUoAMCAQICCQCKas1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MREwDwYDVQQDEwhDQSBPd25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5j
+b20wHhcNMTAwMjA4MTYyNTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEG
+A1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8w
+DQYDVQQDEwZzZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0G
+CSqGSIb3DQEBAQUAA0sAMEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4Xrcpta
+lAppHYPKxZGyPwRyYeS461vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJ
+YIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTz
+NObDUXiSm5NJjk4wvBiIJGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzAN
+BgkqhkiG9w0BAQUFAAOBgQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+j
+qhE+bnJfSho4oD9fLInWr8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoU
+Edbw25ve5K3NKQH9OMEhnPqiC15tXRRUzgo8968P5n/HOQCzMzCCAqswggIUoAMCAQICCQCK
+as1RvpSgFTANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T
+dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBP
+d25lcjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20wHhcNMTAwMjA4MTYy
+NTQyWhcNMTEwMjA4MTYyNTQyWjB5MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
+ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ8wDQYDVQQDEwZzZW5kZXIx
+ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sA
+MEgCQQCoOEGQHefNK8tiz63/cPZEXfNLfiF1tlzhfsInO4XrcptalAppHYPKxZGyPwRyYeS4
+61vOtRB32Kffi8laFBVhAgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9w
+ZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTzNObDUXiSm5NJjk4wvBiI
+JGk0CTAfBgNVHSMEGDAWgBSNGy29vSToGWKuTMkqWJAIHNEFKzANBgkqhkiG9w0BAQUFAAOB
+gQBTkNtt0KoPyzKULqW80Q0nfIXYCGxS/rFNGJTCEP9Pj3Ergb+jqhE+bnJfSho4oD9fLInW
+r8Vs/1ljEy37Wi9Ysnc+UoMHAcZOzxmcmvMXrGPCAPMYuSem1RoUEdbw25ve5K3NKQH9OMEh
+nPqiC15tXRRUzgo8968P5n/HOQCzMzGCAvAwggLsAgEBMIGKMH0xCzAJBgNVBAYTAkFVMRMw
+EQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx
+ETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNv
+bQIJAIpqzVG+lKAVMAkGBSsOAwIaBQCgggH8MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEw
+HAYJKoZIhvcNAQkFMQ8XDTEwMDIwODIzMjA0MFowIwYJKoZIhvcNAQkEMRYEFJXLFU9+rB4Q
+gPV6QSV6J7blwox4MF8GCSqGSIb3DQEJDzFSMFAwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMH
+MA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB
+KDCBmwYJKwYBBAGCNxAEMYGNMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93
+bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJAIpqzVG+lKAVMIGd
+BgsqhkiG9w0BCRACCzGBjaCBijB9MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0
+ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhDQSBPd25l
+cjEjMCEGCSqGSIb3DQEJARYUY2Eub3duZXJAZXhhbXBsZS5jb20CCQCKas1RvpSgFTANBgkq
+hkiG9w0BAQEFAARAai2FuYDJS0n8idViQ6y3pocwSKJRg0hrSP1K3GiVyh4an5y1lWuotK/q
+tziPXZ2qeGSB/mmBf7mwfjPYgGZkoQAAAAAAAA==
+--------------ms090206030705090204050109--
diff --git a/t/data/smime/mails/3-signed-binary.eml b/t/data/smime/mails/3-signed-binary.eml
new file mode 100644
index 0000000..ff3449d
--- /dev/null
+++ b/t/data/smime/mails/3-signed-binary.eml
@@ -0,0 +1,95 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709CC5.4010607 at example.com>
+Date: Tue, 09 Feb 2010 02:22:45 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:3
+Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha1; boundary="------------ms020101060809030506070801"
+
+This is a cryptographically signed message in MIME format.
+
+--------------ms020101060809030506070801
+Content-Type: multipart/mixed;
+ boundary="------------060502090104050607070406"
+
+This is a multi-part message in MIME format.
+--------------060502090104050607070406
+Content-Type: text/plain; charset=UTF-8; format=flowed
+Content-Transfer-Encoding: quoted-printable
+
+This is a test email with binary attachment and detached signature.
+ID:3
+
+
+--------------060502090104050607070406
+Content-Type: image/png;
+ name="favicon.png"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="favicon.png"
+
+iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABmJLR0QAAAAAAAD5Q7t/AAAB
+BElEQVR42u1WWw6DMAwz0+5FbzbvZuZk2cfUritpea77wVIRIBQ7dhsBdIQkM8AMMJImyW6d
+BXweyJ7UAMnUvQFGwHp2bizIJfUTUHZO8j/k1pt8lntvchbdH8ndtqyS+Gj3fyVPAtZAkm3N
+ffCyi/chBIQQ3iqs3cQ0TZCERzbhngDocOS4z94wXTCmu2V45LuQW8hsSWpaP8v9sy+2IRZj
+ZTP5ububbp8Az4ly5W6QqJ33YwKSkIYbZVy5uNMFsOJGLaLTBMRC8Yy7bmR/OD8TUB00DvkW
+AcPSB7FIPoji0AGQBtU4jt+Fh1R6Dcc6B2Znv4HTHTiAJkfXv+ILFy5c8PACgtsiPj7qOgAA
+AAAASUVORK5CYII=
+--------------060502090104050607070406--
+
+--------------ms020101060809030506070801
+Content-Type: application/pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+Content-Description: S/MIME Cryptographic Signature
+
+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
+53knAqbgZ9v3viuGCo0fj6RvFU4CHgAAAAAAAA==
+--------------ms020101060809030506070801--
diff --git a/t/data/smime/mails/4-encrypted-plain.eml b/t/data/smime/mails/4-encrypted-plain.eml
new file mode 100644
index 0000000..481a858
--- /dev/null
+++ b/t/data/smime/mails/4-encrypted-plain.eml
@@ -0,0 +1,32 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709D39.6090102 at example.com>
+Date: Tue, 09 Feb 2010 02:24:41 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:4
+Content-Type: application/pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP
+BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ
+AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEB4NUWl1nJB+cQVXPRmHEj+uxapSKRQ2PFeP+Eh
+VJHyPpsgf8APPxhS/6s1DBIWE9fwkghiM7JTgYZow42q/tdfMIHhAgEAMIGKMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz
+IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl
+eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEAFd/zqPwzjH8gKZoGUA/yY
+7aDfJzlAsg2tar47hM1xeSTgJ5JpluYy9V/43oK++Q+3HceI4P+aE91CjMrcbqvlMIAGCSqG
+SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQIsDsGzNXDhPmggASBiBdO/BdF/SrEjAeIi2is
+G71RuJ/lcnNlAltdk9lMJLoOxxTaa495lk8HuVD0xFYQueNS8AsACRjkOwgSf9Avh1elFRV5
+U3XZrmCOqbnDsWRTr2KEc8K9CXxqY6CwFizaoFlTftpji7W3ATU2+/QufIKYBS7Za3Zq1u7M
+HLbv4GLdEP1GVPDj2fAECP7azsN17fhCAAAAAAAAAAAAAA==
diff --git a/t/data/smime/mails/5-encrypted-attachment.eml b/t/data/smime/mails/5-encrypted-attachment.eml
new file mode 100644
index 0000000..b6fb9b4
--- /dev/null
+++ b/t/data/smime/mails/5-encrypted-attachment.eml
@@ -0,0 +1,42 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709D8E.1000001 at example.com>
+Date: Tue, 09 Feb 2010 02:26:06 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:5
+Content-Type: application/pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP
+BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ
+AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEALN6in6tg2C0yVmkb0XWJr6qRLrwrJLiqcoamd
+a3VAyQeHcqIB14UYuHiN6zZA2lABUI1DsjFlDiCEg8TSyJuAMIHhAgEAMIGKMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz
+IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl
+eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEBDqZbltMcqBRxIshfZ+jSa
+49l6RJAX6HVIBVZRu77rmlyVs2ft18qP0YVgwDPgD5Iok4c1Taemo3Rg7M2bHLwlMIAGCSqG
+SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQIWcv/5Jc8r/uggASCAqjneNECFsRSAPgwjW7G
+Hi+zLy+vPBLNfWgyuEAlKGeM347PdUciZNLhiz3D49lfHmVypGOxYTNU0kfeJVTW5bfwYHdS
+ZmPRx49tNJt08GR0eqbePKZtH0/0BW7LF1//lcNeJchsSdyRvkMB8zvTBhnNVhUSQWumrbda
+OUqvVpSdqx4SeqbyiyQKI+7AiZ2ChcZX9fA+YoiWT85NVtmgBNMMne0uHgmdBMaQHF4bTXvY
+/Mg8ew7Vg3TkVjg9QlaAe7JGrgyvSx/H4f+sn6mb68NaF5jGjiwen4a6ThRJO8lIJ30rTlb+
+WMqszV6OZK2ieWNn5BQlXOI/ew92UIuoyd5PtDkrLbkYio20KfCLpLbt1tvT8ZG0csgg9PO3
+iM3S0PWpjg4axknCYonphwSczsPcvUYZ+y4cIMdXvk5A3byMAQjLPYh0N6F9Q9tETc3HhDhA
+rSdVRot6JILv/tUs2ISPxJcMlLh8TcHZNchnRUcDg0wojULs49rONZIw8UGzbZi1H6IoGebk
+1HBsskw21pPDUjG7LpV0bKFKan0wxE5kJP1Xk5EN2Yw+2EHDE19QHs/ru2FdTjfbtcQFGlT1
+yiNUI7UwAPpCPyLoOpfvwYL2u4nSnbnKHCdjDHl4VAre8bngCMTzdRM91w/nydjpHfBbv+l5
+/EOKbYPC/SNG3IJZy70iExcXU+WydHdYCW0NhR7K1sCdwDsUpziQMvzlkJKclPC894Yljqnn
+83S8G2z3pTJ+SEAabdXY1GmYdfFeLGwnRmegzmWe0wCZKz0m4CabkDX4h0u8xu/C/5gbfU5Y
+JO1s3iVxzGa1TgyvD4aPvqh9pIQ/wSFt43HtZ5/ReG2ul7PTzOK92xC1c3xpMnXdtLEeRmNx
+2KU0Kfk1Si0lBJHf3R/JhwhHvwQI0IeGb5Ho55oAAAAAAAAAAAAA
diff --git a/t/data/smime/mails/6-encrypted-binary.eml b/t/data/smime/mails/6-encrypted-binary.eml
new file mode 100644
index 0000000..f4d5088
--- /dev/null
+++ b/t/data/smime/mails/6-encrypted-binary.eml
@@ -0,0 +1,48 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709DE8.9000101 at example.com>
+Date: Tue, 09 Feb 2010 02:27:36 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:6
+Content-Type: application/pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
diff --git a/t/data/smime/mails/7-signed-encrypted-plain.eml b/t/data/smime/mails/7-signed-encrypted-plain.eml
new file mode 100644
index 0000000..7dd981a
--- /dev/null
+++ b/t/data/smime/mails/7-signed-encrypted-plain.eml
@@ -0,0 +1,97 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709E37.3080003 at example.com>
+Date: Tue, 09 Feb 2010 02:28:55 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:7
+Content-Type: application/pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+AAAAAAA=
diff --git a/t/data/smime/mails/8-signed-encrypted-attachment.eml b/t/data/smime/mails/8-signed-encrypted-attachment.eml
new file mode 100644
index 0000000..1c53ad9
--- /dev/null
+++ b/t/data/smime/mails/8-signed-encrypted-attachment.eml
@@ -0,0 +1,107 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709E7D.8050407 at example.com>
+Date: Tue, 09 Feb 2010 02:30:05 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:8
+Content-Type: application/pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+AAAAAAAAAAAA
diff --git a/t/data/smime/mails/9-signed-encrypted-binary.eml b/t/data/smime/mails/9-signed-encrypted-binary.eml
new file mode 100644
index 0000000..eab9d5b
--- /dev/null
+++ b/t/data/smime/mails/9-signed-encrypted-binary.eml
@@ -0,0 +1,113 @@
+X-Mozilla-Status: 0801
+X-Mozilla-Status2: 00000000
+X-Mozilla-Keys:                                                                                 
+FCC: imap://sender@localhost/Sent
+X-Identity-Key: id1
+X-Account-Key: account1
+Message-ID: <4B709ECC.4020500 at example.com>
+Date: Tue, 09 Feb 2010 02:31:24 +0300
+From: tester <sender at example.com>
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0
+User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; ru; rv:1.9.1.7) Gecko/20100111 Thunderbird/3.0.1
+MIME-Version: 1.0
+To: root at example.com
+Subject: Test Email ID:9
+Content-Type: application/pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+qwQIeNe1tczOd/sAAAAAAAAAAAAA

commit d023ad027eb242c57cb504b9d462de59c6ff77cc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 15:07:10 2010 +0300

    add and update SMIME tests

diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index bfdf429..003d672 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -25,6 +25,7 @@ my $keyring = RT::Test->new_temp_dir(
 );
 
 RT->Config->Set( Crypt =>
+    Enable   => 1,
     Incoming => ['SMIME'],
     Outgoing => 'SMIME',
 );
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index 3d0bdc0..1d3ac23 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -122,8 +122,8 @@ END
     ok(eval {
         run3([
             $openssl, qw(smime -decrypt -passin pass:123456),
-            '-inkey', File::Spec->catfile($keys, 'recipient.key'),
-            '-recip', File::Spec->catfile($keys, 'recipient.crt')
+            '-inkey', File::Spec->catfile($keys, 'root at example.com.key'),
+            '-recip', File::Spec->catfile($keys, 'root at example.com.crt')
         ], \$mails[0], \$buf, \$err )
         }, 'can decrypt'
     );
diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
new file mode 100644
index 0000000..65c6f23
--- /dev/null
+++ b/t/mail/smime/realmail.t
@@ -0,0 +1,147 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test tests => 197;
+
+my $openssl = RT::Test->find_executable('openssl');
+plan skip_all => 'openssl executable is required.'
+    unless $openssl;
+
+use Digest::MD5 qw(md5_hex);
+
+my $mails = RT::Test::get_abs_relocatable_dir(
+    (File::Spec->updir()) x 2,
+    qw(data smime mails),
+);
+my $keyring = RT::Test->new_temp_dir(
+    crypt => smime => 'smime_keyring'
+);
+
+RT->Config->Set( Crypt =>
+    Enable   => 1,
+    Incoming => ['SMIME'],
+    Outgoing => 'SMIME',
+);
+RT->Config->Set( GnuPG => Enable => 0 );
+RT->Config->Set( SMIME =>
+    Enable => 1,
+    Passphrase => {
+        'root at example.com' => '123456',
+    },
+    OpenSSL => $openssl,
+    Keyring => $keyring,
+);
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
+
+RT::Test->import_smime_key('root at example.com');
+RT::Test->import_smime_key('sender at example.com');
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'we did log in';
+$m->get_ok( '/Admin/Queues/');
+$m->follow_link_ok( {text => 'General'} );
+$m->submit_form( form_number => 3,
+         fields      => { CorrespondAddress => 'root at example.com' } );
+
+diag "load Everyone group" if $ENV{'TEST_VERBOSE'};
+my $everyone;
+{
+    $everyone = RT::Group->new( $RT::SystemUser );
+    $everyone->LoadSystemInternalGroup('Everyone');
+    ok $everyone->id, "loaded 'everyone' group";
+}
+
+RT::Test->set_rights(
+    Principal => $everyone,
+    Right => ['CreateTicket'],
+);
+
+
+my $eid = 0;
+for my $usage (qw/signed encrypted signed&encrypted/) {
+    for my $attachment (qw/plain text-attachment binary-attachment/) {
+        ++$eid;
+        diag "Email $eid: $usage, $attachment email" if $ENV{TEST_VERBOSE};
+        eval { email_ok($eid, $usage, $attachment) };
+    }
+}
+
+sub email_ok {
+    my ($eid, $usage, $attachment) = @_;
+    diag "email_ok $eid: $usage, $attachment" if $ENV{'TEST_VERBOSE'};
+
+    my ($file) = glob("$mails/$eid-*");
+    my $mail = RT::Test->file_content($file);
+
+    my ($status, $id) = RT::Test->send_via_mailgate($mail);
+    is ($status >> 8, 0, "$eid: The mail gateway exited normally");
+    ok ($id, "$eid: got id of a newly created ticket - $id");
+
+    my $tick = RT::Ticket->new( $RT::SystemUser );
+    $tick->Load( $id );
+    ok ($tick->id, "$eid: loaded ticket #$id");
+
+    is ($tick->Subject,
+        "Test Email ID:$eid",
+        "$eid: Created the ticket"
+    );
+
+    my $txn = $tick->Transactions->First;
+    my ($msg, @attachments) = @{$txn->Attachments->ItemsArrayRef};
+
+    is( $msg->GetHeader('X-RT-Privacy'),
+        'SMIME',
+        "$eid: recorded incoming mail that is secured"
+    );
+
+    if ($usage =~ /encrypted/) {
+        is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+            'Success',
+            "$eid: recorded incoming mail that is encrypted"
+        );
+        like( $attachments[0]->Content, qr/ID:$eid/,
+                "$eid: incoming mail did NOT have original body"
+        );
+    }
+    else {
+        is( $msg->GetHeader('X-RT-Incoming-Encryption'),
+            'Not encrypted',
+            "$eid: recorded incoming mail that is not encrypted"
+        );
+        like( $msg->Content || $attachments[0]->Content, qr/ID:$eid/,
+            "$eid: got original content"
+        );
+    }
+
+    if ($usage =~ /signed/) {
+        is( $msg->GetHeader('X-RT-Incoming-Signature'),
+            'RT Test <sender at example.com>',
+            "$eid: recorded incoming mail that is signed"
+        );
+    }
+    else {
+        is( $msg->GetHeader('X-RT-Incoming-Signature'),
+            undef,
+            "$eid: recorded incoming mail that is not signed"
+        );
+    }
+
+    if ($attachment =~ /attachment/) {
+        my ($a) = grep $_->Filename, @attachments;
+        ok ($a && $a->Id, "$eid: found attachment with filename");
+
+        my $acontent = $a->Content;
+        if ($attachment =~ /binary/)
+        {
+            is(md5_hex($acontent), '1e35f1aa90c98ca2bab85c26ae3e1ba7', "$eid: The binary attachment's md5sum matches");
+        }
+        else
+        {
+            like($acontent, qr/zanzibar/, "$eid: The attachment isn't screwed up in the database.");
+        }
+    }
+
+    return 0;
+}
+
diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
new file mode 100644
index 0000000..08890f3
--- /dev/null
+++ b/t/web/smime/outgoing.t
@@ -0,0 +1,437 @@
+#!/usr/bin/perl -w
+use strict;
+use warnings;
+
+use RT::Test tests => 492;
+
+my $openssl = RT::Test->find_executable('openssl');
+plan skip_all => 'openssl executable is required.'
+    unless $openssl;
+
+use RT::Action::SendEmail;
+use File::Temp qw(tempdir);
+
+RT::Test->set_mail_catcher;
+
+use_ok('RT::Crypt::SMIME');
+
+my $keys = RT::Test::get_abs_relocatable_dir(
+    (File::Spec->updir()) x 2,
+    qw(data smime keys),
+);
+
+my $keyring = RT::Test->new_temp_dir(
+    crypt => smime => 'smime_keyring'
+);
+
+RT->Config->Set( Crypt =>
+    Enable   => 1,
+    Incoming => ['SMIME'],
+    Outgoing => 'SMIME',
+);
+RT->Config->Set( GnuPG => Enable => 0 );
+RT->Config->Set( SMIME =>
+    Enable => 1,
+    OutgoingMessagesFormat => 'RFC',
+    Passphrase => {
+        'sender at example.com' => '123456',
+    },
+    OpenSSL => $openssl,
+    Keyring => $keyring,
+);
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
+
+RT::Test->import_smime_key('sender at example.com');
+
+{
+    my $cf = RT::CustomField->new( $RT::SystemUser );
+    my ($ret, $msg) = $cf->Create(
+        Name       => 'SMIME Key',
+        LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
+        Type       => 'TextSingle',
+    );
+    ok($ret, "Custom Field created");
+
+    my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
+    $OCF->Create(
+        CustomField => $cf->id,
+        ObjectId    => 0,
+    );
+}
+
+my $user_email = 'root at example.com';
+{
+    my $user = RT::User->new( $RT::SystemUser );
+    $user->LoadByEmail( $user_email );
+    unless ( $user->id ) {
+        $user->Create(
+            Name         => $user_email,
+            EmailAddress => $user_email,
+            Privileged   => 1,
+        );
+    }
+    my ($status, $msg) = $user->AddCustomFieldValue(
+        Field => 'SMIME Key',
+        Value => RT::Test->file_content([$keys, $user_email .'.crt']),
+    );
+    ok $status, "set key for the user" or diag "error: $msg";
+}
+
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'Regression',
+    CorrespondAddress => 'sender at example.com',
+    CommentAddress    => 'sender at example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+RT::Test->set_rights(
+    Principal => 'Everyone',
+    Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+my ($baseurl, $m) = RT::Test->started_ok;
+ok $m->login, 'logged in';
+
+my @variants = (
+    {},
+    { Sign => 1 },
+    { Encrypt => 1 },
+    { Sign => 1, Encrypt => 1 },
+);
+
+# collect emails
+my %mail = (
+    plain            => [],
+    signed           => [],
+    encrypted        => [],
+    signed_encrypted => [],
+);
+
+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 );
+        $m->goto_create_ticket( $queue );
+        $m->form_name('TicketCreate');
+        if ( $variant->{'Encrypt'} ) {
+            ok $m->value('Encrypt', 2), "encrypt tick box is checked";
+        } else {
+            ok !$m->value('Encrypt', 2), "encrypt tick box is unchecked";
+        }
+        if ( $variant->{'Sign'} ) {
+            ok $m->value('Sign', 2), "sign tick box is checked";
+        } else {
+            ok !$m->value('Sign', 2), "sign tick box is unchecked";
+        }
+    }
+
+    # to avoid encryption/signing during create
+    set_queue_crypt_options();
+
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    my ($id) = $ticket->Create(
+        Subject   => 'test',
+        Queue     => $queue->id,
+        Requestor => $user_email,
+    );
+    ok $id, 'ticket created';
+
+    foreach my $variant ( @variants ) {
+        set_queue_crypt_options( %$variant );
+        $m->goto_ticket( $id );
+        $m->follow_link_ok({text => 'Reply'}, '-> reply');
+        $m->form_number(3);
+        if ( $variant->{'Encrypt'} ) {
+            ok $m->value('Encrypt', 2), "encrypt tick box is checked";
+        } else {
+            ok !$m->value('Encrypt', 2), "encrypt tick box is unchecked";
+        }
+        if ( $variant->{'Sign'} ) {
+            ok $m->value('Sign', 2), "sign tick box is checked";
+        } else {
+            ok !$m->value('Sign', 2), "sign tick box is unchecked";
+        }
+    }
+}
+
+# create a ticket for each combination
+foreach my $queue_set ( @variants ) {
+    set_queue_crypt_options( %$queue_set );
+    foreach my $ticket_set ( @variants ) {
+        create_a_ticket( %$ticket_set );
+    }
+}
+
+my $tid;
+{
+    my $ticket = RT::Ticket->new( $RT::SystemUser );
+    ($tid) = $ticket->Create(
+        Subject   => 'test',
+        Queue     => $queue->id,
+        Requestor => $user_email,
+    );
+    ok $tid, 'ticket created';
+}
+
+# again for each combination add a reply message
+foreach my $queue_set ( @variants ) {
+    set_queue_crypt_options( %$queue_set );
+    foreach my $ticket_set ( @variants ) {
+        update_ticket( $tid, %$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 recieving emails
+# ------------------------------------------------------------------------------
+
+unlink $_ foreach glob( $keyring ."/*" );
+RT::Test->import_smime_key('sender at example.com', 'public');
+RT::Test->import_smime_key($user_email);
+
+$queue = RT::Test->load_or_create_queue(
+    Name              => 'Regression',
+    CorrespondAddress => $user_email,
+    CommentAddress    => $user_email,
+);
+ok $queue && $queue->id, 'changed props of the queue';
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'plain'} } ) {
+    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};
+
+    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";
+
+    like $msg->Content, qr/Some content/, "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed'} } ) {
+    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};
+
+    is $msg->GetHeader('X-RT-Privacy'), 'SMIME',
+        "RT's outgoing mail has crypto" or exit 0;
+    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";
+
+    like $attachments[0]->Content, qr/Some content/,
+        "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'encrypted'} } ) {
+    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};
+
+    is $msg->GetHeader('X-RT-Privacy'), 'SMIME',
+        "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";
+
+    like $attachments[0]->Content, qr/Some content/,
+        "RT's mail includes copy of ticket text";
+}
+
+foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed_encrypted'} } ) {
+    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};
+
+    is $msg->GetHeader('X-RT-Privacy'), 'SMIME',
+        "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";
+
+    like $attachments[0]->Content, qr/Some content/,
+        "RT's mail includes copy of ticket text";
+}
+
+sub create_a_ticket {
+    my %args = (@_);
+
+    RT::Test->clean_caught_mails;
+
+    describe_options('creating a ticket: ', %args);
+
+    $m->goto_create_ticket( $queue );
+    $m->form_name('TicketCreate');
+    $m->field( Subject    => 'test' );
+    $m->field( Requestors => $user_email );
+    $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";
+
+    unlike($m->content, qr/unable to sign outgoing email messages/);
+
+    $m->get_ok('/'); # ensure that the mail has been processed
+
+    my @mail = RT::Test->fetch_caught_mails;
+    check_text_emails( \%args, @mail );
+}
+
+sub update_ticket {
+    my $tid = shift;
+    my %args = (@_);
+
+    RT::Test->clean_caught_mails;
+
+    describe_options('updating ticket #'. $tid .': ', %args);
+
+    ok $m->goto_ticket( $tid ), "UI -> ticket #$tid";
+    $m->follow_link_ok( { text => 'Reply' }, 'ticket -> reply' );
+    $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_like(qr/Message recorded/, 'Message recorded');# or diag $m->content;
+
+    $m->get_ok('/'); # ensure that the mail has been processed
+
+    my @mail = RT::Test->fetch_caught_mails;
+    check_text_emails( \%args, @mail );
+}
+
+sub check_text_emails {
+    my %args = %{ shift @_ };
+    my @mail = @_;
+
+    describe_options('testing that we got at least one mail: ', %args);
+
+    ok scalar @mail, "got some mail";
+    for my $mail (@mail) {
+        if ( $args{'Encrypt'} ) {
+            unlike $mail, qr/Some content/, "outgoing email was encrypted";
+        } else {
+            like $mail, qr/Some content/, "outgoing email was not encrypted";
+        }
+
+        if ( $args{'Encrypt'} ) {
+            like $mail, qr/application\/x-pkcs7-mime/, 'outgoing email was processed';
+        } elsif ( $args{'Sign'} ) {
+            like $mail, qr/x-pkcs7-signature/, 'outgoing email was processed';
+        } else {
+            unlike $mail, qr/smime/, 'outgoing email was not processed';
+        }
+    }
+    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 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 X-RT-Original-Encoding RT-Originator RT-Ticket X-RT-Loop-Prevention) ) {
+        $mail =~ s/^$field:.*?\n(?! |\t)//gmsi;
+    }
+    return $mail;
+}
+
+sub set_queue_crypt_options {
+    my %args = @_;
+
+    describe_options('setting queue options: ', %args);
+
+    $m->get_ok("/Admin/Queues/Modify.html?id=". $queue->id);
+    $m->form_with_fields('Sign', 'Encrypt');
+    foreach my $opt ('Sign', 'Encrypt') {
+        if ( $args{$opt} ) {
+            $m->tick($opt => 1);
+        } else {
+            $m->untick($opt => 1);
+        }
+    }
+    $m->submit;
+}
+
+sub describe_options {
+    return unless $ENV{'TEST_VERBOSE'};
+
+    my $msg = shift;
+    my %args = @_;
+    if ( $args{'Encrypt'} && $args{'Sign'} ) {
+        $msg .= 'encrypt and sign';
+    }
+    elsif ( $args{'Sign'} ) {
+        $msg .= 'sign';
+    }
+    elsif ( $args{'Encrypt'} ) {
+        $msg .= 'encrypt';
+    }
+    else {
+        $msg .= 'no encrypt and no sign';
+    }
+    diag $msg;
+}
+

commit de47989a3be34e847937f9fe4ff6215271981a14
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 15:09:13 2010 +0300

    move IO::Handler::CRLF

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 347b6cd..0d7a215 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -321,3 +321,16 @@ sub GetKeysInfo {
 }
 
 1;
+
+# helper package to avoid using temp file
+package IO::Handle::CRLF;
+
+use base qw(IO::Handle);
+
+sub print {
+    my ($self, @args) = (@_);
+    s/\r*\n/\x0D\x0A/g foreach @args;
+    return $self->SUPER::print( @args );
+}
+
+1;
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index ac57894..44a2531 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2396,16 +2396,3 @@ if ($@ && $@ !~ qr{^Can't locate RT/Crypt/GnuPG_Local.pm}) {
 };
 
 1;
-
-# helper package to avoid using temp file
-package IO::Handle::CRLF;
-
-use base qw(IO::Handle);
-
-sub print {
-    my ($self, @args) = (@_);
-    s/\r*\n/\x0D\x0A/g foreach @args;
-    return $self->SUPER::print( @args );
-}
-
-1;

commit 968cb5569ee58b02fccf5f424f41d56b3bde9f0c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 15:54:14 2010 +0300

    refactor SignEncrypt to allow independant actions

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index d68f73b..612f938 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -32,88 +32,88 @@ sub SignEncrypt {
 
     my $entity = $args{'Entity'};
 
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} );
-    }
 
     my %res = (exit_code => 0, status => '');
 
-    my @addresses =
-        map $_->address,
-        map Email::Address->parse($_),
-        grep defined && length,
-        map $entity->head->get($_),
-        qw(To Cc Bcc);
-
     my @keys;
-    foreach my $address ( @addresses ) {
-        $RT::Logger->debug( "Considering encrypting message to " . $address );
-
-        my %key_info = $self->GetKeysInfo( Key => $address );
-        unless ( %key_info ) {
-            $res{'exit_code'} = 1;
-            my $reason = 'Key not found';
-            $res{'status'} .=
-                "Operation: RecipientsCheck\nStatus: ERROR\n"
-                ."Message: Recipient '$address' is unusable, the reason is '$reason'\n"
-                ."Recipient: $address\n"
-                ."Reason: $reason\n\n",
-            ;
-            next;
-        }
+    if ( $args{'Encrypt'} ) {
+        my @addresses =
+            map $_->address,
+            map Email::Address->parse($_),
+            grep defined && length,
+            map $entity->head->get($_),
+            qw(To Cc Bcc);
+
+        foreach my $address ( @addresses ) {
+            $RT::Logger->debug( "Considering encrypting message to " . $address );
+
+            my %key_info = $self->GetKeysInfo( Key => $address );
+            unless ( %key_info ) {
+                $res{'exit_code'} = 1;
+                my $reason = 'Key not found';
+                $res{'status'} .=
+                    "Operation: RecipientsCheck\nStatus: ERROR\n"
+                    ."Message: Recipient '$address' is unusable, the reason is '$reason'\n"
+                    ."Recipient: $address\n"
+                    ."Reason: $reason\n\n",
+                ;
+                next;
+            }
 
-        unless ( $key_info{'info'}[0]{'Expire'} ) {
-            # we continue here as it's most probably a problem with the key,
-            # so later during encryption we'll get verbose errors
-            $RT::Logger->error(
-                "Trying to send an encrypted message to ". $address
-                .", but we couldn't get expiration date of the key."
-            );
-        }
-        elsif ( $key_info{'info'}[0]{'Expire'}->Diff( time ) < 0 ) {
-            $res{'exit_code'} = 1;
-            my $reason = 'Key expired';
-            $res{'status'} .=
-                "Operation: RecipientsCheck\nStatus: ERROR\n"
-                ."Message: Recipient '$address' is unusable, the reason is '$reason'\n"
-                ."Recipient: $address\n"
-                ."Reason: $reason\n\n",
-            ;
-            next;
+            unless ( $key_info{'info'}[0]{'Expire'} ) {
+                # we continue here as it's most probably a problem with the key,
+                # so later during encryption we'll get verbose errors
+                $RT::Logger->error(
+                    "Trying to send an encrypted message to ". $address
+                    .", but we couldn't get expiration date of the key."
+                );
+            }
+            elsif ( $key_info{'info'}[0]{'Expire'}->Diff( time ) < 0 ) {
+                $res{'exit_code'} = 1;
+                my $reason = 'Key expired';
+                $res{'status'} .=
+                    "Operation: RecipientsCheck\nStatus: ERROR\n"
+                    ."Message: Recipient '$address' is unusable, the reason is '$reason'\n"
+                    ."Recipient: $address\n"
+                    ."Reason: $reason\n\n",
+                ;
+                next;
+            }
+            push @keys, $key_info{'info'}[0]{'Content'};
         }
-        push @keys, $key_info{'info'}[0]{'Content'};
     }
     return %res if $res{'exit_code'};
 
-    foreach my $key ( @keys ) {
-        my $key_file = File::Temp->new;
-        print $key_file $key;
-        $key = $key_file;
-    }
-
     my $opts = RT->Config->Get('SMIME');
 
+    my @command;
+    if ( $args{'Sign'} ) {
+        # XXX: implement support for -nodetach
+        $args{'Passphrase'} = $self->GetPassphrase( Address => $args{'Signer'} )
+            unless defined $args{'Passphrase'};
+
+        push @command, join ' ', shell_quote(
+            $self->OpenSSLPath, qw(smime -sign -passin env:SMIME_PASS),
+            -signer => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
+            -inkey  => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
+        );
+    }
+    if ( $args{'Encrypt'} ) {
+        foreach my $key ( @keys ) {
+            my $key_file = File::Temp->new;
+            print $key_file $key;
+            $key = $key_file;
+        }
+        push @command, join ' ', shell_quote(
+            $self->OpenSSLPath, qw(smime -encrypt -des3),
+            map { $_->filename } @keys
+        );
+    }
+
     $entity->make_multipart('mixed', Force => 1);
     my ($buf, $err) = ('', '');
     {
-        local $ENV{'SMIME_PASS'};
-        
-        my @command;
-        if ( $args{'Sign'} ) {
-            $ENV{'SMIME_PASS'} = $args{'Passphrase'};
-            push @command, join ' ', shell_quote(
-                $self->OpenSSLPath, qw(smime -sign -passin env:SMIME_PASS),
-                -signer => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
-                -inkey  => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
-            );
-        }
-        if ( $args{'Encrypt'} ) {
-            push @command, join ' ', shell_quote(
-                $self->OpenSSLPath, qw(smime -encrypt -des3),
-                map { $_->filename } @keys
-            );
-        }
-
+        local $ENV{'SMIME_PASS'} = $args{'Passphrase'};
         local $SIG{'CHLD'} = 'DEFAULT';
         safe_run_child { run3(
             join( ' | ', @command ),

commit 41db365eff4d775895c74a9cff31f5698b780cdd
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 17:06:42 2010 +0300

    both signature formats can be handled in the same way

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 612f938..a7a212a 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -184,6 +184,12 @@ sub VerifyDecrypt {
     return %res;
 }
 
+sub VerifyRFC3156 {
+    my $self = shift;
+    my %args = ( Top => undef, Data => undef, Signature => undef, @_);
+    return $self->VerifyRFC3851( %args, Data => $args{'Top'} );
+}
+
 sub VerifyRFC3851 {
     my $self = shift;
     my %args = (Data => undef, Queue => undef, @_ );

commit 6328429943924f663aed323b73d62bcd0cc16ddb
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 17:07:46 2010 +0300

    line ends should be \x0D\x0A

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index a7a212a..61e3e4b 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -195,6 +195,7 @@ sub VerifyRFC3851 {
     my %args = (Data => undef, Queue => undef, @_ );
 
     my $msg = $args{'Data'}->as_string;
+    $msg =~ s/\r*\n/\x0D\x0A/g;
 
     my %res;
     my $buf;

commit 850a9ebcfc950f8eec394493ab4ff6cf0fac0af0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 9 17:11:00 2010 +0300

    clients can use application/x-pkcs7-signature or application/pkcs7-signature

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 61e3e4b..f9f49e8 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -398,7 +398,10 @@ sub CheckIfProtected {
             return ();
         }
 
-        unless ( $protocol eq 'application/pgp-signature' ) {
+        unless (
+            $protocol eq 'application/x-pkcs7-signature'
+            || $protocol eq 'application/pkcs7-signature'
+        ) {
             $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" );
             return ();
         }

commit 3705319721bf50cc507e49ce6f5277019819bba9
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 9f367a55155e3081dd3514fc641b9d948a74cab8
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 af324544c80c36adfd4a0e7e0afa8101b66604cc
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 f1caa82ef7bf78e833f521916f92f854b95a7e40
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 a4ef3e8a549fbd849e5de64982e6983720f75dea
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 bca60198b1d43c80339f2e0a12f5af45c4383bf1
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 86bedd42c7884f368cad96ac8e1ea9c3b14bca40
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;

commit f22b90d1e4bed741f0aa0f3866f73ded3d9a8e7f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 10 13:17:15 2010 +0300

    fix cofiguration and emails in tests

diff --git a/t/web/gnupg-outgoing.t b/t/web/gnupg-outgoing.t
index a956678..444e209 100644
--- a/t/web/gnupg-outgoing.t
+++ b/t/web/gnupg-outgoing.t
@@ -31,7 +31,7 @@ RT->Config->Set( GnuPGOptions =>
 RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::GnuPG' );
 
 RT::Test->import_gnupg_key('rt-recipient at example.com');
-my $user_email = 'root at example.com';
+my $user_email = 'rt-test at example.com';
 RT::Test->import_gnupg_key($user_email, 'public');
 
 my $queue = RT::Test->load_or_create_queue(
diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index 08890f3..b72eab2 100644
--- a/t/web/smime/outgoing.t
+++ b/t/web/smime/outgoing.t
@@ -35,6 +35,7 @@ RT->Config->Set( SMIME =>
     OutgoingMessagesFormat => 'RFC',
     Passphrase => {
         'sender at example.com' => '123456',
+        'root at example.com' => '123456',
     },
     OpenSSL => $openssl,
     Keyring => $keyring,
@@ -184,7 +185,7 @@ foreach my $queue_set ( @variants ) {
 
 # ------------------------------------------------------------------------------
 # now delete all keys from the keyring and put back secret/pub pair for rt-test@
-# and only public key for rt-recipient@ so we can verify signatures and decrypt
+# and only public key for sender@ so we can verify signatures and decrypt
 # like we are on another side recieving emails
 # ------------------------------------------------------------------------------
 
@@ -237,7 +238,7 @@ foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed'} } ) {
     is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Not encrypted',
         "RT's outgoing mail looks not encrypted";
     like $msg->GetHeader('X-RT-Incoming-Signature'),
-        qr/<rt-recipient\@example.com>/,
+        qr/<sender\@example\.com>/,
         "RT's outgoing mail looks signed";
 
     like $attachments[0]->Content, qr/Some content/,
@@ -284,7 +285,7 @@ foreach my $mail ( map cleanup_headers($_), @{ $mail{'signed_encrypted'} } ) {
     is $msg->GetHeader('X-RT-Incoming-Encryption'), 'Success',
         "RT's outgoing mail looks encrypted";
     like $msg->GetHeader('X-RT-Incoming-Signature'),
-        qr/<rt-recipient\@example.com>/,
+        qr/<sender\@example.com>/,
         "RT's outgoing mail looks signed";
 
     like $attachments[0]->Content, qr/Some content/,

commit daf278b6cc67746ea4a1c50b0485c5cfd3777351
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:01:37 2010 +0300

    we can not test with old files, new set of keys

diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index 003d672..ecf449b 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 42;
+use RT::Test tests => 19;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
@@ -42,7 +42,21 @@ RT->Config->Set( SMIME =>
 
 RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
 
-my $mails = RT::Test::find_relocatable_path( 'data', 'smime', 'mails' );
+{
+    my $cf = RT::CustomField->new( $RT::SystemUser );
+    my ($ret, $msg) = $cf->Create(
+        Name       => 'SMIME Key',
+        LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
+        Type       => 'TextSingle',
+    );
+    ok($ret, "Custom Field created");
+
+    my $OCF = RT::ObjectCustomField->new( $RT::SystemUser );
+    $OCF->Create(
+        CustomField => $cf->id,
+        ObjectId    => 0,
+    );
+}
 
 my ($url, $m) = RT::Test->started_ok;
 ok $m->login, "logged in";
@@ -123,75 +137,6 @@ RT::Test->close_mailgate_ok($mail);
 }
 
 {
-    my $message = RT::Test->file_content([$mails, 'simple-txt-enc.eml']);
-    my ($status, $tid) = RT::Test->send_via_mailgate( $message );
-    ok !$status, "executed gate";
-
-    my $tick = RT::Ticket->new( $RT::SystemUser );
-    $tick->Load( $tid );
-    ok( $tick->Id, "found ticket " . $tick->Id );
-    is( $tick->Subject, 'test', 'Created the ticket' );
-
-    my $txn = $tick->Transactions->First;
-    my ($msg, $attach, $orig) = @{$txn->Attachments->ItemsArrayRef};
-    is( $msg->GetHeader('X-RT-Incoming-Encryption'),
-        'Success',
-        'recorded incoming mail that is encrypted'
-    );
-    is( $msg->GetHeader('X-RT-Privacy'),
-        'SMIME',
-        'recorded incoming mail that is encrypted'
-    );
-    ok( $msg->GetHeader('User-Agent'), 'header is there');
-    like( $attach->Content, qr'test');
-}
-
-{
-    my $message = RT::Test->file_content([$mails, 'with-text-attachment.eml']);
-    my ($status, $tid) = RT::Test->send_via_mailgate( $message );
-    ok !$status, "executed gate";
-
-    my $tick = RT::Ticket->new( $RT::SystemUser );
-    $tick->Load( $tid );
-    ok( $tick->Id, "found ticket " . $tick->Id );
-    is( $tick->Subject, 'test', 'Created the ticket' );
-    my $txn = $tick->Transactions->First;
-    my @attachments = @{ $txn->Attachments->ItemsArrayRef };
-    is( @attachments, 4, '4 attachments: top, two parts and orig' );
-
-    is( $attachments[0]->GetHeader('X-RT-Incoming-Encryption'),
-        'Success',
-        'recorded incoming mail that is encrypted'
-    );
-    ok( $attachments[0]->GetHeader('User-Agent'), 'header is there' );
-    like( $attachments[1]->Content, qr'test' );
-    like( $attachments[2]->Content, qr'text attachment' );
-    is( $attachments[2]->Filename, 'attachment.txt' );
-}
-
-{
-    my $message = RT::Test->file_content([$mails, 'with-bin-attachment.eml']);
-    my ($status, $tid) = RT::Test->send_via_mailgate( $message );
-    ok !$status, "executed gate";
-
-    my $tick = RT::Ticket->new( $RT::SystemUser );
-    $tick->Load( $tid );
-    ok( $tick->Id, "found ticket " . $tick->Id );
-    is( $tick->Subject, 'test', 'Created the ticket' );
-    my $txn = $tick->Transactions->First;
-    my @attachments = @{ $txn->Attachments->ItemsArrayRef };
-    is( @attachments, 4, '4 attachments: top, two parts and orig' );
-
-    is( $attachments[0]->GetHeader('X-RT-Incoming-Encryption'),
-        'Success',
-        'recorded incoming mail that is encrypted'
-    );
-    ok( $attachments[0]->GetHeader('User-Agent'), 'header is there');
-    like( $attachments[1]->Content, qr'test');
-    is( $attachments[2]->Filename, 'attachment.bin' );
-}
-
-{
     my $buf = '';
 
     run3(

commit a31066d664b23950abebf6b45216d7e1f5a49c10
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:03:09 2010 +0300

    remove old files,we don't have keys for them

diff --git a/t/data/smime/mails/simple-txt-enc.eml b/t/data/smime/mails/simple-txt-enc.eml
deleted file mode 100644
index df38c4a..0000000
--- a/t/data/smime/mails/simple-txt-enc.eml
+++ /dev/null
@@ -1,36 +0,0 @@
-Date: Fri, 22 Dec 2006 05:01:04 +0300
-From: root at localhost
-X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
-User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
-MIME-Version: 1.0
-To: sender at test.com
-Subject: test
-Content-Type: application/x-pkcs7-mime; name="smime.p7m"
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment; filename="smime.p7m"
-Content-Description: S/MIME Encrypted Message
-
-MIAGCSqGSIb3DQEHA6CAMIACAQAxggQ8MIIBjgIBADB2MGIxCzAJBgNVBAYTAlpBMSUwIwYD
-VQQKExxUaGF3dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVy
-c29uYWwgRnJlZW1haWwgSXNzdWluZyBDQQIQBgzQJigV55Om1dc1u4CuYzANBgkqhkiG9w0B
-AQEFAASCAQBEI9JKBQgIqLcRTiYCwDHR1dVTnlOoAAVHoM10dHxzszYBNV5GBnQgPVzV5EbU
-kkjKPJy1Ipv6Eixoqed2u54/68fmvGSEC+zH8nu7noMbvF7nuspPfwawf9GQNq3jt3qZRuuk
-Us2EB5GGz8p9gxgFnv/GtrUQ+7HxCVvJRwFuyXkqwfqo2kxnE4C1jS05xjZ3ioo5gQlncC5f
-ib63YtU6Gvlnh9zq/LV0bMUg6SygRNAHoO+BTKBBHlzNMg0ixUESzRmxF1hJA3rbhCx4xPfe
-OHZh6NtSHPSD+88nzK3qBv1Gosz9In6O5/aYreQgLT6Vrbb+jlCPs3BJGaRPNwVKMIICpgIB
-ADCBjTCBhzELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0
-LVBldGVyc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRl
-c3RlcjEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbQIBAzANBgkqhkiG9w0BAQEFAASC
-AgAqLoqQoTHSNgSyp+8XuylkokpE8/zE8mQ0TVnVrP8LrK8ppxBAkVr8/GGG2BNtwKXJEaEP
-7cDoHYABQS4xQD3CrEBtq180rDR0yGunRYuCTlBrAAZWy8Nq3/KGbwxJJBaqHUomaqiRUeiD
-j+V42pU1hVhBVUR+dNlDrtnLyh1OhbR1/ddhU0WCioAAdbyVCntgyHQU0Sr55xVINP69RUte
-OCHQj+s5b6HnfN2pQjdjZf5pHJdCajO5IpKn68LaBY2Q0YZVZYs3PLnRe0yHpZvHa7T5KRNs
-XL4fkJ5n6wrP48UH2eHyopHCww28qZupbnSkhZCERa7mJ3niExaRxnZnS28IpaU4AlbMLZGu
-fz7woFxcxrcwkGeGXCTxZkj6UkXXFZK/s04iWoQdcN59A56yGc0RTHg29AHQshJ2d3Ydcm+N
-/Uv9OwSkDOG/V6f6BvmWjBldF6SI2sOmEEK+SQy0OC81TYJfqVHRfT0lyUfz43JGGwOhgb1j
-hyxBgugzGq2+KkIdyONMdBZXe4HuZOm4MStB/5NOdb1lPttB5zgkcdq9I/rVl+QRSh3wjZWf
-2JL9HvlljweVNbVoCiwQjm17u14PnNlW0797YTXizHlsLUpupMgI8N2eDKv53cKTGyT/z+yc
-AmqCYdph3oA7VWlXBvQbZUeDxOarpfTnq+aOEjCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE
-CBUAZSknCBIhoIAEYHg/qMyTJn4qoHtG5PnCve9BA5m+02B8RFLe1EQ/4+S3r2tP8jSvCPvk
-jMrzK7wrza2xlXaOisFvakPMyjTqwCkup55n3LDELbXMe3eFt62L7mWSD1HI+bwCEM2d7v/5
-DAQIbHw1VJzs8tsAAAAAAAAAAAAA
diff --git a/t/data/smime/mails/with-bin-attachment.eml b/t/data/smime/mails/with-bin-attachment.eml
deleted file mode 100644
index 4f4f89d..0000000
--- a/t/data/smime/mails/with-bin-attachment.eml
+++ /dev/null
@@ -1,45 +0,0 @@
-Date: Fri, 22 Dec 2006 06:41:22 +0300
-From: root at localhost
-X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
-User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
-MIME-Version: 1.0
-To:  sender at test.com
-Subject: test
-Content-Type: application/x-pkcs7-mime; name="smime.p7m"
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment; filename="smime.p7m"
-Content-Description: S/MIME Encrypted Message
-
-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
-AA==
diff --git a/t/data/smime/mails/with-text-attachment.eml b/t/data/smime/mails/with-text-attachment.eml
deleted file mode 100644
index e18c759..0000000
--- a/t/data/smime/mails/with-text-attachment.eml
+++ /dev/null
@@ -1,44 +0,0 @@
-Date: Fri, 22 Dec 2006 05:24:50 +0300
-From: root at localhost
-X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
-User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
-MIME-Version: 1.0
-To: sender at test.com
-Subject: test
-Content-Type: application/x-pkcs7-mime; name="smime.p7m"
-Content-Transfer-Encoding: base64
-Content-Disposition: attachment; filename="smime.p7m"
-Content-Description: S/MIME Encrypted Message
-
-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
-qA5aPVUAAAAAAAAAAAAA

commit 0e38749daae9a08586b2b5b51598da8ad8a01c5b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:03:57 2010 +0300

    always pass top entity when we detecting crypto parts

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 0d7a215..a9b032e 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -51,13 +51,18 @@ sub FindProtectedParts {
     my $entity = $args{'Entity'};
     return () if $args{'Skip'}{ $entity };
 
+    $args{'TopEntity'} ||= $entity;
+
     my @protocols = $args{'Protocols'}
         ? @{ $args{'Protocols'} } 
         : $self->EnabledOnIncoming;
         
     foreach my $protocol ( @protocols ) {
         my $class = $self->LoadImplementation( $protocol );
-        my %info = $class->CheckIfProtected( Entity => $entity );
+        my %info = $class->CheckIfProtected(
+            TopEntity => $args{'TopEntity'},
+            Entity    => $entity,
+        );
         next unless keys %info;
 
         $args{'Skip'}{ $entity } = 1;
@@ -95,6 +100,7 @@ sub FindProtectedParts {
         foreach my $protocol ( @protocols ) {
             my $class = $self->LoadImplementation( $protocol );
             my @list = $class->FindScatteredParts(
+                Entity  => $args{'TopEntity'},
                 Parts   => \@parts,
                 Parents => \%parent,
                 Skip    => $args{'Skip'}

commit 0411bf5c37203109fb9359771d47970d744daa0d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:05:06 2010 +0300

    when key not found, $res{info} is empty

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index ae20332..46596a4 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -47,7 +47,7 @@ sub SignEncrypt {
             $RT::Logger->debug( "Considering encrypting message to " . $address );
 
             my %key_info = $self->GetKeysInfo( Key => $address );
-            unless ( %key_info ) {
+            unless ( defined $key_info{'info'} ) {
                 $res{'exit_code'} = 1;
                 my $reason = 'Key not found';
                 $res{'status'} .=

commit ce60d312a87f484ace7fa77e2ee5860f13f93706
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:05:55 2010 +0300

    improve recipient detection for SMIME
    
    check all recipients from message head, as well every address related
    to the queue, use someheuristicto guess most likely candidate

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 46596a4..6e9607b 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -259,33 +259,38 @@ sub DecryptRFC3851 {
 
     my $msg = $args{'Data'}->as_string;
 
+    my %addresses;
+    $addresses{lc $_}++ foreach
+        map $_->address,
+        map Email::Address->parse($_),
+        @{$args{'Recipients'}};
+
     my $action = 'correspond';
     $action = 'comment' if grep defined && $_ eq 'comment', @{ $args{'Actions'}||[] };
-
-    my $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;
+    if ( $action eq 'correspond' ) {
+        my $i = 1;
+        $addresses{lc $_} += $i++ foreach (
+            $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
+            $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
+        );
+    } else {
+        my $i = 1;
+        $addresses{lc $_} += $i++ foreach (
+            $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
+            $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress'),
+        );
     }
+    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
 
     my $buf;
-    {
+    my $found_key = 0;
+    my $encrypted_to;
+    foreach my $address ( sort { $addresses{$b} <=> $addresses{$a} } grep length, keys %addresses ) {
+        my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
+        next unless -e $key_file && -r _;
+
+        $found_key = 1;
+
         local $ENV{SMIME_PASS} = $self->GetPassphrase( Address => $address );
         local $SIG{CHLD} = 'DEFAULT';
         my $cmd = join( ' ', shell_quote(
@@ -294,11 +299,17 @@ sub DecryptRFC3851 {
             -recip => $key_file,
         ) );
         safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
+        unless ( $? ) {
+            $encrypted_to = $address;
+            last;
+        }
+
+        next if index($res{'stderr'}, 'no recipient matches key') >= 0;
+
         $res{'exit_code'} = $?;
-    }
-    if ( $res{'exit_code'} ) {
         $res{'message'} = "openssl exitted with error code ". ($? >> 8)
             ." and error: $res{stderr}";
+        $RT::Logger->error( $res{'message'} );
         $res{'status'} = $self->FormatStatus({
             Operation => 'Decrypt', Status => 'ERROR',
             Message => 'Decryption failed',
@@ -306,6 +317,16 @@ sub DecryptRFC3851 {
         });
         return %res;
     }
+    unless ( $found_key ) {
+        $res{'exit_code'} = 1;
+        $res{'status'} = $self->FormatStatus({
+            Operation => 'KeyCheck',
+            Status    => 'MISSING',
+            Message   => "Secret key is not available",
+            KeyType   => 'secret',
+        });
+        return %res;
+    }
 
     my $res_entity = _extract_msg_from_buf( \$buf, 1 );
     $res_entity->make_multipart( 'mixed', Force => 1 );
@@ -317,7 +338,7 @@ sub DecryptRFC3851 {
     $res{'status'} = $self->FormatStatus({
         Operation => 'Decrypt', Status => 'DONE',
         Message => 'Decryption process succeeded',
-        EncryptedTo => $address,
+        EncryptedTo => $encrypted_to,
     });
 
     return %res;
@@ -434,13 +455,20 @@ sub CheckIfProtected {
                 }
             }
         }
-        return () if !$security_type && $type eq 'application/octet-stream';
+        return () unless $security_type;
 
-        return (
+        my %res = (
             Type   => $security_type,
             Format => 'RFC3851',
             Data   => $entity,
         );
+
+        if ( $security_type eq 'encrypted' ) {
+            my $top = $args{'TopEntity'}->head;
+            $res{'Recipients'} = [grep defined && length, map $top->get($_), 'To', 'Cc'];
+        }
+
+        return %res;
     }
     elsif ( $type eq 'multipart/signed' ) {
         # RFC3156, multipart/signed

commit 544ca4ef0820aefb7a8547b9a96deda205fbf1fb
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:20:42 2010 +0300

    fix number of tests

diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index b72eab2..709d902 100644
--- a/t/web/smime/outgoing.t
+++ b/t/web/smime/outgoing.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 492;
+use RT::Test tests => 494;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'

commit 77c2953862d65de55af4dbbb4ad9608237f978e0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 06:35:53 2010 +0300

    make protocol case insensetive

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index a9b032e..48fbc07 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -8,6 +8,7 @@ require RT::Crypt::GnuPG;
 require RT::Crypt::SMIME;
 
 our @PROTOCOLS = ('GnuPG', 'SMIME');
+our %PROTOCOLS = map { lc $_ => $_ } @PROTOCOLS;
 
 sub Protocols {
     return @PROTOCOLS;
@@ -28,7 +29,8 @@ sub EnabledOnIncoming {
 
 { my %cache;
 sub LoadImplementation {
-    my $class = 'RT::Crypt::'. $_[1];
+    my $proto = $PROTOCOLS{ lc $_[1] } or die "Unknown protocol '$_[1]'";
+    my $class = 'RT::Crypt::'. $proto;
     return $class if $cache{ $class }++;
 
     eval "require $class; 1" or do { require Carp; Carp::confess( $@ ) };

commit fa3a06db8bc295f02c74c95efc8aa15e8c4f852b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Feb 24 05:33:09 2010 +0300

    SMIME documentation

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 48fbc07..170f23d 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -7,6 +7,66 @@ package RT::Crypt;
 require RT::Crypt::GnuPG;
 require RT::Crypt::SMIME;
 
+=head1 NAME
+
+RT::Crypt - encrypt/decrypt and sign/verify subsystem for RT
+
+=head1 DESCRIPTION
+
+This module provides support for encryption and signing of outgoing messages, 
+as well as the decryption and verification of incoming emails using variouse
+encryption standards. At this moment L<GnuPG|RT::Crypt::GnuPG> and
+L<SMIME|RT::Crypt::SMIME> protocols are supported.
+
+=head1 CONFIGURATION
+
+You can control the configuration of this subsystem from RT's configuration file.
+Some options are available via the web interface, but to enable this functionality,
+you MUST start in the configuration file.
+
+For each protocol there is a hash with the same name in the configuration file.
+This hash controls RT specific options regarding the protocol. It allows you to
+enable/disable facility or change the format of messages, for example GnuPG use
+the following config:
+
+    Set( %GnuPG,
+        Enable => 1,
+        ... other options ...
+    );
+
+Enable the only key that generic for all protocols. A protocol may have
+additional options to tune behaviour.
+
+However, note that you B<must> add the
+L<Auth::Crypt|RT::Interface::Email::Auth::Crypt> email filter to enable
+the handling of incoming encrypted/signed messages.
+
+=head2 %Crypt
+
+Config option hash to choose protocols decrypted and verified
+in incoming messages, pick protocol for outgoing emails, behaviour on
+errors during decryptions and signatures.
+
+By default all these options are generated. Every enabled protocol
+is checked on incomming messages, but you can change that:
+
+    Set( %Crypt,
+        ...
+        Incoming => ['SMIME'],
+        ...
+    );
+
+Protocol for outgoing emails can be only one and by default it's
+first one value from above list.
+
+    Set( %Crypt,
+        ...
+        Outgoing => 'GnuPG',
+        ...
+    );
+
+=cut
+
 our @PROTOCOLS = ('GnuPG', 'SMIME');
 our %PROTOCOLS = map { lc $_ => $_ } @PROTOCOLS;
 
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 44a2531..0dbf953 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -64,7 +64,7 @@ RT::Crypt::GnuPG - encrypt/decrypt and sign/verify email messages with the GNU P
 =head1 DESCRIPTION
 
 This module provides support for encryption and signing of outgoing messages, 
-as well as the decryption and verification of incoming email.
+as well as the decryption and verification of incoming emails.
 
 =head1 CONFIGURATION
 
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 6e9607b..1aa48c0 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -1,3 +1,50 @@
+# BEGIN BPS TAGGED BLOCK {{{
+# 
+# COPYRIGHT:
+# 
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+#                                          <jesse 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 }}}
 
 use strict;
 use warnings;
@@ -10,6 +57,40 @@ use IPC::Run3 0.036 'run3';
 use String::ShellQuote 'shell_quote';
 use RT::Util 'safe_run_child';
 
+=head1 NAME
+
+RT::Crypt::SMIME - encrypt/decrypt and sign/verify email messages with the SMIME
+
+=head1 CONFIGURATION
+
+You should start from reading L<RT::Crypt>.
+
+=head2 %SMIME
+
+    Set( %SMIME,
+        Enable => 1,
+        OpenSSL => '/opt/local/bin/openssl',
+        Keyring => '/opt/rt3/var/data/smime',
+        Passphrase => {
+            'queue.address at exampl.com' => 'passphrase',
+        },
+    );
+
+=head3 OpenSSL
+
+Path to openssl executable.
+
+=head3 Keyring
+
+Path to directory with keys and certificates for queues. Key and certificates
+should be stored in a PEM file named F<email.address at example.com.pem>.
+
+=head3 Passphrase
+
+Hash with passphrases for keys in the keyring.
+
+=cut
+
 { my $cache = shift;
 sub OpenSSLPath {
     return $cache ||= RT->Config->Get('SMIME')->{'OpenSSL'};

commit 6dfc69e6df95c97d56fe251c80695d4b0907ba8d
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Wed Feb 24 17:33:43 2010 -0500

    this check is separate from the gpg check
    
    still need sbin/rt-test-depencies test for IPC::Run3

diff --git a/configure.ac b/configure.ac
index dd52632..417f3cc 100755
--- a/configure.ac
+++ b/configure.ac
@@ -316,7 +316,7 @@ AC_SUBST(RT_GPG)
 
 dnl RT's SMIME support
 AC_CHECK_PROG([RT_SMIME], [openssl], "yes", "no")
-AC_ARG_ENABLE(gpg,
+AC_ARG_ENABLE(smime,
             AC_HELP_STRING([--enable-smime],
                            [Turns on Secure MIME (SMIME) support]),
             RT_SMIME=$enableval)

commit 780cd7c36c88664cbec3406558f0286336797a6f
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Thu Feb 25 18:15:38 2010 -0500

    autocreate a User custom field
    
    This holds a pem formatted smime certificate

diff --git a/etc/smime.initialdata b/etc/smime.initialdata
new file mode 100644
index 0000000..1e24d33
--- /dev/null
+++ b/etc/smime.initialdata
@@ -0,0 +1,8 @@
+ at CustomFields = (
+    {
+        Name        => 'SMIME Key',
+        Type        => 'TextSingle',
+        Disabled    => 0,
+        LookupType  => 'RT::User',
+    },
+);

commit 5f99c53df696d369dcb1f980a9e8114a95f3126e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Mar 2 08:48:08 2010 +0300

    extract users' certificates out of signed messages

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 1aa48c0..d292f5b 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -279,11 +279,13 @@ sub VerifyRFC3851 {
 
     my %res;
     my $buf;
+    my $keyfh = File::Temp->new;
     {
         local $SIG{CHLD} = 'DEFAULT';
-        my $cmd = join( ' ', shell_quote(
+        my $cmd = join ' ', shell_quote(
             $self->OpenSSLPath, qw(smime -verify -noverify),
-        ) );
+            '-signer', $keyfh->filename,
+        );
         safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
         $res{'exit_code'} = $?;
     }
@@ -295,7 +297,27 @@ sub VerifyRFC3851 {
     }
 
     my @signers;
-    {
+    if ( my $key = do { $keyfh->seek(0, 0); local $/; readline $keyfh } ) {{
+        my %info = $self->GetCertificateInfo( Certificate => $key );
+        last if $info{'exit_code'};
+
+        push @signers, @{ $info{'info'} };
+
+        my $user = RT::User->new( $RT::SystemUser );
+        # if we're not going to create a user here then
+        # later it will be created without key
+        $user->LoadOrCreateByEmail( $signers[0]{'User'}[0]{'String'} );
+        my $current_key = $user->FirstCustomFieldValue('SMIME Key');
+        last if $current_key && $current_key eq $key;
+
+        my ($status, $msg) = $user->AddCustomFieldValue(
+            Field => 'SMIME Key', Value => $key,
+        );
+        $RT::Logger->error("Couldn't set 'SMIME Key' for user #". $user->id .": $msg")
+            unless $status;
+    }}
+
+    if ( !@signers ) {
         my $pkcs7_info;
         local $SIG{CHLD} = 'DEFAULT';
         my $cmd = join( ' ', shell_quote(
@@ -326,7 +348,7 @@ sub VerifyRFC3851 {
     $res{'status'} =
         "Operation: Verify\nStatus: DONE\n"
         ."Message: The signature is good\n"
-        ."UserString: ". $signers[0]{'User'}{'String'} ."\n"
+        ."UserString: ". $signers[0]{'User'}[0]{'String'} ."\n"
     ;
 
     return %res;
@@ -807,8 +829,8 @@ sub ParsePKCS7Info {
     # oddly, but a certificate can be duplicated
     my %seen;
     @res = grep !$seen{ $_->{'Content'} }++, grep keys %$_, @res;
-    $_->{'User'} = delete $_->{'Subject'} foreach @res;
-    
+    $_->{'User'} = [delete $_->{'Subject'}] foreach @res;
+
     return @res;
 }
 

commit d2bfab7dad27aaa1b4ad8f238ea399c0abab0dca
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Apr 14 19:27:48 2010 +0400

    update description of the %Crypt hash option

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index d43b26b..114fc52 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -706,9 +706,18 @@ protocols are supported.
 
 Options generic for all cryptography protocols.
 
-Set C<ProcessIncomming> to list of security protocols that should be
-analyzed in incomming emails. Note that C<Auth::Crypt> mail plugin
-should be added anyway to the C<@MailPlugins> option.
+Every enabled security protocol analyzed in incoming emails. Set
+C<Incoming> to list of only those security protocols that should be
+analyzed in incoming emails. Usually you don't need to change this.
+Note that C<Auth::Crypt> mail plugin should be added anyway to
+the C<@MailPlugins> option.
+
+For outgoing emails first security protocol from the above list is
+used. Use C<Outgoing> option to set a security protocol that should
+be used in outgoing emails. This helpful when SMIME and GnuPG are
+analyzed in incoming and you want make sure the correct one is used
+for outogin. At this moment only one protocol can be used to protect
+outgoing emails.
 
 Set C<RejectOnMissingPrivateKey> to false if you don't want to reject
 emails encrypted for key RT doesn't have and can not decrypt.

commit 2fa6fa6f5cf5894f4f9fde0d421db6193c0f874d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Apr 14 19:28:36 2010 +0400

    Don't set GnuPG explicitly as the protocol used for outoging emails
    
    Check in the config code will pick enabled one and GnuPG if
    multiple are enabled

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 114fc52..108559a 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -730,8 +730,8 @@ with incorrect data.
 =cut
 
 Set( %Crypt,
-    Incomming                 => undef, # ['GnuPG', 'SMIME']
-    Outgoing                  => 'GnuPG', # 'SMIME'
+    Incoming                  => undef, # ['GnuPG', 'SMIME']
+    Outgoing                  => undef, # 'SMIME' or 'GnuPG'
 
     RejectOnMissingPrivateKey => 1,
     RejectOnBadData           => 1,

commit 1c1048b38d4d41588d5914e286db89dc9f0a9e11
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Apr 17 01:59:36 2010 +0400

    if passphrase is empty then we shouldn't use -passin

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index d292f5b..4ca9405 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -173,9 +173,12 @@ sub SignEncrypt {
             unless defined $args{'Passphrase'};
 
         push @command, join ' ', shell_quote(
-            $self->OpenSSLPath, qw(smime -sign -passin env:SMIME_PASS),
+            $self->OpenSSLPath, qw(smime -sign),
             -signer => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
             -inkey  => $opts->{'Keyring'} .'/'. $args{'Signer'} .'.pem',
+            (defined $args{'Passphrase'} && length $args{'Passphrase'})
+                ? (qw(-passin env:SMIME_PASS))
+                : (),
         );
     }
     if ( $args{'Encrypt'} ) {
@@ -398,8 +401,10 @@ sub DecryptRFC3851 {
         local $SIG{CHLD} = 'DEFAULT';
         my $cmd = join( ' ', shell_quote(
             $self->OpenSSLPath,
-            qw(smime -decrypt -passin env:SMIME_PASS),
-            -recip => $key_file,
+            qw(smime -decrypt), '-recip' => $key_file,
+            (defined $ENV{'SMIME_PASS'} && length $ENV{'SMIME_PASS'})
+                ? (qw(-passin env:SMIME_PASS))
+                : (),
         ) );
         safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
         unless ( $? ) {

commit d0f0fd40603d3762b4d819dccf1de42351c2bd5b
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Apr 17 04:38:37 2010 +0400

    users' private keys are only supported for GnuPG

diff --git a/share/html/Elements/GnuPG/SelectKeyForSigning b/share/html/Elements/GnuPG/SelectKeyForSigning
index 13b40df..813fc73 100644
--- a/share/html/Elements/GnuPG/SelectKeyForSigning
+++ b/share/html/Elements/GnuPG/SelectKeyForSigning
@@ -58,7 +58,10 @@ $Name => 'SignUsing',
 $User => undef,
 </%ARGS>
 <%INIT>
-return unless RT->Config->Get('GnuPG')->{'Enable'};
+return unless RT->Config->Get('Crypt')->{'Enable'};
 
-my $user_key = $User->PrivateKey;
+# XXX: Only GnuPG at this moment supports user's private keys
+my $user_key;
+$user_key = $User->PrivateKey
+    if RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG';;
 </%INIT>
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
index e51ec1d..fe7493a 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -48,7 +48,9 @@
 <table>
 <td><% loc('Sign')%></td>
 <td><& /Widgets/Form/Boolean:InputOnly, Name => 'Sign', CurrentValue => $self->{'Sign'} &>
+% if ( RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG' ) {
 using <& SelectKeyForSigning, User => $session{'CurrentUser'}->UserObj &>
+% }
 </td>
 <td><% loc('Encrypt')%></td>
 <td><& /Widgets/Form/Boolean:InputOnly, Name => 'Encrypt', CurrentValue => $self->{'Encrypt'} &></td>

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


More information about the Rt-commit mailing list