[Rt-commit] rt branch, 4.2/smime-v2, created. rt-4.0.4-338-gadccbd6

Ruslan Zakirov ruz at bestpractical.com
Fri Dec 2 09:55:12 EST 2011


The branch, 4.2/smime-v2 has been created
        at  adccbd695104c87fd177dc4134ecb3c624a08f0a (commit)

- Log -----------------------------------------------------------------
commit fb36f3ce5c9f6442322aec36cc46d07980beab36
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 d252504..d256b60 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -582,6 +582,7 @@ sub SignEncryptRFC3156 {
 }
 
 sub SignEncryptInline {
+    my $self = shift;
     my %args = ( @_ );
 
     my $entity = $args{'Entity'};
@@ -590,19 +591,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,
 
@@ -691,6 +693,7 @@ sub _SignEncryptTextInline {
 }
 
 sub _SignEncryptAttachmentInline {
+    my $self = shift;
     my %args = (
         Entity => undef,
 
@@ -793,6 +796,7 @@ sub _SignEncryptAttachmentInline {
 }
 
 sub SignEncryptContent {
+    my $self = shift;
     my %args = (
         Content => undef,
 
@@ -1030,6 +1034,7 @@ sub FindProtectedParts {
 =cut
 
 sub VerifyDecrypt {
+    my $self = shift;
     my %args = (
         Entity    => undef,
         Detach    => 1,
@@ -1053,7 +1058,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
@@ -1091,9 +1096,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 = GnuPG::Interface->new();
@@ -1148,6 +1154,7 @@ sub VerifyAttachment {
 }
 
 sub VerifyRFC3156 {
+    my $self = shift;
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
     my $gnupg = GnuPG::Interface->new();
@@ -1195,6 +1202,7 @@ sub VerifyRFC3156 {
 }
 
 sub DecryptRFC3156 {
+    my $self = shift;
     my %args = (
         Data => undef,
         Info => undef,
@@ -1275,6 +1283,7 @@ sub DecryptRFC3156 {
 }
 
 sub DecryptInline {
+    my $self = shift;
     my %args = (
         Data => undef,
         Passphrase => undef,
@@ -1325,7 +1334,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,
@@ -1383,6 +1392,7 @@ sub DecryptInline {
 }
 
 sub _DecryptInlineBlock {
+    my $self = shift;
     my %args = (
         GnuPG => undef,
         BlockHandle => undef,
@@ -1433,6 +1443,7 @@ sub _DecryptInlineBlock {
 }
 
 sub DecryptAttachment {
+    my $self = shift;
     my %args = (
         Top  => undef,
         Data => undef,
@@ -1466,7 +1477,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,
@@ -1492,6 +1503,7 @@ sub DecryptAttachment {
 }
 
 sub DecryptContent {
+    my $self = shift;
     my %args = (
         Content => undef,
         Passphrase => undef,
@@ -2392,7 +2404,7 @@ sub DrySign {
         Data    => ['t'],
     );
 
-    my %res = SignEncrypt(
+    my %res = $self->SignEncrypt(
         Sign    => 1,
         Encrypt => 0,
         Entity  => $mime,

commit 807624928a8da1778f02a49a49fd660443d8aa5f
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 5c904bc40cb6cf1ec0732d7d14841ea56d0a0685
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 d256b60..b05d791 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -397,27 +397,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 b758639ec0cc5f5ae0fc2d8493b913f165a93758
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 b05d791..428a20e 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -877,144 +877,174 @@ 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 $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
+    # we check inline PGP block later in another sub
+    return () 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  => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
-                Data    => $entity,
-            };
-        }
-        $io->close;
+    # 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 => {}, @_ );
+
+    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 ];
 
-        if ( $type eq 'multipart/encrypted' ) {
-            unless ( $protocol eq 'application/pgp-encrypted' ) {
-                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
-                return ();
+            # 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 =~ /\.${RE_FILE_EXTENSIONS}$/;
 
-        $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 || '') =~ /\.${RE_FILE_EXTENSIONS}$/}
-            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;
+
+        my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
+
+        $args{'Skip'}{$part} = 1;
+        push @res, {
+            Type      => $type,
+            Format    => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
+            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 0f20117cc6a2e3f040e9f4fb92c35c4e84022fd3
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 428a20e..4c46981 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1052,26 +1052,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 );
@@ -1082,6 +1083,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';
@@ -1089,18 +1092,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';
@@ -1108,6 +1112,8 @@ sub VerifyDecrypt {
                 'X-RT-GnuPG-Status' => $res[-1]->{'status'}
             );
         }
+    } else {
+        die "Unknow type '". $item->{'Type'} ."' of protected item";
     }
     return @res;
 }

commit 9b6eb8f5cf791c3114418cfcc8f7dcae68278361
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 9e4de5e..7a6f00f 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -578,7 +578,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',
         PostLoadCheck => sub {

commit b6d183b46192ccfd66e808576cb57a383f6ec948
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 3a5e0c29196c3a09b3411ee1b51ec425caed3a9a
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/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index e24a32c..d31bbf4 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -115,7 +115,7 @@ sub GetCurrentUser {
         Entity => $args{'Message'}, AddStatus => 1,
     );
     if ( $status && !@res ) {
-        $args{'Message'}->head->add(
+        $args{'Message'}->head->replace(
             'X-RT-Incoming-Encryption' => 'Not encrypted'
         );
         return 1;
@@ -150,21 +150,21 @@ sub GetCurrentUser {
                     $decrypted = 1;
                 }
                 if ( $_->{Operation} eq 'Verify' && $_->{Status} eq 'DONE' ) {
-                    $part->head->add(
+                    $part->head->replace(
                         'X-RT-Incoming-Signature' => $_->{UserString}
                     );
                 }
             }
         }
 
-        $part->head->add(
+        $part->head->replace(
             'X-RT-Incoming-Encryption' => 
                 $decrypted ? 'Success' : 'Not encrypted'
         );
     }
 
     my %seen;
-    $args{'Message'}->head->add( 'X-RT-Privacy' => $_ )
+    $args{'Message'}->head->replace( 'X-RT-Privacy' => $_ )
         foreach grep !$seen{$_}++, @found;
 
     return 1;
diff --git a/lib/RT/Interface/Email/Auth/GnuPG.pm b/lib/RT/Interface/Email/Auth/GnuPG.pm
index eb2cddc..f8d1341 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/rt4/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->replace(
-            '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->replace( '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->replace(
-                        'X-RT-Incoming-Signature' => $_->{UserString}
-                    );
-                }
-            }
-        }
-
-        $part->head->replace(
-            '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(@_) }
 
 RT::Base->_ImportOverlays();
 

commit ee2d4cd5b6729b8b3d14fe49a32dd5a34be96acc
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 a066de9af61aea3f162f34c5352d4b3a4c7ae22d
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 4cd742426183e7288e60b9e0386d72b4f839f6eb
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.pm b/lib/RT/Attachment.pm
index 6825d83..f21a83c 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -742,7 +742,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 4c46981..86e018e 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1720,6 +1720,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 e8070e9..497ad01 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -819,8 +819,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'} );
@@ -887,7 +887,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 ffb0597..06e2c9d 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -22,7 +22,7 @@ diag 'only signing. correct passphrase';
         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'} );
@@ -36,7 +36,7 @@ diag 'only signing. correct passphrase';
     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" );
@@ -60,7 +60,7 @@ diag 'only signing. missing passphrase';
     );
     my %res;
     warning_like {
-        %res = RT::Crypt::GnuPG::SignEncrypt(
+        %res = RT::Crypt->SignEncrypt(
             Entity     => $entity,
             Encrypt    => 0,
             Passphrase => ''
@@ -85,7 +85,7 @@ diag 'only signing. wrong passphrase';
 
     my %res;
     warning_like {
-        %res = RT::Crypt::GnuPG::SignEncrypt(
+        %res = RT::Crypt->SignEncrypt(
             Entity     => $entity,
             Encrypt    => 0,
             Passphrase => 'wrong',
@@ -109,7 +109,7 @@ diag 'encryption only';
         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';
 
     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" );
@@ -138,7 +138,7 @@ diag 'encryption only, bad recipient';
 
     my %res;
     warning_like {
-        %res = RT::Crypt::GnuPG::SignEncrypt(
+        %res = RT::Crypt->SignEncrypt(
             Entity => $entity,
             Sign   => 0,
         );
@@ -160,7 +160,7 @@ diag 'encryption and signing with combined method';
         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" );
 
@@ -175,7 +175,7 @@ diag 'encryption and signing with combined method';
 
     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" );
@@ -190,14 +190,14 @@ diag 'encryption and signing with cascading, sign on encrypted';
         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" );
@@ -212,7 +212,7 @@ diag 'find signed/encrypted part deep inside';
         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(
@@ -220,7 +220,7 @@ diag 'find signed/encrypted part deep inside';
         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" );
@@ -236,7 +236,7 @@ diag 'wrong signed/encrypted parts: no protocol';
         Data    => ['test'],
     );
 
-    my %res = RT::Crypt::GnuPG::SignEncrypt(
+    my %res = RT::Crypt->SignEncrypt(
         Entity => $entity,
         Sign   => 0,
     );
@@ -246,7 +246,7 @@ diag 'wrong signed/encrypted parts: no protocol';
 
     my @parts;
     warning_like {
-        @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+        @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     } qr{Entity is 'multipart/encrypted', but has no protocol defined. Skipped};
 
     is( scalar @parts, 0, 'no protected parts' );
@@ -261,7 +261,7 @@ diag 'wrong signed/encrypted parts: not enought parts';
         Data    => ['test'],
     );
 
-    my %res = RT::Crypt::GnuPG::SignEncrypt(
+    my %res = RT::Crypt->SignEncrypt(
         Entity => $entity,
         Sign   => 0,
     );
@@ -271,7 +271,7 @@ diag 'wrong signed/encrypted parts: not enought parts';
 
     my @parts;
     warning_like {
-        @parts = RT::Crypt::GnuPG::FindProtectedParts( Entity => $entity );
+        @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
     } qr/Encrypted or signed entity must has two subparts. Skipped/;
     is( scalar @parts, 0, 'no protected parts' );
 }
@@ -284,11 +284,11 @@ diag 'wrong signed/encrypted parts: wrong proto';
         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' );
 }
 
@@ -300,11 +300,11 @@ diag 'wrong signed/encrypted parts: wrong proto';
         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' );
 }
 
@@ -314,7 +314,7 @@ diag 'verify inline and in attachment signatures';
     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 ad22cd2010cb87ff222dfca48c8ec9162215bcd8
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 86e018e..02aa767 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -440,7 +440,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;
@@ -622,7 +622,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'} ) {
@@ -711,7 +711,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'};
@@ -814,7 +814,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'} ) {
@@ -888,7 +888,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" );
@@ -1251,7 +1251,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 );
@@ -1330,7 +1330,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 );
@@ -1491,7 +1491,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 );
@@ -1545,7 +1545,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 );
@@ -1611,6 +1611,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';
 }
@@ -2009,7 +2010,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'};
 
@@ -2030,7 +2031,7 @@ sub GetKeysForEncryption {
 
 sub GetKeysForSigning {
     my $key_id = shift;
-    return GetKeysInfo( $key_id, 'private', @_ );
+    return $self->GetKeysInfo( $key_id, 'private', @_ );
 }
 
 sub CheckRecipients {
@@ -2107,12 +2108,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;
@@ -2318,6 +2320,7 @@ sub _ParseDate {
 }
 
 sub DeleteKey {
+    my $self = shift;
     my $key = shift;
 
     my $gnupg = GnuPG::Interface->new();
@@ -2365,6 +2368,7 @@ sub DeleteKey {
 }
 
 sub ImportKey {
+    my $self = shift;
     my $key = shift;
 
     my $gnupg = GnuPG::Interface->new();
@@ -2417,6 +2421,7 @@ Returns a true value if all went well.
 =cut
 
 sub DrySign {
+    my $self = shift;
     my $from = shift;
 
     my $mime = MIME::Entity->build(
@@ -2449,6 +2454,7 @@ properly (and false otherwise).
 
 
 sub Probe {
+    my $self = shift;
     my $gnupg = GnuPG::Interface->new();
     my %opt = RT->Config->Get('GnuPGOptions');
     $gnupg->options->hash_init(

commit fa25a51ef44a8320f1a5c1bb1a702139a8fd316f
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 317045bbf6da7774c3bd97682d2ccc4512a849f8
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.pm b/lib/RT/Attachment.pm
index f21a83c..fbda3a8 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -728,7 +728,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 497ad01..1d5f29a 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -823,7 +823,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 19dfb0d..1f9ce2e 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1142,7 +1142,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 b0f366ff3532be560fe861dc38a6793d617edb2b
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 06e2c9d..b86850a 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -25,7 +25,9 @@ diag 'only signing. correct passphrase';
     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');
@@ -42,9 +44,11 @@ diag 'only signing. correct passphrase';
     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');
@@ -69,7 +73,9 @@ diag 'only signing. missing passphrase';
     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';
     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';
     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');
@@ -147,7 +157,9 @@ diag 'encryption only, bad recipient';
     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');
 }
@@ -164,7 +176,9 @@ diag 'encryption and signing with combined method';
     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');
@@ -325,8 +339,10 @@ diag 'verify inline and in attachment signatures';
     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 738bee86181033871dfec38b13ab770febbcee59
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 cd5426a..fa40fe5 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 7d77e134cf43ac655dda1932f57e5b01736cbbbe
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 dabf66e..ccbd0b6 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -134,7 +134,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 fa40fe5..2aaa871 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 0191e140d8399ed0a7be83864a57f4de20e55e6e
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 02aa767..f25ba3a 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -408,6 +408,7 @@ sub SignEncrypt {
 }
 
 sub SignEncryptRFC3156 {
+    my $self = shift;
     my %args = (
         Entity => undef,
 
@@ -1012,7 +1013,7 @@ sub FindScatteredParts {
     foreach my $part ( @parts ) {
         next if $args{'Skip'}{$part};
 
-        my $type = $self->_CheckIfProtectedInline( $entity );
+        my $type = $self->_CheckIfProtectedInline( $part );
         next unless $type;
 
         my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
@@ -1021,7 +1022,7 @@ sub FindScatteredParts {
         push @res, {
             Type      => $type,
             Format    => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
-            Data      => $entity,
+            Data      => $part,
         };
     }
 
@@ -1089,7 +1090,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' ) {
@@ -1109,13 +1110,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( @_ ) }
@@ -2009,6 +2010,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'};
@@ -2030,18 +2032,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;
@@ -2100,14 +2104,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 da1b5ec6d67c1a34ecefd567504de37569df4420
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 b1c3a63880e52798e01f7ad90577785b348dedb2
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 fbe0e4f70b57e88eb04173a439b4e6478d592407
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 9a31d15caee5fe83214f1a26a651100fd89fec65
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 290f7fbf348026a317478357137aba8e6d7244a3
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 f25ba3a..f89e2cf 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -898,11 +898,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 ();
@@ -1032,6 +1053,7 @@ sub FindScatteredParts {
 sub _CheckIfProtectedInline {
     my $self = shift;
     my $entity = shift;
+    my $check_for_signature = shift || 0;
 
     my $io = $entity->open('r');
     unless ( $io ) {
@@ -1039,8 +1061,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 6f45837b41c8dd68c76def4196f4d01da53ba0a9
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 f89e2cf..9fb95af 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1005,7 +1005,7 @@ sub FindScatteredParts {
             push @res, {
                 Type      => 'signed',
                 Format    => 'Attachment',
-                Top       => $entity,
+                Top       => $args{'Parents'}{$data_part_in},
                 Data      => $data_part_in,
                 Signature => $sig_part,
             };
@@ -1025,7 +1025,7 @@ sub FindScatteredParts {
         push @res, {
             Type    => 'encrypted',
             Format  => 'Attachment',
-            Top     => $entity,
+            Top     => $args{'Parents'}{$part},
             Data    => $part,
         };
     }

commit 0702e6abc24f261ff8af81a7a37d86dff20445a5
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 b86850a..c353082 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -10,9 +10,10 @@ BEGIN {
         qw/data gnupg keyrings/ );
 }
 
-use RT::Test::GnuPG tests => 96, gnupg_options => { homedir => $homedir };
+use RT::Test::GnuPG tests => 99, gnupg_options => { homedir => $homedir };
 use Test::Warn;
 
+use_ok('RT::Crypt');
 use_ok('MIME::Entity');
 
 diag 'only signing. correct passphrase';
@@ -258,12 +259,11 @@ diag 'wrong signed/encrypted parts: no protocol';
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => undef );
 
-    my @parts;
-    warning_like {
-        @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
-    } qr{Entity is 'multipart/encrypted', but has no protocol defined. Skipped};
-
-    is( scalar @parts, 0, 'no protected parts' );
+    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" );
+    is( $parts[0]->{'Top'}, $entity, "it's the same entity" );
 }
 
 diag 'wrong signed/encrypted parts: not enought parts';

commit c99a804d9004de4617b6eea048dc375606cc3f27
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 1f4f962..ea12648 100755
--- a/configure.ac
+++ b/configure.ac
@@ -307,6 +307,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 d87e50c7a2015f86f9f36c3dc5127d134dcad27b
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 fb8fbc1..c394734 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1949,8 +1949,59 @@ Set($DefaultTimeUnitsToHours, 0);
 
 
 
+=head1 Cryptography
 
-=head1 GnuPG integration
+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
@@ -1960,27 +2011,21 @@ be found by running the command `perldoc L<RT::Crypt::GnuPG>` (or
 
 =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 1.
 
-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.
-
 =cut
 
 Set(%GnuPG,
-    Enable => @RT_GPG@,
+    Enable                 => @RT_GPG@,
     OutgoingMessagesFormat => "RFC", # Inline
     AllowEncryptDataInDB   => 0,
-
-    RejectOnMissingPrivateKey => 1,
-    RejectOnBadData           => 1,
 );
 
 =item C<%GnuPGOptions>

commit aa5613f091910bd36b9540ef5132204bf4a1d027
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 7a6f00f..594dc47 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -611,8 +611,22 @@ our %META = (
             warn 'RTFM has been integrated into core RT, and must be removed from your @Plugins';
         },
     },
+    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');
@@ -627,7 +641,6 @@ our %META = (
                 return;
             }
 
-
             require RT::Crypt::GnuPG;
             unless (RT::Crypt::GnuPG->Probe()) {
                 $RT::Logger->debug(
@@ -635,6 +648,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."
+                );
+            }
         }
     },
     ResolveDefaultUpdateType => {

commit 7e17a9e0084041c6ef6158ad305b8e3653d29008
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 de88b8aa2ee0ea97a9b4480b6d35f3c6f8cca80b
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 c394734..e3c717f 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1977,7 +1977,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 594dc47..b5eba24 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -615,11 +615,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];
             }
         },
     },
@@ -631,6 +636,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 7b5ec9c834ac8f988bfc85124720aeb3ef68b4d2
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 9fb95af..5b4e196 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;
@@ -2047,7 +2048,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;
 
@@ -2196,12 +2197,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 b089ebb05119c6b1e9a680ee3c76a97c6ef4abb5
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 62566be4aaeb95b4db7e4ba3cd19bc7d43016d15
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 9df237425887be7ccfe6e32b088075731fbc97a4
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 8f31ae9b7eb85ab7049631533dc283a94d7f4096
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 5b4e196..ed3cbd8 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2039,7 +2039,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'};
 
@@ -2061,7 +2061,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 {
@@ -2130,29 +2130,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 = GnuPG::Interface->new();

commit 452138ef375b071732a2ea4ba696ae0a89c5db13
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 961e84e..831ed63 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 class="label"><% loc('Trust') %>:</th>  <td><% loc( $res{'info'}{'Trust'} ) %></td></tr>
 % }
 
@@ -71,9 +80,14 @@
 </td></tr>
 % }
 
-</table>
 % }
-</&>
+
+% if ( @protocols ) {
+<tr><th colspan="2"> </th></tr>
+% }
+
+% }
+</table></&>
 
 <%ARGS>
 $EmailAddress
@@ -81,14 +95,13 @@ $Type => 'public'
 </%ARGS>
 <%INIT>
 return if ($m->cache_self( key => join("||",$EmailAddress,$Type, $$), expires_in => '2 minutes'));
-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 05f11d8d63d0514b684fec1aa494bb707c82ef33
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 186e0da..f528677 100755
--- a/share/html/Admin/Queues/Modify.html
+++ b/share/html/Admin/Queues/Modify.html
@@ -113,7 +113,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>
@@ -126,7 +126,7 @@
 % $m->callback( %ARGS, QueueObj => $QueueObj, results => \@results );
 </td></tr>
 
-% if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+% if ( RT->Config->Get('Crypt')->{'Enable'} ) {
 <tr><td colspan="4">
 % if ( my $email = $QueueObj->CorrespondAddress || RT->Config->Get('CorrespondAddress') ) {
 <& /Admin/Elements/ShowKeyInfo, Type => 'private', EmailAddress => $email &>
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/GnuPG/SignEncryptWidget
index ccbd0b6..05aad87 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -138,7 +138,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'};
     }
 }
@@ -161,13 +161,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 306b779..a417667 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 2aaa871..242cbda 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 218b5343ecf545aa15d9cd88388d3a15b03f6b6e
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 2d5d5d9..6fb1a93 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -20,6 +20,7 @@ use RT::Test::GnuPG
 use String::ShellQuote 'shell_quote';
 use IPC::Run3 'run3';
 
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
 my ($baseurl, $m) = RT::Test->started_ok;
 
 # configure key for General queue
diff --git a/t/web/gnupg-select-keys-on-create.t b/t/web/gnupg-select-keys-on-create.t
index 893ae65..ba3984d 100644
--- a/t/web/gnupg-select-keys-on-create.t
+++ b/t/web/gnupg-select-keys-on-create.t
@@ -37,7 +37,7 @@ diag "check that signing doesn't work if there is no key";
 {
     RT::Test->import_gnupg_key('rt-recipient at example.com');
     RT::Test->trust_gnupg_key('rt-recipient at example.com');
-    my %res = RT::Crypt::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';
 }
 
@@ -74,7 +74,7 @@ diag "import first key of rt-test\@example.com";
 my $fpr1 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com', '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'};
 }
@@ -123,7 +123,7 @@ diag "import a second key of rt-test\@example.com";
 my $fpr2 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com.2', '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'};
 }
@@ -170,7 +170,7 @@ diag "check that things still doesn't work if two keys are not trusted";
 
 {
     RT::Test->lsign_gnupg_key( $fpr1 );
-    my %res = RT::Crypt::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 1509d1d..cc7baa0 100644
--- a/t/web/gnupg-select-keys-on-update.t
+++ b/t/web/gnupg-select-keys-on-update.t
@@ -52,7 +52,7 @@ diag "check that signing doesn't work if there is no key";
 {
     RT::Test->import_gnupg_key('rt-recipient at example.com');
     RT::Test->trust_gnupg_key('rt-recipient at example.com');
-    my %res = RT::Crypt::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';
 }
 
@@ -91,7 +91,7 @@ diag "import first key of rt-test\@example.com";
 my $fpr1 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com', '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'};
 }
@@ -141,7 +141,7 @@ diag "import a second key of rt-test\@example.com";
 my $fpr2 = '';
 {
     RT::Test->import_gnupg_key('rt-test at example.com.2', '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'};
 }
@@ -189,7 +189,7 @@ diag "check that things still doesn't work if two keys are not trusted";
 
 {
     RT::Test->lsign_gnupg_key( $fpr1 );
-    my %res = RT::Crypt::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 8d0081aca07f68a3df2de1f15cd39bde8766b89d
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.pm b/lib/RT/Attachment.pm
index fbda3a8..e8c86be 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -728,9 +728,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 ed3cbd8..5aecea5 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -506,7 +506,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);
@@ -629,7 +629,7 @@ sub _SignEncryptTextInline {
 
     if ( $args{'Encrypt'} ) {
         $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             @{ $args{'Recipients'} || [] };
     }
 
@@ -719,7 +719,7 @@ sub _SignEncryptAttachmentInline {
     my $entity = $args{'Entity'};
     if ( $args{'Encrypt'} ) {
         $gnupg->options->push_recipients( $_ ) foreach
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             @{ $args{'Recipients'} || [] };
     }
 
@@ -821,7 +821,7 @@ sub SignEncryptContent {
 
     if ( $args{'Encrypt'} ) {
         $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
+            map RT::Crypt->UseKeyForEncryption($_) || $_,
             @{ $args{'Recipients'} || [] };
     }
 
@@ -1993,40 +1993,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.
@@ -2038,8 +2004,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'};
 
@@ -2060,8 +2026,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 {
@@ -2072,7 +2038,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;
@@ -2082,7 +2048,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,
@@ -2433,10 +2399,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.
@@ -2445,7 +2411,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",
@@ -2472,7 +2439,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 e785fd5..3e5425c 100644
--- a/lib/RT/Interface/Web/Handler.pm
+++ b/lib/RT/Interface/Web/Handler.pm
@@ -153,7 +153,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
 
@@ -180,10 +180,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.pm b/lib/RT/User.pm
index 94c7352..bbecb07 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -1539,18 +1539,18 @@ 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);
@@ -1593,7 +1593,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 a8d8b0e..b545f67 100644
--- a/share/html/Admin/Users/GnuPG.html
+++ b/share/html/Admin/Users/GnuPG.html
@@ -79,7 +79,7 @@ $Update     => undef
 <%INIT>
 return unless RT->Config->Get('GnuPG')->{'Enable'};
 
-require RT::Crypt::GnuPG;
+require RT::Crypt;
 
 my @results;
 
@@ -91,7 +91,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 3835537..77c48f9 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 05aad87..613debe 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -70,9 +70,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>
@@ -134,11 +132,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'};
     }
 }
@@ -161,7 +159,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 9381b5311842d603e7e6be50c7aea88a955785a9
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 5aecea5..9363bce 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2026,7 +2026,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 3ec75268a9a8d38118175788ce74a6579af4adc9
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 60416cdd8f0b0c350ca9f03c343bad469e433b2a
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 6fb1a93..e8b24db 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -73,6 +73,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"fnord\r\n",
     \$buf,
@@ -115,6 +116,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"clearfnord\r\n",
     \$buf,
@@ -157,6 +159,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"orzzzzzz\r\n",
     \$buf,
@@ -205,6 +208,7 @@ run3(
         '--default-key' => 'rt at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'test',
+        '--no-permission-warning',
     ),
     \"alright\r\n",
     \$buf,
@@ -240,6 +244,7 @@ run3(
         qw(gpg --batch --no-tty --armor --encrypt),
         '--recipient'   => 'random at localhost',
         '--homedir'     => $homedir,
+        '--no-permission-warning',
     ),
     \"should not be there either\r\n",
     \$buf,
@@ -277,6 +282,7 @@ run3(
         qw(gpg --batch --no-tty --armor --encrypt),
         '--recipient'   => 'rt at example.com',
         '--homedir'     => $homedir,
+        '--no-permission-warning',
     ),
     \"really should not be there either\r\n",
     \$buf,

commit bbc61321ff3501b2245b71a2c92ba9f91a33d4eb
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 b5eba24..ed5a5cf 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -615,13 +615,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 294b7d001df0ef365dfb64a92772cefb714f8cc0
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 ed5a5cf..4910840 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -629,7 +629,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 f88832ac7568cdc8d2ee4eb9e9dff71d228d50d6
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 1f9ce2e..7f297a6 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -256,6 +256,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 = @_;
@@ -263,11 +307,12 @@ 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: $!";
 
     my $dbname = $ENV{RT_TEST_PARALLEL}? "rt4test_$port" : "rt4test";
-    print $config qq{
+    print $config_fh qq{
 Set( \$WebDomain, "localhost");
 Set( \$WebPort,   $port);
 Set( \$WebPath,   "");
@@ -275,34 +320,34 @@ Set( \@LexiconLanguages, qw(en zh_TW zh_CN fr ja));
 Set( \$RTAddressRegexp , qr/^bad_re_that_doesnt_match\$/i);
 };
     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";
     }
 
     if ( $args{'plugins'} ) {
-        print $config "Set( \@Plugins, qw(". join( ' ', @{ $args{'plugins'} } ) .") );\n";
+        print $config_fh "Set( \@Plugins, qw(". join( ' ', @{ $args{'plugins'} } ) .") );\n";
     }
 
     if ( $INC{'Devel/Cover.pm'} ) {
-        print $config "Set( \$DevelMode, 0 );\n";
+        print $config_fh "Set( \$DevelMode, 0 );\n";
     }
     elsif ( $ENV{RT_TEST_DEVEL} ) {
-        print $config "Set( \$DevelMode, 1 );\n";
+        print $config_fh "Set( \$DevelMode, 1 );\n";
     }
     else {
-        print $config "Set( \$DevelMode, 0 );\n";
+        print $config_fh "Set( \$DevelMode, 0 );\n";
     }
 
-    $self->bootstrap_logging( $config );
+    $self->bootstrap_logging( $config_fh );
 
     # 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;
 
@@ -315,13 +360,13 @@ Set( \$MailCommand, sub {
 } );
 END
 
-    $self->bootstrap_more_config($config, \%args);
+    $self->bootstrap_more_config($config_fh, \%args);
 
-    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;
 }
@@ -1097,6 +1142,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)) {
@@ -1119,34 +1179,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;
@@ -1272,6 +1317,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;
 

commit 947a1a95627260e8d52b1ced736e7ba8942eb79f
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 d31bbf4..5fec8d8 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->replace(

commit e6618c5bd607baeeba05d3d9687c2b0b9a15a2f9
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 1d5f29a..aeff528 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1368,6 +1368,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
@@ -1379,6 +1383,8 @@ sub Gateway {
         next unless $check_cb->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
         );
 
         $skip_plugin{ $class }++;
@@ -1390,6 +1396,8 @@ sub Gateway {
         my ($status, $msg) = $Code->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
         );
         next if $status > 0;
 
@@ -1440,10 +1448,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 fde6aacfa988a1b10aff9d273d5f80fec907ce94
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 ce1b254fca769aaee4362662463346a0cab6b129
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
+
+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
+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
+
+MiUE2aqlaGHE7c/exUY/SyTWHMvKoDj0hnLDsoKpanCG1kxaPxparIezOCNvuQIW
+FAM2Uyez9NWdDp/yVqpsEeW7QHQSP+tCpBBqAsB6bXYlNTrpdUmZVpbj0ifvYqSq
+cWraapOjYoV9JnAzvusV8KRsOsgneLZ0EWdDV5Yh5V8szF4sCDihXSKumllWIfR5
+NldWueh9nJ+lmQQPz3LE0DJVIXSCizjsVvVo4AzBjLgUTl4Vfm0qbDshOc9k20Fy
++WrMkSMywY5ExyurxY+F9qOoHQHPBb9aGP+bfu40sECTndAXgWfmauRw2E5i8Yke
+wnA+OAu7gUcGGL2tzWfqFAHffSWH6JoyJMFyqQbZtjd6BTLwL+XP6oxvkf01Hzts
+QIDlJyMFQLBdE3G0EpKd3haHBjMaEF66h+yv1OPwd2mQuUb8A5EcwQJGTZ3AVQw6
+2xyXwJbvntHnfe+HBC9XjuJTvgqKU8+3IWNbKSK1zo93M6XcLXYX9xCv258G9oTZ
+P03G3enSdb+aRDr1B9BOhg7MFtcCWlSk8YdEDlqpInM2NrcYzNYEEBGGzeSiURw3
+Af031hMDcLpwqw53zx/gYPgPwamkK/b/wVbOKVv15UTNfVQD094ZBn5MW2xjGrJ2
+h9uRZhhweyTPb34RiRMwvTI1CNxe3WyxjiDY170X2RitQv63TglUG0FSgqCWM7x4
+ZG+wI9QGtanxL2mCcRJDmr6Nvgm4rnTGhf38Ssn08w5vCIDHYss6LXpCWZJ6LI+e
+N6qm56Lu/cnU/7/V2+6fC3rxOLn0P1jrKUOYpP9bewcTHF5VAJN8arAMby1uSjtP
+IpGVyurjt+yD0kgfQQUUPsJ3d3nWzRZ/5vqG3yuUNzmyl7sy32DL4eBxEOFW790S
+dR0dto7tTd2eBliGUOaz3XN/A24Ci4omnz+YojUHIignnjkdn6iEzZOfbZdAN5/0
+R/keO6XHf0P/s9nfKYHORZUvVLQ8FU2HIX1V89JtM6rH3TTseJiXcrXtJb0BKTMV
+5z4S8n2gGI0L9gdXAlru9Tg1VdbQwyM/vCYJzHOQGXTXr15MLG9jf421+jt7t/q4
+Jc6Yc1IMmpCsVrVatVpLP/niRapbxwDN8oa3PEHrOlo+SqGfZ6qSE999ihnZ1obN
+potTKWuK5ht/M4d4vEknngP6HWbB8f42Tp+ZOWQNXsfuKoRIu8rnJaYCdCaw5G2+
+d9wAnlKcl54p4h5EUVK+G1NyUZU9paV5veD88+kfh/MV95amfyGCQPx59vDuyrYZ
+C1ycCUp4lPLg+Qu+Qmnugi12cnvyGTIaXwYIeOa/A5B50chvNdKjkox/0OZPIsYX
+ZZ2A76I5UC4JUuJx56qNWXi55Aa6YDMb99vHmvmptjeMqSKlJBRhXiVoWyljj8SW
+a7XZCy/EPvuBJvhTFB0JA9yXRaWqfpozleualDnBoxmJXjYP6keaHPHk5IPC8VCR
+yEHSqSGJCEODJdDNe7dFabXhLIDI1EFHTr+hDNNv0iQiJlTj6jMgWeUNfDxoFw7z
+mMr2jMoDZ761W0FqB64oseyS9tePloKUcfUGL0e2o2JWukX218094Htbxz/wR+12
+IG8kn1eH2uoLi823VTKTrg3mZjPSFuDrX+lh4+PU9FgEwkbp373G+uyAqDXZRMdV
+50RmCWkPSO1BpNZjMNqsgFSek4f+KWF9Zh90e3NQNavyt52qECw1qbwURzgZ/yah
+b6SAnlvoZe50hxoIbqORbWG29PER/mfL3jggFqDssRcHonAOKNIyPOSK4U+mpkVa
+1uDeu1JzVu8NIikL6pbYVwlFnR+oZRDU97DUobiEnikWNNCB/O/L1atpnX/Vcu2D
+BqI4tTJKmAP7+aLnQ5XuvaeHjojg3yS8c5n39tlPir49IXOsxILX3H7kFNxn/44T
+WiiIfoTHM8gWhamaMDJ6pQP8Md1vPvCGg0D0T+thrXSMTWKHbXRTj4S2OHFqRYXt
+7cz8PEaS/h68qaei2E54LcQ7nmx/uFLOzU1lS4egpdlypT2Lu9HekybGkqm5/KpG
+Nb4LcrbaKfaYeUQYy9H4hk9Y/hkDSXzvH/7y/26S1QA0rFCFKJswPUxjTzqpwGRZ
+rPU6jbq42F+Zkk/0Nt87OhdKfILbbO1zAUlLnYKmram+lDX9Z3/bZVQ57mAGvRiP
+fdQGp5AkzxsHh84sdcoF4hlGnB15JkLL8+b+rs9lnhhD4f025mPgakWbBuhwh/Q/
+thZpkQTFVPs53X7TuVAcwpJxQZn0Z66YMhPIWUqNRvZCbqIuhr6i3A+zX8msiYkQ
+0FRlO2luY5UpURUJ3yHbCPCQsCklJKTNVM1eP8mKi0F+WHu79uuoda7bKPGBK3Qu
+hnfZ4hxI1tl4bpPwO5yEvU9nQXJrro1PabbXD4pjk3yaZZWAMKFnCQMdb/mxiiml
+F2TxWwd3O4lmxtCQqY7EtnGhytq/nqlImqPlHrZmqOdqIXF7IzsKOViOsx/OpBEI
+MCJBI0k1/2zrd64qCuXdYpElycKfJOFVwNER77vwRfXXK89T4PJ4DzddtuXAjT7a
+mjdCSaG7ePsYDdWViOObxcZSJtvysatQ6noPurDrNAqRq0dUHIVZSCj26AQ5L/wY
+uGD1yOUVIy7LNK5RRFStONk06eJ4V4qb2xXhMOLDaFZDbJkaFXpUtVMN5KZm6c+/
+4x/Cz6cdBy2M4unvSmer3oLg5b+k6eroBaNEDWKUaABjGeLlK1DgGb43MrdhIfp3
+TqMtWyx/n3WWcrmcYWi3kAH+sUV9BzZvJUINGUPiHeOO4ic1sqRiUHKulle3o9wN
+eilMg5LNi4S+tNsfbWdO59aWLr1Ub//f39GsSJZVpww9csZFg1HrNyI3+9EOE4M+
+fufyifVWg1Jf52xwk4nUoM2ue5fs17AqSy6ePcnTYucoxciq+D80BVzlnEVATh6h
+o2IXU3e2eKJ2BxsUQ+ffIQge9OazfuKk5NeXHZDvQqWFeVh4VJ/ztbK8ew89KXWa
+NRChgYmQ/943udG8tdtC+6PDpjwThiBedzH7bD2t1lSuEzcg/BUueNwzRiH14oqW
+YbHykyu0wmd3Q+wPdy+b1uBSMmvPen4k8Z9zlxM5UnjjDiTYVMd/Em6EtBJw33Fe
+-----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
+
+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
+-----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-----
+MIIGkDCCBHigAwIBAgIBAzANBgkqhkiG9w0BAQQFADCBhzELMAkGA1UEBhMCUlUx
+EzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVyc2J1cmcxDTAL
+BgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRlc3RlcjEcMBoG
+CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wMzAyMTExNTU4MjlaFw0wNzAy
+MTAxNTU4MjlaMHgxCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRlMQ0w
+CwYDVQQKEwRUZXN0MRQwEgYDVQQLEwtTZW5kZXIgdW5pdDEPMA0GA1UEAxMGU2Vi
+ZGVyMR4wHAYJKoZIhvcNAQkBFg9zZW5kZXJAdGVzdC5jb20wggIiMA0GCSqGSIb3
+DQEBAQUAA4ICDwAwggIKAoICAQCXbJOzfVLNRcCJGqjHFDFKY4O9SftHAgTeU2Dr
+tgp8ZDVWr3euNPc1vppZaljmQjXpj04FY67NXTsRVEKncNlJGnxDSrAlKOyv05Rc
+G+w2CG0whdi7s+ods9UM8ir4o5fR3mOvlfHD89z3pOS5hejAmSOA1RjJ7iP1XIQ0
+HSZ/9CWxXJXiI6uYutmBKAqbAAOzWgDPtP8tBFpg2r7eZRJoNyf+GBuXaV+v/7Me
+6LFzQnJ6tyTFTm/mMSxWBHN0aun/Q0yQ6OYbSSmg63Bqm0STk9YtciZXjbbR+qGi
+lu/KGrWZm1MwMHacjHDiWXMLVH5OctJ7lIW//4d21h6gBLawFOFlKLS51j8RpQKO
+R4uLpHZlkoWCe5j1HY/x2ARwZANZBroJVS0TiuRk3XAsbzNcJJdJGeGdPBuAtYqQ
+24Vdpa6X0jaq0A9Bgzq3k0ng9liDot0a6Y9/bbCfjaAi5uytzWFi7SzIX7ZHK5EU
+1x7L6wRZdTQ1mAMX38KuaQ9Ok6eOAuMgTtiM9vbpw05KO0WQomBnabBXIrajc1l3
+ibwHExLM9aU0sAxTLnrlcJRFKqFjqNWpRaqHZ/Ru+TS/rV+zpzEt/cTwNV1sFEIX
+1IfpWYZTtq8ASk7Hlw2hqqOA3CfOuWyTZUULFA+HrM+ji5twOxBl9jRcnP1GKRs0
+Q/gc5wIDAQABo4IBEzCCAQ8wCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3Bl
+blNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFMYJ80iXKvBo4CF/
+GgoWMsOLkxsVMIG0BgNVHSMEgawwgamAFJM2YO7eLbLS0pKj+rBi7Dd+fizyoYGN
+pIGKMIGHMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEWMBQGA1UE
+BxMNU3QtUGV0ZXJzYnVyZzENMAsGA1UEChMEVGVzdDENMAsGA1UECxMEdGVzdDEP
+MA0GA1UEAxMGVGVzdGVyMRwwGgYJKoZIhvcNAQkBFg10ZXN0QHRlc3QuY29tggEA
+MA0GCSqGSIb3DQEBBAUAA4ICAQBGP0xoSLBztmRJDUob9sVu6WZYfLjRcbXggoJ4
+ZExoeU1UOOZykbp9YNF82VYp2AANALzWc2yLX152/8FpIDEkmMR4n/xzrHGdQNMr
+6bYtSgnAJ3qmGFaQ+afhKM21zySNsCDqXRGQadnXgWqhYCjm0qPqmgQ8ND9X5OTT
+vc4sJowkAU/Ebk3IxIsp2oxmcB9keh9Dw5mR8PLqo7DRyiaitEVw0/b4EIVGiLtG
+aYNmhtWOaOTedWTkAQIy/fmj5Lkg5MP2xUoARApOtHY1jv2kjWoTjVJ+bbmEXmB9
+iihmRwPLx/Ug//MRVAN63l7/+OCyGJCBXlVjZtnEf1JuAy3t9IfXCBwq+I1xo2lQ
+yviS8/R9kMCgKCLkwM3U/duS/jrPQMmtr0LpFfBcYbfOYLuT53+gUG+ZEZClnn2r
+MvOYpAg+AAEtcb9+/SBD14NVWcuJKstDmn/KbUe0e7YN507/Y1KjEH3/lYxUmUNw
+l0nzozj3cULB6ji9pizh3At8Y/Ohn/vVMwwNNmVWi6NsRJXKiIx6NKi2zOocPf71
+/CPHujPaI4el7HlzGMPY7UuoXy1rAlf9lULjS15AqqKhMp/xUBQMypCHKuk3pobM
+eeqDGtjRpnu04Z9RywMFKGK4LNDLc6sy9z9+Lb0uCWL1nAx9Njs3eJgmS9E+e7hL
+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 4d01075b259bcaa40b5ed71f3ffe74248fe9a9eb
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 e67e458c83c17db1a5c004a65d2029ceebbf17a8
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 3fb84439410452e88019315ad9504f362e5325c6
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 3c989c85e7056fe54aa2ab9ac72252bc7690895b
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 7f297a6..2958221 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -304,9 +304,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: $!";
@@ -318,6 +315,8 @@ Set( \$WebPort,   $port);
 Set( \$WebPath,   "");
 Set( \@LexiconLanguages, qw(en zh_TW zh_CN fr ja));
 Set( \$RTAddressRegexp , qr/^bad_re_that_doesnt_match\$/i);
+Set( \$LogDir,     '$tmp{directory}');
+Set( \$LogToFile , "debug");
 };
     if ( $ENV{'RT_TEST_DB_SID'} ) { # oracle case
         print $config_fh "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n";

commit d59fca374d1914904642da252efe211bd7a6d582
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 210bde49638c825d6889c0de411e784da0bf73a1
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..e38b6db 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 9363bce..94cd5b2 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2030,72 +2030,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 8c55eb076cb616349f0304ad1c1ba933425ac57b
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 94cd5b2..bbe8021 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2004,8 +2004,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 67dc7a724cb79587f8dfd45c3e2c10529e84cd70
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..c10515d 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 bbe8021..4cef2bc 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2126,7 +2126,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;
         }
@@ -2139,7 +2139,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;
         }
@@ -2147,7 +2147,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;
         }
@@ -2225,22 +2225,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 f65b5b6b4cd667edb24cb5be9edaa8e188163d70
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 831ed63..6912990 100644
--- a/share/html/Admin/Elements/ShowKeyInfo
+++ b/share/html/Admin/Elements/ShowKeyInfo
@@ -73,10 +73,12 @@
 <td><% $res{'info'}{'Expire'}? $res{'info'}{'Expire'}->AsString( Time => 0 ): loc('never') %></td></tr>
 
 % foreach my $uinfo( @{ $res{'info'}{'User'} } ) {
-<tr><th class="label"><% loc('User (created - expire)') %>:</th>
+<tr><th class="label"><% 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 93f26f95502668daa97f7887fe9cdb11c3ee4d98
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 c6b4291c0e1e683015c9a7f6089484bf468609d5
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 aeff528..75381bc 100644
--- 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 e83ae09d2cbec376d4d2dd1f25f4c10f2e310e27
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 e38b6db..ac5e63e 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 613debe..4a29bb8 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -165,7 +165,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 242cbda..54c87dd 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 c90d6ff305a9e3427cfaf03db1c3279f7415af42
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 e8b24db..ed32e43 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -190,7 +190,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 834014c..1609cff 100644
--- a/t/mail/gnupg-realmail.t
+++ b/t/mail/gnupg-realmail.t
@@ -62,7 +62,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 c739b1b..4303eaa 100644
--- a/t/web/crypt-gnupg.t
+++ b/t/web/crypt-gnupg.t
@@ -99,7 +99,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'),
@@ -167,7 +167,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'),
@@ -239,7 +239,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'),
@@ -305,7 +305,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'),

commit 2824aca71fd4c4441534d5a6886841c5d4667df4
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 081f6598966a752895756cf76c076c05955108a6
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 c10515d..d82f21e 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 4cef2bc..b975b47 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2326,31 +2326,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 @@ sub _make_gpg_handles {
 
 RT::Base->_ImportOverlays();
 
+1;
+
 # helper package to avoid using temp file
 package IO::Handle::CRLF;
 

commit d51139e65230edef439dc466900ba32cefd35d7b
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 06d272bbd9010703e9ffd981b93ac334cc9cdce6
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',

commit b44474033d6a13959d4fcb55c7856f18f4801098
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 5c60c2382e5e8a7eabf3546047afc7f30d9c4e4d
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
+
+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
+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 fe76a278fcf3e0cf8f77e5051c0b52b1ca6321b4
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 549ca36b36fbbbffd65a5dbe6dded1e2bb453b2b
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 ac5e63e..f77c911 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 b975b47..e36c8cd 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2396,16 +2396,3 @@ sub _make_gpg_handles {
 RT::Base->_ImportOverlays();
 
 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 b2e4b8b6c0c68866b75adf542f389e2c181f33b8
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 7bc91d916c0c8c59f4501ffe161b7e69d2d76a7f
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 d77a30ed45f679980068518eeec2923dc825863d
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 c5440c2cd6081f2a21e9558f707207308ce32250
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 fe5392db9827d2cdaa3abe342072014131adb044
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 da01b91b7b857abe53a60c987a1285ca67709e7e
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 5fec8d8..deb84e0 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 74ea2e32a6a5556d4b4d8e97f97e1faa12226862
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 dcabbd0beeb4c8f035fae4b7cd24ce363b7f4a16
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 0bdf6106856b8c4b41d2dc28ffb63371c00954ef
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 5863257061b2dca6cb49e4eccf567c698e61d20d
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 a9f481f830f31957434888c4ff6e9998d0bc70d0
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 fbc571fa630a8c2d1255fdca5ad2c831b7b295e7
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/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 82a9d8f633e327be8fadb98c481eb917a4d4c092
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 3dde7eb2b2f8780d472312a9b7b747ca95bdad69
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 f53321446c7b729c485e7b76b7bcc32cf0436959
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 f77c911..984d784 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 7d6f8af9d77b2a8e8d329c923a27418039f5aeac
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 03f58215fbfbc825c884a20715376afdcdbc310e
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 2c98a57e9e46d9acf9785e03bdb07336a0096053
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Feb 16 04:19:20 2010 +0300

    upgrade script

diff --git a/etc/upgrade/3.8.8/content b/etc/upgrade/3.8.8/content
index cad77e9..e7748e7 100644
--- a/etc/upgrade/3.8.8/content
+++ b/etc/upgrade/3.8.8/content
@@ -34,5 +34,36 @@
             $prev_type = $lt;
         }
     },
+    sub {
+        my $cf = RT::CustomField->new( $RT::SystemUser );
+        $cf->LoadByCols(
+            Name       => 'PublicKey',
+            LookupType => RT::User->new( $RT::SystemUser )->CustomFieldLookupType,
+        );
+        unless ( $cf->id ) {
+            $RT::Logger->info("You don't have PublicKey. Nothing to do.");
+            return 1;
+        }
+        my ($status, $msg) = $cf->SetName('SMIME Key');
+        unless ( $status ) {
+            $RT::Logger->error("Couldn't rename custom field: $msg");
+        } else {
+            $RT::Logger->info("Renamed custom field: $msg");
+        }
+        return 1;
+    },
+    sub {
+        $RT::Logger->info("Going to delete all SMIMEKeyNotAfter attributes");
+        my $attrs = RT::Attributes->new( $RT::SystemUser );
+        $attrs->Limit( FIELD => 'ObjectType', VALUE => 'RT::User' );
+        $attrs->Limit( FIELD => 'Name', VALUE => 'SMIMEKeyNotAfter' );
+        while ( my $attr = $attrs->Next ) {
+            my ($status, $msg) = $attr->Delete;
+            unless ( $status ) {
+                $RT::Logger->error("Couldn't delete attribute: $msg");
+            }
+        }
+        return 1;
+    },
 );
 

commit 221cebcdce7b2eb9d90727e0b7740e50048dc2e1
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 ecd426a9cf0a9d8ccc948ab8260bb61268fbd7ab
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 984d784..3d0c6ee 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 4a5118fd698d306e742d9a1d925e575ec700953b
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 3d0c6ee..f3f6503 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 e36c8cd..5688a4b 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 717eb4e15ccb3262ab60cf25f7b1fc0917ad7f7b
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 ea12648..ec35027 100755
--- a/configure.ac
+++ b/configure.ac
@@ -309,7 +309,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 0d4b91376b04cfdba78694814c589dd463edd4d6
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 68913899dd1f1c7e4f2eb346f217070b181bad84
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 73b40538d5212b219fb0b6e40feec75d3d870078
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 e3c717f..fe8e931 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1962,9 +1962,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 1920139fce1b8a9a10deab9dd2cdb89c5fd8ccc1
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 fe8e931..3fdacab 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -1986,8 +1986,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 9cc32d43a7d97276e86f0b06831b4cba7397c042
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 18fcd32b8d4b3fe706635070d5a85206fac42c03
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 8e2b837..413a312 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 4a29bb8..a84ae7f 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -47,6 +47,7 @@
 %# END BPS TAGGED BLOCK }}}
 <table><tr>
 % my $columnsplit = "</td><td>";
+% if ( RT->Config->Get('Crypt')->{'Outgoing'} eq 'GnuPG' ) {
 <td><% loc( 'Sign[_1][_2] using [_3]',
     $columnsplit,
     $m->scomp('/Widgets/Form/Boolean:InputOnly',
@@ -54,6 +55,14 @@
     ),
     $m->scomp('SelectKeyForSigning', User => $session{'CurrentUser'}->UserObj ),
 ) |n %></td>
+% } else {
+<td><% loc( 'Sign[_1][_2]',
+    $columnsplit,
+    $m->scomp('/Widgets/Form/Boolean:InputOnly',
+        Name => 'Sign', CurrentValue => $self->{'Sign'}
+    ),
+) |n %></td>
+% }
 
 <td><% loc('Encrypt')%></td>
 <td><& /Widgets/Form/Boolean:InputOnly, Name => 'Encrypt', CurrentValue => $self->{'Encrypt'} &></td>

commit 354eeab53218195d124639d7c537ce4b4df87ea7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 2 02:04:25 2011 +0400

    variable name typo

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 5688a4b..611a319 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1038,7 +1038,7 @@ sub FindScatteredParts {
         my $type = $self->_CheckIfProtectedInline( $part );
         next unless $type;
 
-        my $file = ($entity->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
+        my $file = ($part->head->recommended_filename||'') =~ /\.${RE_FILE_EXTENSIONS}$/;
 
         $args{'Skip'}{$part} = 1;
         push @res, {

commit afa424c215a8f234a02d1c2c849312188756641f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 2 02:07:27 2011 +0400

    use Auth::Crypt mail plugin in tests
    
    help avoid warnings from config checker

diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
index 56e748b..662940d 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -104,7 +104,7 @@ Set(\%GnuPG, (
     OutgoingMessagesFormat => 'RFC',
 ));
 Set(\%GnuPGOptions => \%{ $dumped_gnupg_options });
-Set(\@MailPlugins => qw(Auth::MailFrom Auth::GnuPG));
+Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
 };
 
 }

commit c2bce899cacc9f2e6a68330c26dc8db7e568b3d7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 2 02:09:00 2011 +0400

    privacy header was changed to match protocol
    
    so it's GnuPG, not PGP

diff --git a/lib/RT/Test/GnuPG.pm b/lib/RT/Test/GnuPG.pm
index 662940d..ce2fb24 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -273,7 +273,7 @@ sub send_email_and_check_transaction {
           "RT's outgoing mail looks not signed";
     }
     elsif ( $type eq 'signed' ) {
-        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";
@@ -282,7 +282,7 @@ sub send_email_and_check_transaction {
           "RT's outgoing mail looks signed";
     }
     elsif ( $type eq 'encrypted' ) {
-        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";
@@ -291,7 +291,7 @@ sub send_email_and_check_transaction {
 
     }
     elsif ( $type eq 'signed_encrypted' ) {
-        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 f9b3d4439994dd596014867cb0540533a8bbe195
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Apr 26 15:25:26 2010 +0400

    some programs use x-pkcs7-... and some use just pkcs7-...

diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index 709d902..4fce381 100644
--- a/t/web/smime/outgoing.t
+++ b/t/web/smime/outgoing.t
@@ -370,9 +370,9 @@ sub check_text_emails {
         }
 
         if ( $args{'Encrypt'} ) {
-            like $mail, qr/application\/x-pkcs7-mime/, 'outgoing email was processed';
+            like $mail, qr/application\/(?:x-)?pkcs7-mime/, 'outgoing email was processed';
         } elsif ( $args{'Sign'} ) {
-            like $mail, qr/x-pkcs7-signature/, 'outgoing email was processed';
+            like $mail, qr/(?:x-)?pkcs7-signature/, 'outgoing email was processed';
         } else {
             unlike $mail, qr/smime/, 'outgoing email was not processed';
         }

commit adccbd695104c87fd177dc4134ecb3c624a08f0a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Dec 2 18:54:02 2011 +0400

    todo file for SMIME integration

diff --git a/TODO.SMIME b/TODO.SMIME
new file mode 100644
index 0000000..bfb7c0a
--- /dev/null
+++ b/TODO.SMIME
@@ -0,0 +1,17 @@
+* fill basics on SMIME in lib/RT/Interface/Email/Auth/Crypt
+
+* port RT::Attachment::{Encrypt,Decrypt} over new API with SMIME support
+
+* continue with share//html/Ticket/Elements/ShowGnuPGStatus
+
+* work harder on re-verification
+
+* handle bad passphrase in decrypt by parsing error
+** there is no way to handle bad passphrase, error just
+   says that decryption failed
+
+* figure out what happened with t/web/gnupg-outgoing.t
+  and restore all changes from SMIME branch that changed
+  this file
+
+* move SMIME upgrade steps from etc/upgrade/3.8.8/content

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


More information about the Rt-commit mailing list