[Rt-commit] rt branch, 4.2-on-4.0/smime, created. rt-4.0.9-186-gea5c7c3

Alex Vandiver alexmv at bestpractical.com
Mon Apr 8 15:11:14 EDT 2013


The branch, 4.2-on-4.0/smime has been created
        at  ea5c7c3a679de19a35a7fea3dec7800182dc2ca1 (commit)

- Log -----------------------------------------------------------------
commit 1447d8a435d2d37bb2f3271a39134ae397bd0227
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Aug 31 14:31:52 2012 -0400

    Refactor code which calls GPG::Interface to elimate duplicate code
    
    Many of the functions in RT::Crypt::GnuPG contain nearly-duplicate
    swaths of code.  Refactor these into a common CallGnuPG method, which
    deals with setting up IO handles, printing content, trapping errors,
    reading output, and returning results in the form of a data structure.

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 6164a42..fdc8d1a 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -364,6 +364,107 @@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
 #            ...
 #        );
 
+=head2 CallGnuPG
+
+=cut
+
+sub CallGnuPG {
+    my %args = (
+        Options     => undef,
+        Key         => undef,
+        Recipients  => [],
+        Passphrase  => undef,
+
+        Method      => undef,
+        CommandArgs => [],
+
+        Content     => undef,
+        Handles     => {},
+        Direct      => undef,
+        Output      => undef,
+        @_
+    );
+
+    my %handle = %{$args{Handles}};
+    my ($handles, $handle_list) = _make_gpg_handles( %handle );
+    $handles->options( $_ )->{'direct'} = 1
+        for @{$args{Direct} || [keys %handle] };
+    %handle = %$handle_list;
+
+    my $content = $args{Content};
+    my $method  = $args{Method};
+
+    my %GnuPGOptions = RT->Config->Get('GnuPGOptions');
+    my %opt = (
+        'digest-algo' => 'SHA1',
+        %GnuPGOptions,
+        %{ $args{Options} || {} },
+    );
+    my $gnupg = GnuPG::Interface->new;
+    $gnupg->options->hash_init(
+        _PrepareGnuPGOptions( %opt ),
+    );
+    $gnupg->options->armor( 1 );
+    $gnupg->options->meta_interactive( 0 );
+    $gnupg->options->default_key( $args{Key} );
+
+    my %seen;
+    $gnupg->options->push_recipients( $_ ) for
+        map { UseKeyForEncryption($_) || $_ }
+        grep { !$seen{ $_ }++ }
+            @{ $args{Recipients} || [] };
+
+    $args{Passphrase} = $GnuPGOptions{passphrase}
+        unless defined $args{'Passphrase'};
+    $args{Passphrase} = GetPassphrase( Address => $args{Key} )
+        unless defined $args{'Passphrase'};
+    $gnupg->passphrase( $args{'Passphrase'} );
+
+    eval {
+        local $SIG{'CHLD'} = 'DEFAULT';
+        my $pid = safe_run_child {
+            $gnupg->$method(
+                handles      => $handles,
+                command_args => $args{CommandArgs},
+            )
+        };
+        {
+            local $SIG{'PIPE'} = 'IGNORE';
+            if (Scalar::Util::blessed($content) and $content->can("print")) {
+                $content->print( $handle{'stdin'} );
+            } elsif (ref($content) eq "SCALAR") {
+                $handle{'stdin'}->print( ${ $content } );
+            } elsif (defined $content) {
+                $handle{'stdin'}->print( $content );
+            }
+            close $handle{'stdin'} or die "Can't close gnupg handle: $!";
+        }
+        waitpid $pid, 0;
+    };
+    my $err = $@;
+    if ($args{Output}) {
+        push @{$args{Output}}, readline $handle{stdout};
+        close $handle{stdout};
+    }
+
+    my %res;
+    $res{'exit_code'} = $?;
+
+    foreach ( qw(stderr logger status) ) {
+        $res{$_} = do { local $/ = undef; readline $handle{$_} };
+        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+        close $handle{$_};
+    }
+    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    if ( $err || $res{'exit_code'} ) {
+        $res{'message'} = $err? $err : "gpg exited with error code ". ($res{'exit_code'} >> 8);
+    }
+
+    return %res;
+}
+
 =head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ]
 
 Signs and/or encrypts an email message with GnuPG utility.
@@ -435,28 +536,7 @@ sub SignEncryptRFC3156 {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnuPGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined $args{'Passphrase'};
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
     my $entity = $args{'Entity'};
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
-
     my %res;
     if ( $args{'Sign'} && !$args{'Encrypt'} ) {
         # required by RFC3156(Ch. 5) and RFC1847(Ch. 2.1)
@@ -468,46 +548,26 @@ sub SignEncryptRFC3156 {
                 );
             }
         }
-
-        my ($handles, $handle_list) = _make_gpg_handles(stdin =>IO::Handle::CRLF->new );
-        my %handle = %$handle_list;
-
-        $gnupg->passphrase( $args{'Passphrase'} );
-
-        eval {
-            local $SIG{'CHLD'} = 'DEFAULT';
-            my $pid = safe_run_child { $gnupg->detach_sign( handles => $handles ) };
-            $entity->make_multipart( 'mixed', Force => 1 );
-            {
-                local $SIG{'PIPE'} = 'IGNORE';
-                $entity->parts(0)->print( $handle{'stdin'} );
-                close $handle{'stdin'};
-            }
-            waitpid $pid, 0;
-        };
-        my $err = $@;
-        my @signature = readline $handle{'stdout'};
-        close $handle{'stdout'};
-
-        $res{'exit_code'} = $?;
-        foreach ( qw(stderr logger status) ) {
-            $res{$_} = do { local $/; readline $handle{$_} };
-            delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-            close $handle{$_};
-        }
-        $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-        $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-        $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-        if ( $err || $res{'exit_code'} ) {
-            $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-            return %res;
-        }
+        $entity->make_multipart( 'mixed', Force => 1 );
+
+        my @signature;
+        %res = CallGnuPG(
+            Key        => $args{'Signer'},
+            Method     => "detach_sign",
+            Handles    => { stdin => IO::Handle::CRLF->new },
+            Direct     => [],
+            Passphrase => $args{'Passphrase'},
+            Content    => $entity->parts(0),
+            Output     => \@signature,
+        );
+        return %res if $res{message};
 
         # setup RFC1847(Ch.2.1) requirements
         my $protocol = 'application/pgp-signature';
+        my $algo = RT->Config->Get('GnuPGOptions')->{'digest-algo'} || 'SHA1';
         $entity->head->mime_attr( 'Content-Type' => 'multipart/signed' );
         $entity->head->mime_attr( 'Content-Type.protocol' => $protocol );
-        $entity->head->mime_attr( 'Content-Type.micalg'   => 'pgp-'. lc $opt{'digest-algo'} );
+        $entity->head->mime_attr( 'Content-Type.micalg'   => 'pgp-'. lc $algo );
         $entity->attach(
             Type        => $protocol,
             Disposition => 'inline',
@@ -516,48 +576,23 @@ sub SignEncryptRFC3156 {
         );
     }
     if ( $args{'Encrypt'} ) {
-        my %seen;
-        $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
-            grep !$seen{ $_ }++, map $_->address,
+        my @recipients = map $_->address,
             map Email::Address->parse( $entity->head->get( $_ ) ),
             qw(To Cc Bcc);
 
         my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
         binmode $tmp_fh, ':raw';
 
-        my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-        my %handle = %$handle_list;
-        $handles->options( 'stdout'  )->{'direct'} = 1;
-        $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
-        eval {
-            local $SIG{'CHLD'} = 'DEFAULT';
-            my $pid = safe_run_child { $args{'Sign'}
-                ? $gnupg->sign_and_encrypt( handles => $handles )
-                : $gnupg->encrypt( handles => $handles ) };
-            $entity->make_multipart( 'mixed', Force => 1 );
-            {
-                local $SIG{'PIPE'} = 'IGNORE';
-                $entity->parts(0)->print( $handle{'stdin'} );
-                close $handle{'stdin'};
-            }
-            waitpid $pid, 0;
-        };
-
-        $res{'exit_code'} = $?;
-        foreach ( qw(stderr logger status) ) {
-            $res{$_} = do { local $/; readline $handle{$_} };
-            delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-            close $handle{$_};
-        }
-        $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-        $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-        $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exited with error code ". ($? >> 8);
-            return %res;
-        }
+        $entity->make_multipart( 'mixed', Force => 1 );
+        %res = CallGnuPG(
+            Key        => $args{'Signer'},
+            Recipients => \@recipients,
+            Method     => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
+            Handles    => { stdout => $tmp_fh },
+            Passphrase => $args{'Passphrase'},
+            Content    => $entity->parts(0),
+        );
+        return %res if $res{message};
 
         my $protocol = 'application/pgp-encrypted';
         $entity->parts([]);
@@ -617,72 +652,23 @@ sub _SignEncryptTextInline {
     );
     return unless $args{'Sign'} || $args{'Encrypt'};
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
-
-    if ( $args{'Encrypt'} ) {
-        $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
-            @{ $args{'Recipients'} || [] };
-    }
-
-    my %res;
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-
-    $handles->options( 'stdout'  )->{'direct'} = 1;
-    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
     my $entity = $args{'Entity'};
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $args{'Sign'} && $args{'Encrypt'}
-            ? 'sign_and_encrypt'
-            : ($args{'Sign'}? 'clearsign': 'encrypt');
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $entity->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    my $err = $@;
-
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-        return %res;
-    }
+    my %res = CallGnuPG(
+        Key        => $args{'Signer'},
+        Recipients => $args{'Recipients'},
+        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+                      ? 'sign_and_encrypt'
+                      : ( $args{'Sign'}
+                        ? 'clearsign'
+                        : 'encrypt' ) ),
+        Handles    => { stdout => $tmp_fh },
+        Passphrase => $args{'Passphrase'},
+        Content    => $entity->bodyhandle,
+    );
+    return %res if $res{message};
 
     $entity->bodyhandle( MIME::Body::File->new( $tmp_fn) );
     $entity->{'__store_tmp_handle_to_avoid_early_cleanup'} = $tmp_fh;
@@ -705,71 +691,25 @@ sub _SignEncryptAttachmentInline {
     );
     return unless $args{'Sign'} || $args{'Encrypt'};
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
 
     my $entity = $args{'Entity'};
-    if ( $args{'Encrypt'} ) {
-        $gnupg->options->push_recipients( $_ ) foreach
-            map UseKeyForEncryption($_) || $_,
-            @{ $args{'Recipients'} || [] };
-    }
-
-    my %res;
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout'  )->{'direct'} = 1;
-    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $args{'Sign'} && $args{'Encrypt'}
-            ? 'sign_and_encrypt'
-            : ($args{'Sign'}? 'detach_sign': 'encrypt');
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $entity->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    my $err = $@;
-
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-        return %res;
-    }
+    my %res = CallGnuPG(
+        Key        => $args{'Signer'},
+        Recipients => $args{'Recipients'},
+        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+                      ? 'sign_and_encrypt'
+                      : ( $args{'Sign'}
+                        ? 'detach_sign'
+                        : 'encrypt' ) ),
+        Handles    => { stdout => $tmp_fh },
+        Passphrase => $args{'Passphrase'},
+        Content    => $entity->bodyhandle,
+    );
+    return %res if $res{message};
 
     my $filename = mime_recommended_filename( $entity ) || 'no_name';
     if ( $args{'Sign'} && !$args{'Encrypt'} ) {
@@ -807,70 +747,22 @@ sub SignEncryptContent {
     );
     return unless $args{'Sign'} || $args{'Encrypt'};
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'default_key'} = $args{'Signer'}
-        if $args{'Sign'} && $args{'Signer'};
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
-    );
-
-    if ( $args{'Sign'} && !defined $args{'Passphrase'} ) {
-        $args{'Passphrase'} = GetPassphrase( Address => $args{'Signer'} );
-    }
-
-    if ( $args{'Encrypt'} ) {
-        $gnupg->options->push_recipients( $_ ) foreach 
-            map UseKeyForEncryption($_) || $_,
-            @{ $args{'Recipients'} || [] };
-    }
-
-    my %res;
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout'  )->{'direct'} = 1;
-    $gnupg->passphrase( $args{'Passphrase'} ) if $args{'Sign'};
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $args{'Sign'} && $args{'Encrypt'}
-            ? 'sign_and_encrypt'
-            : ($args{'Sign'}? 'clearsign': 'encrypt');
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $handle{'stdin'}->print( ${ $args{'Content'} } );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    my $err = $@;
-
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-        return %res;
-    }
+    my %res = CallGnuPG(
+        Key        => $args{'Signer'},
+        Recipients => $args{'Recipients'},
+        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+                      ? 'sign_and_encrypt'
+                      : ( $args{'Sign'}
+                        ? 'clearsign'
+                        : 'encrypt' ) ),
+        Handles    => { stdout => $tmp_fh },
+        Passphrase => $args{'Passphrase'},
+        Content    => $args{'Content'},
+    );
+    return %res if $res{message};
 
     ${ $args{'Content'} } = '';
     seek $tmp_fh, 0, 0;
@@ -1131,14 +1023,6 @@ sub VerifyInline { return DecryptInline( @_ ) }
 sub VerifyAttachment {
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     foreach ( $args{'Data'}, $args{'Signature'} ) {
         next unless $_->bodyhandle->is_encoded;
 
@@ -1151,82 +1035,28 @@ sub VerifyAttachment {
     $args{'Data'}->bodyhandle->print( $tmp_fh );
     $tmp_fh->flush;
 
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->verify(
-            handles => $handles, command_args => [ '-', $tmp_fn ]
-        ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $args{'Signature'}->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $@ || $? ) {
-        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-    }
-    return %res;
+    return CallGnuPG(
+        Method      => "verify",
+        CommandArgs => [ '-', $tmp_fn ],
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Signature'}->bodyhandle,
+    );
 }
 
 sub VerifyRFC3156 {
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw:eol(CRLF?)';
     $args{'Data'}->print( $tmp_fh );
     $tmp_fh->flush;
 
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->verify(
-            handles => $handles, command_args => [ '-', $tmp_fn ]
-        ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $args{'Signature'}->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'};
-        }
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $@ || $? ) {
-        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-    }
-    return %res;
+    return CallGnuPG(
+        Method      => "verify",
+        CommandArgs => [ '-', $tmp_fn ],
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Signature'}->bodyhandle,
+    );
 }
 
 sub DecryptRFC3156 {
@@ -1238,66 +1068,27 @@ sub DecryptRFC3156 {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     if ( $args{'Data'}->bodyhandle->is_encoded ) {
         require RT::EmailParser;
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout' )->{'direct'} = 1;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        $gnupg->passphrase( $args{'Passphrase'} );
-        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            $args{'Data'}->bodyhandle->print( $handle{'stdin'} );
-            close $handle{'stdin'}
-        }
-
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    my %res = CallGnuPG(
+        Method      => "decrypt",
+        Handles     => { stdout => $tmp_fh },
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Data'}->bodyhandle,
+    );
 
     # if the decryption is fine but the signature is bad, then without this
     # status check we lose the decrypted text
     # XXX: add argument to the function to control this check
-    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-            return %res;
-        }
-    }
+    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
+
+    return %res if $res{message};
 
     seek $tmp_fh, 0, 0;
     my $parser = RT::EmailParser->new();
@@ -1316,27 +1107,11 @@ sub DecryptInline {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnuPGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     if ( $args{'Data'}->bodyhandle->is_encoded ) {
         require RT::EmailParser;
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
@@ -1362,7 +1137,6 @@ sub DecryptInline {
             my ($res_fh, $res_fn);
             ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
                 %args,
-                GnuPG => $gnupg,
                 BlockHandle => $block_fh,
             );
             return %res unless $res_fh;
@@ -1399,7 +1173,6 @@ sub DecryptInline {
         my ($res_fh, $res_fn);
         ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
             %args,
-            GnuPG => $gnupg,
             BlockHandle => $block_fh,
         );
         return %res unless $res_fh;
@@ -1419,49 +1192,26 @@ sub DecryptInline {
 
 sub _DecryptInlineBlock {
     my %args = (
-        GnuPG => undef,
         BlockHandle => undef,
         Passphrase => undef,
         @_
     );
-    my $gnupg = $args{'GnuPG'};
 
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(
-            stdin => $args{'BlockHandle'}, 
-            stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout' )->{'direct'} = 1;
-    $handles->options( 'stdin' )->{'direct'} = 1;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        $gnupg->passphrase( $args{'Passphrase'} );
-        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    my %res = CallGnuPG(
+        Method      => "decrypt",
+        Handles     => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} },
+        Passphrase  => $args{'Passphrase'},
+    );
 
     # if the decryption is fine but the signature is bad, then without this
     # status check we lose the decrypted text
     # XXX: add argument to the function to control this check
-    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-            return (undef, undef, %res);
-        }
-    }
+    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
+
+    return (undef, undef, %res) if $res{message};
 
     seek $tmp_fh, 0, 0;
     return ($tmp_fh, $tmp_fn, %res);
@@ -1475,27 +1225,11 @@ sub DecryptAttachment {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnuPGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
     if ( $args{'Data'}->bodyhandle->is_encoded ) {
         require RT::EmailParser;
         RT::EmailParser->_DecodeBody($args{'Data'});
     }
 
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
     $args{'Data'}->bodyhandle->print( $tmp_fh );
@@ -1503,7 +1237,6 @@ sub DecryptAttachment {
 
     my ($res_fh, $res_fn, %res) = _DecryptInlineBlock(
         %args,
-        GnuPG => $gnupg,
         BlockHandle => $tmp_fh,
     );
     return %res unless $res_fh;
@@ -1533,62 +1266,22 @@ sub DecryptContent {
         @_
     );
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-
-    # handling passphrase in GnupGOptions
-    $args{'Passphrase'} = delete $opt{'passphrase'}
-        if !defined($args{'Passphrase'});
-
-    $opt{'digest-algo'} ||= 'SHA1';
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    $args{'Passphrase'} = GetPassphrase()
-        unless defined $args{'Passphrase'};
-
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my ($handles, $handle_list) = _make_gpg_handles(
-            stdout => $tmp_fh);
-    my %handle = %$handle_list;
-    $handles->options( 'stdout' )->{'direct'} = 1;
-
-    my %res;
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        $gnupg->passphrase( $args{'Passphrase'} );
-        my $pid = safe_run_child { $gnupg->decrypt( handles => $handles ) };
-        {
-            local $SIG{'PIPE'} = 'IGNORE';
-            print { $handle{'stdin'} } ${ $args{'Content'} };
-            close $handle{'stdin'};
-        }
-
-        waitpid $pid, 0;
-    };
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+    my %res = CallGnuPG(
+        Method      => "decrypt",
+        Handles     => { stdout => $tmp_fh },
+        Passphrase  => $args{'Passphrase'},
+        Content     => $args{'Content'},
+    );
 
     # if the decryption is fine but the signature is bad, then without this
     # status check we lose the decrypted text
     # XXX: add argument to the function to control this check
-    if ( $res{'status'} !~ /DECRYPTION_OKAY/ ) {
-        if ( $@ || $? ) {
-            $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-            return %res;
-        }
-    }
+    delete $res{'message'} if $res{'status'} =~ /DECRYPTION_OKAY/;
+
+    return %res if $res{'message'};
 
     ${ $args{'Content'} } = '';
     seek $tmp_fh, 0, 0;
@@ -2122,49 +1815,19 @@ sub GetKeysInfo {
         return (exit_code => 0) unless $force;
     }
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $opt{'digest-algo'} ||= 'SHA1';
-    $opt{'with-colons'} = undef; # parseable format
-    $opt{'fingerprint'} = undef; # show fingerprint
-    $opt{'fixed-list-mode'} = undef; # don't merge uid with keys
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
+    my @info;
+    my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
+    my %res = CallGnuPG(
+        Options     => {
+            'with-colons'     => undef, # parseable format
+            'fingerprint'     => undef, # show fingerprint
+            'fixed-list-mode' => undef, # don't merge uid with keys
+        },
+        Method      => $method,
+        ( $email ? (CommandArgs => ['--', $email]) : () ),
+        Output      => \@info,
     );
-
-    my %res;
-
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
-        my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email
-                                                        ? (command_args => [ "--", $email])
-                                                        : () ) };
-        close $handle{'stdin'};
-        waitpid $pid, 0;
-    };
-
-    my @info = readline $handle{'stdout'};
-    close $handle{'stdout'};
-
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $@ || $? ) {
-        $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-        return %res;
-    }
+    return %res if $res{'message'};
 
     @info = ParseKeysInfo( @info );
     $res{'info'} = \@info;
@@ -2354,7 +2017,7 @@ sub DeleteKey {
     my %res;
     $res{'exit_code'} = $?;
     foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
+        $res{$_} = do { local $/ = undef; readline $handle{$_} };
         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
         close $handle{$_};
     }
@@ -2370,43 +2033,10 @@ sub DeleteKey {
 sub ImportKey {
     my $key = shift;
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
+    return CallGnuPG(
+        Method      => "import_keys",
+        Content     => $key,
     );
-
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--import'],
-        ) };
-        print { $handle{'stdin'} } $key;
-        close $handle{'stdin'};
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'stdout'};
-
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-    }
-    return %res;
 }
 
 =head2 KEY
@@ -2463,7 +2093,7 @@ sub Probe {
     my ($handles, $handle_list) = _make_gpg_handles();
     my %handle = %$handle_list;
 
-    local $@;
+    local $@ = undef;
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
         my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) };
@@ -2487,7 +2117,7 @@ sub Probe {
             . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
             . ".";
         foreach ( qw(stderr logger status) ) {
-            my $tmp = do { local $/; readline $handle{$_} };
+            my $tmp = do { local $/ = undef; readline $handle{$_} };
             next unless $tmp && $tmp =~ /\S/s;
             close $handle{$_};
             $msg .= "\n$_:\n$tmp\n";

commit 822bd9b3f94de27143e30e1b3512630700e39df4
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jul 26 16:29:47 2011 -0400

    Factor out code which finalizes GnuPG output into a data structure

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index fdc8d1a..441bb70 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -447,6 +447,13 @@ sub CallGnuPG {
         close $handle{stdout};
     }
 
+    return Finalize( $err, \%handle );
+}
+
+sub Finalize {
+    my ($err, $handle) = @_;
+    my %handle = %{ $handle };
+
     my %res;
     $res{'exit_code'} = $?;
 
@@ -2014,20 +2021,7 @@ sub DeleteKey {
     my $err = $@;
     close $handle{'stdout'};
 
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(stderr logger status) ) {
-        $res{$_} = do { local $/ = undef; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-    }
-    return %res;
+    return Finalize( $err, \%handle );
 }
 
 sub ImportKey {

commit 150229525327834734a3fcc7158857208d9ce294
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Aug 31 14:32:48 2012 -0400

    Generalize CallGnuPG slightly more, allowing more code reuse
    
    Three remaining locations had not been altered to use CallGnuPG, for two
    reaspons: they required custom interactions with the streams beyond
    simply printing content into gnupg's stdin, and because GnuPG::Interface
    provided no direct method to call them.  Extend CallGnuPG to allow both
    of these features, which thus allows for more code removal.

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 441bb70..a2ef4eb 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -423,10 +423,18 @@ sub CallGnuPG {
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
         my $pid = safe_run_child {
-            $gnupg->$method(
-                handles      => $handles,
-                command_args => $args{CommandArgs},
-            )
+            if ($method =~ /^--/) {
+                $gnupg->wrap_call(
+                    handles      => $handles,
+                    commands     => [$method],
+                    command_args => $args{CommandArgs},
+                );
+            } else {
+                $gnupg->$method(
+                    handles      => $handles,
+                    command_args => $args{CommandArgs},
+                );
+            }
         };
         {
             local $SIG{'PIPE'} = 'IGNORE';
@@ -438,6 +446,7 @@ sub CallGnuPG {
                 $handle{'stdin'}->print( $content );
             }
             close $handle{'stdin'} or die "Can't close gnupg handle: $!";
+            $args{Callback}->(%handle) if $args{Callback};
         }
         waitpid $pid, 0;
     };
@@ -1993,35 +2002,18 @@ sub _ParseDate {
 sub DeleteKey {
     my $key = shift;
 
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    my ($handles, $handle_list) = _make_gpg_handles();
-    my %handle = %$handle_list;
-
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--delete-secret-and-public-key'],
-            command_args => ["--", $key],
-        ) };
-        close $handle{'stdin'};
-        while ( my $str = readline $handle{'status'} ) {
-            if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
-                print { $handle{'command'} } "y\n";
+    return CallGnuPG(
+        Method      => "--delete-secret-and-public-key",
+        CommandArgs => [\--', $key],
+        Callback    => sub {
+            my %handle = @_;
+            while ( my $str = readline $handle{'status'} ) {
+                if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
+                    print { $handle{'command'} } "y\n";
+                }
             }
-        }
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'stdout'};
-
-    return Finalize( $err, \%handle );
+        },
+    );
 }
 
 sub ImportKey {
diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 467905c..8683e74 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1213,125 +1213,50 @@ sub lsign_gnupg_key {
     my $self = shift;
     my $key = shift;
 
-    require RT::Crypt::GnuPG; require GnuPG::Interface;
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    my %handle; 
-    my $handles = GnuPG::Handles->new(
-        stdin   => ($handle{'input'}   = IO::Handle->new()),
-        stdout  => ($handle{'output'}  = IO::Handle->new()),
-        stderr  => ($handle{'error'}   = IO::Handle->new()),
-        logger  => ($handle{'logger'}  = IO::Handle->new()),
-        status  => ($handle{'status'}  = IO::Handle->new()),
-        command => ($handle{'command'} = IO::Handle->new()),
-    );
+    require RT::Crypt::GnuPG;
 
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
-        my $pid = $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--lsign-key'],
-            command_args => [$key],
-        );
-        close $handle{'input'};
-        while ( my $str = readline $handle{'status'} ) {
-            if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL sign_uid\..*/ ) {
-                print { $handle{'command'} } "y\n";
+    return RT::Crypt::GnuPG::CallGnuPG(
+        Method      => '--lsign-key',
+        CommandArgs => [$key],
+        Callback    => sub {
+            my %handle = @_;
+            while ( my $str = readline $handle{'status'} ) {
+                if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL sign_uid\..*/ ) {
+                    print { $handle{'command'} } "y\n";
+                }
             }
-        }
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'output'};
-
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(error logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'error'} ) if $res{'error'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-    }
-    return %res;
+        },
+    );
 }
 
 sub trust_gnupg_key {
     my $self = shift;
     my $key = shift;
 
-    require RT::Crypt::GnuPG; require GnuPG::Interface;
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
-    $gnupg->options->hash_init(
-        RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ),
-        meta_interactive => 0,
-    );
-
-    my %handle; 
-    my $handles = GnuPG::Handles->new(
-        stdin   => ($handle{'input'}   = IO::Handle->new()),
-        stdout  => ($handle{'output'}  = IO::Handle->new()),
-        stderr  => ($handle{'error'}   = IO::Handle->new()),
-        logger  => ($handle{'logger'}  = IO::Handle->new()),
-        status  => ($handle{'status'}  = IO::Handle->new()),
-        command => ($handle{'command'} = IO::Handle->new()),
-    );
+    require RT::Crypt::GnuPG;
 
-    eval {
-        local $SIG{'CHLD'} = 'DEFAULT';
-        local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
-        my $pid = $gnupg->wrap_call(
-            handles => $handles,
-            commands => ['--edit-key'],
-            command_args => [$key],
-        );
-        close $handle{'input'};
-
-        my $done = 0;
-        while ( my $str = readline $handle{'status'} ) {
-            if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) {
-                if ( $done ) {
-                    print { $handle{'command'} } "quit\n";
-                } else {
-                    print { $handle{'command'} } "trust\n";
+    return RT::Crypt::GnuPG::CallGnuPG(
+        Method      => '--edit-key',
+        CommandArgs => [$key],
+        Callback    => sub {
+            my %handle = @_;
+            my $done = 0;
+            while ( my $str = readline $handle{'status'} ) {
+                if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) {
+                    if ( $done ) {
+                        print { $handle{'command'} } "quit\n";
+                    } else {
+                        print { $handle{'command'} } "trust\n";
+                    }
+                } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) {
+                    print { $handle{'command'} } "5\n";
+                } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) {
+                    print { $handle{'command'} } "y\n";
+                    $done = 1;
                 }
-            } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) {
-                print { $handle{'command'} } "5\n";
-            } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) {
-                print { $handle{'command'} } "y\n";
-                $done = 1;
             }
-        }
-        waitpid $pid, 0;
-    };
-    my $err = $@;
-    close $handle{'output'};
-
-    my %res;
-    $res{'exit_code'} = $?;
-    foreach ( qw(error logger status) ) {
-        $res{$_} = do { local $/; readline $handle{$_} };
-        delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
-    }
-    $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-    $RT::Logger->warning( $res{'error'} ) if $res{'error'};
-    $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-    if ( $err || $res{'exit_code'} ) {
-        $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-    }
-    return %res;
+        },
+    );
 }
 
 sub started_ok {

commit 13cb18d74ab8f8971a56cb318382ced65892cfe2
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jul 26 18:06:15 2011 -0400

    Minor cleanups to Probe, the one remaining non-CallGnuPG gpg interaction

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index a2ef4eb..2b7299a 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2068,13 +2068,11 @@ properly (and false otherwise).
 
 
 sub Probe {
-    my $gnupg = GnuPG::Interface->new();
-    my %opt = RT->Config->Get('GnuPGOptions');
+    my $gnupg = GnuPG::Interface->new;
     $gnupg->options->hash_init(
-        _PrepareGnuPGOptions( %opt ),
-        armor => 1,
-        meta_interactive => 0,
+        _PrepareGnuPGOptions( RT->Config->Get('GnuPGOptions') )
     );
+    $gnupg->options->meta_interactive( 0 );
 
     my ($handles, $handle_list) = _make_gpg_handles();
     my %handle = %$handle_list;
@@ -2082,7 +2080,12 @@ sub Probe {
     local $@ = undef;
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
-        my $pid = safe_run_child { $gnupg->wrap_call( commands => ['--version' ], handles => $handles ) };
+        my $pid = safe_run_child {
+            $gnupg->wrap_call(
+                commands => ['--version' ],
+                handles  => $handles
+            )
+        };
         close $handle{'stdin'};
         waitpid $pid, 0;
     };
@@ -2099,7 +2102,7 @@ sub Probe {
 # but there is no way to get actuall error
     if ( $? && ($? >> 8) != 2 ) {
         my $msg = "Probe for GPG failed."
-            ." Process exitted with code ". ($? >> 8)
+            ." Process exited with code ". ($? >> 8)
             . ($? & 127 ? (" as recieved signal ". ($? & 127)) : '')
             . ".";
         foreach ( qw(stderr logger status) ) {

commit bd5be0b6f1bcb8b8739f5bbdfa27d1634f974181
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Jul 26 16:30:25 2011 -0400

    Catch errors on close()

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 2b7299a..550b9bf 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -445,7 +445,7 @@ sub CallGnuPG {
             } elsif (defined $content) {
                 $handle{'stdin'}->print( $content );
             }
-            close $handle{'stdin'} or die "Can't close gnupg handle: $!";
+            close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
             $args{Callback}->(%handle) if $args{Callback};
         }
         waitpid $pid, 0;
@@ -453,7 +453,9 @@ sub CallGnuPG {
     my $err = $@;
     if ($args{Output}) {
         push @{$args{Output}}, readline $handle{stdout};
-        close $handle{stdout};
+        if (not close $handle{stdout}) {
+            $err ||= "Can't close gnupg output handle: $!";
+        }
     }
 
     return Finalize( $err, \%handle );
@@ -469,7 +471,9 @@ sub Finalize {
     foreach ( qw(stderr logger status) ) {
         $res{$_} = do { local $/ = undef; readline $handle{$_} };
         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-        close $handle{$_};
+        if (not close $handle{$_}) {
+            $err ||= "Can't close gnupg $_ handle: $!";
+        }
     }
     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
     $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
@@ -2086,7 +2090,7 @@ sub Probe {
                 handles  => $handles
             )
         };
-        close $handle{'stdin'};
+        close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
         waitpid $pid, 0;
     };
     if ( $@ ) {
@@ -2108,7 +2112,7 @@ sub Probe {
         foreach ( qw(stderr logger status) ) {
             my $tmp = do { local $/ = undef; readline $handle{$_} };
             next unless $tmp && $tmp =~ /\S/s;
-            close $handle{$_};
+            close $handle{$_} or $tmp .= "\nFailed to close: $!";
             $msg .= "\n$_:\n$tmp\n";
         }
         $RT::Logger->debug( $msg );

commit 51ffb9aa0b78d11ba5106c9c78182596fb30d1f3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 13:36:06 2011 -0400

    Rename Key to Signer for clarity and consistency

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 550b9bf..c24a5ef 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -371,7 +371,7 @@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
 sub CallGnuPG {
     my %args = (
         Options     => undef,
-        Key         => undef,
+        Signer      => undef,
         Recipients  => [],
         Passphrase  => undef,
 
@@ -406,7 +406,7 @@ sub CallGnuPG {
     );
     $gnupg->options->armor( 1 );
     $gnupg->options->meta_interactive( 0 );
-    $gnupg->options->default_key( $args{Key} );
+    $gnupg->options->default_key( $args{Signer} );
 
     my %seen;
     $gnupg->options->push_recipients( $_ ) for
@@ -416,7 +416,7 @@ sub CallGnuPG {
 
     $args{Passphrase} = $GnuPGOptions{passphrase}
         unless defined $args{'Passphrase'};
-    $args{Passphrase} = GetPassphrase( Address => $args{Key} )
+    $args{Passphrase} = GetPassphrase( Address => $args{Signer} )
         unless defined $args{'Passphrase'};
     $gnupg->passphrase( $args{'Passphrase'} );
 
@@ -572,7 +572,7 @@ sub SignEncryptRFC3156 {
 
         my @signature;
         %res = CallGnuPG(
-            Key        => $args{'Signer'},
+            Signer     => $args{'Signer'},
             Method     => "detach_sign",
             Handles    => { stdin => IO::Handle::CRLF->new },
             Direct     => [],
@@ -605,7 +605,7 @@ sub SignEncryptRFC3156 {
 
         $entity->make_multipart( 'mixed', Force => 1 );
         %res = CallGnuPG(
-            Key        => $args{'Signer'},
+            Signer     => $args{'Signer'},
             Recipients => \@recipients,
             Method     => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
             Handles    => { stdout => $tmp_fh },
@@ -677,7 +677,7 @@ sub _SignEncryptTextInline {
 
     my $entity = $args{'Entity'};
     my %res = CallGnuPG(
-        Key        => $args{'Signer'},
+        Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Method     => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
@@ -718,7 +718,7 @@ sub _SignEncryptAttachmentInline {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Key        => $args{'Signer'},
+        Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Method     => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
@@ -771,7 +771,7 @@ sub SignEncryptContent {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Key        => $args{'Signer'},
+        Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Method     => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'

commit 2ba56b5c5589e1ebc0164c8023457b6e6fa33a52
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 13:38:09 2011 -0400

    Rename Method to Command for clarity; "--foo" is not a "method"

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index c24a5ef..941417e 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -375,7 +375,7 @@ sub CallGnuPG {
         Recipients  => [],
         Passphrase  => undef,
 
-        Method      => undef,
+        Command     => undef,
         CommandArgs => [],
 
         Content     => undef,
@@ -392,7 +392,7 @@ sub CallGnuPG {
     %handle = %$handle_list;
 
     my $content = $args{Content};
-    my $method  = $args{Method};
+    my $command = $args{Command};
 
     my %GnuPGOptions = RT->Config->Get('GnuPGOptions');
     my %opt = (
@@ -423,14 +423,14 @@ sub CallGnuPG {
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';
         my $pid = safe_run_child {
-            if ($method =~ /^--/) {
+            if ($command =~ /^--/) {
                 $gnupg->wrap_call(
                     handles      => $handles,
-                    commands     => [$method],
+                    commands     => [$command],
                     command_args => $args{CommandArgs},
                 );
             } else {
-                $gnupg->$method(
+                $gnupg->$command(
                     handles      => $handles,
                     command_args => $args{CommandArgs},
                 );
@@ -573,7 +573,7 @@ sub SignEncryptRFC3156 {
         my @signature;
         %res = CallGnuPG(
             Signer     => $args{'Signer'},
-            Method     => "detach_sign",
+            Command    => "detach_sign",
             Handles    => { stdin => IO::Handle::CRLF->new },
             Direct     => [],
             Passphrase => $args{'Passphrase'},
@@ -607,7 +607,7 @@ sub SignEncryptRFC3156 {
         %res = CallGnuPG(
             Signer     => $args{'Signer'},
             Recipients => \@recipients,
-            Method     => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
+            Command    => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
             Handles    => { stdout => $tmp_fh },
             Passphrase => $args{'Passphrase'},
             Content    => $entity->parts(0),
@@ -679,7 +679,7 @@ sub _SignEncryptTextInline {
     my %res = CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
-        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+        Command    => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
                       : ( $args{'Sign'}
                         ? 'clearsign'
@@ -720,7 +720,7 @@ sub _SignEncryptAttachmentInline {
     my %res = CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
-        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+        Command    => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
                       : ( $args{'Sign'}
                         ? 'detach_sign'
@@ -773,7 +773,7 @@ sub SignEncryptContent {
     my %res = CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
-        Method     => ( $args{'Sign'} && $args{'Encrypt'}
+        Command    => ( $args{'Sign'} && $args{'Encrypt'}
                       ? 'sign_and_encrypt'
                       : ( $args{'Sign'}
                         ? 'clearsign'
@@ -1056,7 +1056,7 @@ sub VerifyAttachment {
     $tmp_fh->flush;
 
     return CallGnuPG(
-        Method      => "verify",
+        Command     => "verify",
         CommandArgs => [ '-', $tmp_fn ],
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Signature'}->bodyhandle,
@@ -1072,7 +1072,7 @@ sub VerifyRFC3156 {
     $tmp_fh->flush;
 
     return CallGnuPG(
-        Method      => "verify",
+        Command     => "verify",
         CommandArgs => [ '-', $tmp_fn ],
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Signature'}->bodyhandle,
@@ -1097,7 +1097,7 @@ sub DecryptRFC3156 {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Method      => "decrypt",
+        Command     => "decrypt",
         Handles     => { stdout => $tmp_fh },
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Data'}->bodyhandle,
@@ -1221,7 +1221,7 @@ sub _DecryptInlineBlock {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Method      => "decrypt",
+        Command     => "decrypt",
         Handles     => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} },
         Passphrase  => $args{'Passphrase'},
     );
@@ -1290,7 +1290,7 @@ sub DecryptContent {
     binmode $tmp_fh, ':raw';
 
     my %res = CallGnuPG(
-        Method      => "decrypt",
+        Command     => "decrypt",
         Handles     => { stdout => $tmp_fh },
         Passphrase  => $args{'Passphrase'},
         Content     => $args{'Content'},
@@ -1843,7 +1843,7 @@ sub GetKeysInfo {
             'fingerprint'     => undef, # show fingerprint
             'fixed-list-mode' => undef, # don't merge uid with keys
         },
-        Method      => $method,
+        Command     => $method,
         ( $email ? (CommandArgs => ['--', $email]) : () ),
         Output      => \@info,
     );
@@ -2007,7 +2007,7 @@ sub DeleteKey {
     my $key = shift;
 
     return CallGnuPG(
-        Method      => "--delete-secret-and-public-key",
+        Command     => "--delete-secret-and-public-key",
         CommandArgs => [\--', $key],
         Callback    => sub {
             my %handle = @_;
@@ -2024,7 +2024,7 @@ sub ImportKey {
     my $key = shift;
 
     return CallGnuPG(
-        Method      => "import_keys",
+        Command     => "import_keys",
         Content     => $key,
     );
 }
diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 8683e74..21283ce 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1216,7 +1216,7 @@ sub lsign_gnupg_key {
     require RT::Crypt::GnuPG;
 
     return RT::Crypt::GnuPG::CallGnuPG(
-        Method      => '--lsign-key',
+        Command     => '--lsign-key',
         CommandArgs => [$key],
         Callback    => sub {
             my %handle = @_;
@@ -1236,7 +1236,7 @@ sub trust_gnupg_key {
     require RT::Crypt::GnuPG;
 
     return RT::Crypt::GnuPG::CallGnuPG(
-        Method      => '--edit-key',
+        Command     => '--edit-key',
         CommandArgs => [$key],
         Callback    => sub {
             my %handle = @_;

commit 379e33f83521bcf7a80bdb4b7391075cc77a5356
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 14:08:02 2011 -0400

    Only set the defualt key if we actually have one

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 941417e..f3f6125 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -406,7 +406,8 @@ sub CallGnuPG {
     );
     $gnupg->options->armor( 1 );
     $gnupg->options->meta_interactive( 0 );
-    $gnupg->options->default_key( $args{Signer} );
+    $gnupg->options->default_key( $args{Signer} )
+        if defined $args{Signer};
 
     my %seen;
     $gnupg->options->push_recipients( $_ ) for

commit b532a640dba0b2ecbdcef801bf4b787e70367d77
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Wed Jul 27 14:11:29 2011 -0400

    Only set the passphrase if we have one

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index f3f6125..88b8d99 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -419,7 +419,8 @@ sub CallGnuPG {
         unless defined $args{'Passphrase'};
     $args{Passphrase} = GetPassphrase( Address => $args{Signer} )
         unless defined $args{'Passphrase'};
-    $gnupg->passphrase( $args{'Passphrase'} );
+    $gnupg->passphrase( $args{'Passphrase'} )
+        if defined $args{Passphrase};
 
     eval {
         local $SIG{'CHLD'} = 'DEFAULT';

commit 27cd7a8d9d9aa98f0857f7be71df9c064e4c7b9f
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Tue Aug 30 19:49:43 2011 -0400

    Split IO::Handle::CRLF into its own file in RT::Crypt::GnuPG::CRLFHandle
    
    In doing so, also document the reasons it is necessary.

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 88b8d99..3c99899 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -52,6 +52,7 @@ use warnings;
 package RT::Crypt::GnuPG;
 
 use IO::Handle;
+use RT::Crypt::GnuPG::CRLFHandle;
 use GnuPG::Interface;
 use RT::EmailParser ();
 use RT::Util 'safe_run_child', 'mime_recommended_filename';
@@ -573,10 +574,12 @@ sub SignEncryptRFC3156 {
         $entity->make_multipart( 'mixed', Force => 1 );
 
         my @signature;
+        # We use RT::Crypt::GnuPG::CRLFHandle to canonicalize the
+        # MIME::Entity output to use \r\n instead of \n for its newlines
         %res = CallGnuPG(
             Signer     => $args{'Signer'},
             Command    => "detach_sign",
-            Handles    => { stdin => IO::Handle::CRLF->new },
+            Handles    => { stdin => RT::Crypt::GnuPG::CRLFHandle->new },
             Direct     => [],
             Passphrase => $args{'Passphrase'},
             Content    => $entity->parts(0),
@@ -2136,15 +2139,4 @@ sub _make_gpg_handles {
 
 RT::Base->_ImportOverlays();
 
-# 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/CRLFHandle.pm b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
new file mode 100644
index 0000000..5f74457
--- /dev/null
+++ b/lib/RT/Crypt/GnuPG/CRLFHandle.pm
@@ -0,0 +1,22 @@
+package RT::Crypt::GnuPG::CRLFHandle;
+use strict;
+use warnings;
+
+use base qw(IO::Handle);
+
+# https://metacpan.org/module/MIME::Tools#Fuzzing-of-CRLF-and-newline-when-encoding-composing
+# means that the output of $entity->print contains lines terminated by
+# "\n"; however, signatures are generated off of the "correct" form of
+# the MIME entity, which uses "\r\n" as the newline separator.  This
+# class, used only when generating signatures, transparently munges "\n"
+# newlines into "\r\n" newlines such that the generated signature is
+# correct for the "\r\n"-newline version of the MIME entity which will
+# eventually be sent over the wire.
+
+sub print {
+    my ($self, @args) = (@_);
+    s/\r*\n/\x0D\x0A/g foreach @args;
+    return $self->SUPER::print( @args );
+}
+
+1;

commit e4ef5a47801dba3fe1a59ae7d3cc73d1407d50d3
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 6164a42..8f3e940 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,
 
@@ -1057,6 +1061,7 @@ sub FindProtectedParts {
 =cut
 
 sub VerifyDecrypt {
+    my $self = shift;
     my %args = (
         Entity    => undef,
         Detach    => 1,
@@ -1080,7 +1085,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
@@ -1126,9 +1131,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();
@@ -1183,6 +1189,7 @@ sub VerifyAttachment {
 }
 
 sub VerifyRFC3156 {
+    my $self = shift;
     my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
 
     my $gnupg = GnuPG::Interface->new();
@@ -1230,6 +1237,7 @@ sub VerifyRFC3156 {
 }
 
 sub DecryptRFC3156 {
+    my $self = shift;
     my %args = (
         Data => undef,
         Info => undef,
@@ -1310,6 +1318,7 @@ sub DecryptRFC3156 {
 }
 
 sub DecryptInline {
+    my $self = shift;
     my %args = (
         Data => undef,
         Passphrase => undef,
@@ -1360,7 +1369,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,
@@ -1418,6 +1427,7 @@ sub DecryptInline {
 }
 
 sub _DecryptInlineBlock {
+    my $self = shift;
     my %args = (
         GnuPG => undef,
         BlockHandle => undef,
@@ -1468,6 +1478,7 @@ sub _DecryptInlineBlock {
 }
 
 sub DecryptAttachment {
+    my $self = shift;
     my %args = (
         Top  => undef,
         Data => undef,
@@ -1501,7 +1512,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,
@@ -1527,6 +1538,7 @@ sub DecryptAttachment {
 }
 
 sub DecryptContent {
+    my $self = shift;
     my %args = (
         Content => undef,
         Passphrase => undef,
@@ -2430,7 +2442,7 @@ sub DrySign {
         Data    => ['t'],
     );
 
-    my %res = SignEncrypt(
+    my %res = $self->SignEncrypt(
         Sign    => 1,
         Encrypt => 0,
         Entity  => $mime,

commit cb5239ec4313aea95c18452374422ca3e748f7b1
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 040fddb6cdde23f985685a97316518c93d26f453
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 8f3e940..743b45c 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 db21ab289e1a06cd53f7de8ffe716a45dd8327a3
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 743b45c..0789f24 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -877,19 +877,14 @@ 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}$/;
-
-        my $io = $entity->open('r');
-        unless ( $io ) {
-            $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
-            return ();
-        }
+    # we check inline PGP block later in another sub
+    return () unless $entity->is_multipart;
 
         # Deal with "partitioned" PGP mail, which (contrary to common
         # sense) unnecessarily applies a base64 transfer encoding to PGP
@@ -917,131 +912,166 @@ sub FindProtectedParts {
             }
         }
 
-        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 => {}, @_ );
 
-        if ( $type eq 'multipart/encrypted' ) {
-            unless ( $protocol eq 'application/pgp-encrypted' ) {
-                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-encrypted' is supported" );
-                return ();
+    my @res;
+
+    my @parts = @{ $args{'Parts'} };
+
+    # attachments signed with signature in another part
+    {
+        my @file_indices;
+        for (my $i = 0; $i < @parts; $i++ ) {
+            my $part = $parts[ $i ];
+
+            # we can not associate a signature within an attachment
+            # without file names
+            my $fname = $part->head->recommended_filename;
+            next unless $fname;
+
+            my $type = $part->effective_type;
+
+            if ( $type eq 'application/pgp-signature' ) {
+                push @file_indices, $i;
             }
-            $RT::Logger->debug("Found encrypted according to RFC3156 part");
-            return {
-                Type    => 'encrypted',
-                Format  => 'RFC3156',
-                Top   => $entity,
-                Data  => $entity->parts(1),
-                Info    => $entity->parts(0),
-            };
-        } else {
-            unless ( $protocol eq 'application/pgp-signature' ) {
-                $RT::Logger->info( "Skipping protocol '$protocol', only 'application/pgp-signature' is supported" );
-                return ();
+            elsif ( $type eq 'application/octet-stream' && $fname =~ /\.sig$/i ) {
+                push @file_indices, $i;
             }
-            $RT::Logger->debug("Found signed according to RFC3156 part");
-            return {
-                Type      => 'signed',
-                Format    => 'RFC3156',
-                Top     => $entity,
-                Data    => $entity->parts(0),
-                Signature => $entity->parts(1),
-            };
         }
-    }
 
-    # attachments signed with signature in another part
-    my @file_indices;
-    foreach my $i ( 0 .. $entity->parts - 1 ) {
-        my $part = $entity->parts($i);
+        foreach my $i ( @file_indices ) {
+            my $sig_part = $parts[ $i ];
+            my $sig_name = $sig_part->head->recommended_filename;
+            my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/;
+
+            my ($data_part_idx) =
+                grep $file_name eq ($parts[$_]->head->recommended_filename||''),
+                grep $sig_part  ne  $parts[$_],
+                    0 .. @parts - 1;
+            unless ( defined $data_part_idx ) {
+                $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
+                next;
+            }
 
-        # we can not associate a signature within an attachment
-        # without file names
-        my $fname = $part->head->recommended_filename;
-        next unless $fname;
+            my $data_part_in = $parts[ $data_part_idx ];
 
-        if ( $part->effective_type eq 'application/pgp-signature' ) {
-            push @file_indices, $i;
-        }
-        elsif ( $fname =~ /\.sig$/i && $part->effective_type eq 'application/octet-stream' ) {
-            push @file_indices, $i;
+            $RT::Logger->debug("Found signature (in '$sig_name') of attachment '$file_name'");
+
+            $args{'Skip'}{$data_part_in} = 1;
+            $args{'Skip'}{$sig_part} = 1;
+            push @res, {
+                Type      => 'signed',
+                Format    => 'Attachment',
+                Top       => $entity,
+                Data      => $data_part_in,
+                Signature => $sig_part,
+            };
         }
     }
 
-    my (@res, %skip);
-    foreach my $i ( @file_indices ) {
-        my $sig_part = $entity->parts($i);
-        $skip{"$sig_part"}++;
-        my $sig_name = $sig_part->head->recommended_filename;
-        my ($file_name) = $sig_name =~ /^(.*?)(?:\.sig)?$/;
+    # attachments with inline encryption
+    foreach my $part ( @parts ) {
+        next if $args{'Skip'}{$part};
 
-        my ($data_part_idx) =
-            grep $file_name eq ($entity->parts($_)->head->recommended_filename||''),
-            grep $sig_part  ne  $entity->parts($_),
-                0 .. $entity->parts - 1;
-        unless ( defined $data_part_idx ) {
-            $RT::Logger->error("Found $sig_name attachment, but didn't find $file_name");
-            next;
-        }
-        my $data_part_in = $entity->parts($data_part_idx);
+        my $fname = $part->head->recommended_filename || '';
+        next unless $fname =~ /\.${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 3d7ea19663fe3910ce09359a4034d944800b9435
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 0789f24..d48b769 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1079,26 +1079,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 );
@@ -1109,6 +1110,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';
@@ -1120,18 +1123,19 @@ sub VerifyDecrypt {
             );
             $status_on->head->modify($modify);
         }
-    }
-    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';
@@ -1143,6 +1147,8 @@ sub VerifyDecrypt {
             );
             $status_on->head->modify($modify);
         }
+    } else {
+        die "Unknow type '". $item->{'Type'} ."' of protected item";
     }
     return @res;
 }

commit dacd3c1b33c1687820c0198b0a6ca93215f44545
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 7fe7b7b..1e820cd 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -576,7 +576,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 8b4701dc409fee6a6a080a5a1bdf2f52693bffb2
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 ef27e93310c4a48a4b4a4ac6fbe0fa6571328063
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 c14bcf0..d0aaf90 100644
--- a/lib/RT/Interface/Email/Auth/GnuPG.pm
+++ b/lib/RT/Interface/Email/Auth/GnuPG.pm
@@ -59,197 +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-Encryption
-            X-RT-Incoming-Signature X-RT-Privacy
-            X-RT-Sign X-RT-Encrypt
-        );
-    }
-
-    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 a9ea2c95d4061a12f5836980d25e415da4dd42e3
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 17ebb342109f893b19106a6e50fff43d511ba920
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 95768b1ef3e0f4dfe25b96fceff6c233217ff32e
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 35387eb..325bb13 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -748,7 +748,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 d48b769..45d70f2 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1756,6 +1756,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 ddf27da..03116f5 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -847,8 +847,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'} );
@@ -915,7 +915,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 b5b2f026c7c7863b232968ccce14cde589c361d5
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 45d70f2..2ff5cec 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'} ) {
@@ -915,7 +915,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" );
@@ -1286,7 +1286,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 );
@@ -1365,7 +1365,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 );
@@ -1526,7 +1526,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 );
@@ -1580,7 +1580,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 );
@@ -1646,6 +1646,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';
 }
@@ -2045,7 +2046,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'};
 
@@ -2066,7 +2067,7 @@ sub GetKeysForEncryption {
 
 sub GetKeysForSigning {
     my $key_id = shift;
-    return GetKeysInfo( $key_id, 'private', @_ );
+    return $self->GetKeysInfo( $key_id, 'private', @_ );
 }
 
 sub CheckRecipients {
@@ -2143,12 +2144,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;
@@ -2356,6 +2358,7 @@ sub _ParseDate {
 }
 
 sub DeleteKey {
+    my $self = shift;
     my $key = shift;
 
     my $gnupg = GnuPG::Interface->new();
@@ -2403,6 +2406,7 @@ sub DeleteKey {
 }
 
 sub ImportKey {
+    my $self = shift;
     my $key = shift;
 
     my $gnupg = GnuPG::Interface->new();
@@ -2455,6 +2459,7 @@ Returns a true value if all went well.
 =cut
 
 sub DrySign {
+    my $self = shift;
     my $from = shift;
 
     my $mime = MIME::Entity->build(
@@ -2487,6 +2492,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 55dfb6f16a38ee15590353608cff1b6d0375d1f5
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 c2b0ada959fe051200ebdebbda4b64ee9c31e5b5
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 325bb13..ce97e56 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -734,7 +734,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 03116f5..d63d3e3 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -851,7 +851,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 467905c..f121719 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1204,7 +1204,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 773ff9858077722d30fe7c0c93eac7d9b79ebd70
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 14038720f5683e21622a8f0338dd68297617335f
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 d246954..89fd3ea 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 e4fe614ba7ff057b00d975eb4b5f9a19359577a7
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 7e0aa2d..0c00b29 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -138,7 +138,7 @@ if ( $self->{'Sign'} ) {
     if ($address ne $private and $address ne $queue) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
-    } elsif ( not RT::Crypt::GnuPG::DrySign( $address ) ) {
+    } elsif ( not RT::Crypt->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 89fd3ea..28e07f9 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 5700b82f5c75f0a3f35eb6760aa6cdd897b4dee6
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 2ff5cec..d5c526f 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,
 
@@ -1039,7 +1040,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}$/;
@@ -1048,7 +1049,7 @@ sub FindScatteredParts {
         push @res, {
             Type      => $type,
             Format    => !$file || $type eq 'signed'? 'Inline' : 'Attachment',
-            Data      => $entity,
+            Data      => $part,
         };
     }
 
@@ -1119,7 +1120,7 @@ sub VerifyDecrypt {
             my $modify = $status_on->head->modify;
             $status_on->head->modify(1);
             $status_on->head->$method(
-                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
+                'X-RT-GnuPG-Status' => $res{'status'}
             );
             $status_on->head->modify($modify);
         }
@@ -1143,14 +1144,14 @@ sub VerifyDecrypt {
             my $modify = $status_on->head->modify;
             $status_on->head->modify(1);
             $status_on->head->$method(
-                'X-RT-GnuPG-Status' => $res[-1]->{'status'}
+                'X-RT-GnuPG-Status' => $res{'status'}
             );
             $status_on->head->modify($modify);
         }
     } else {
         die "Unknow type '". $item->{'Type'} ."' of protected item";
     }
-    return @res;
+    return %res;
 }
 
 sub VerifyInline { return (shift)->DecryptInline( @_ ) }
@@ -2045,6 +2046,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'};
@@ -2066,18 +2068,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;
@@ -2136,14 +2140,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 f661ce4b2e765e7d4b708b02c482c82149ccf8e0
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 3bc545881985c921251c4478e976ca40f54d46e1
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 01dc92fea9547ce98869b8c96cd2bf8166bc0bfa
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 d2c4a69e2be75b2c0c2c64cb6ca0e50c158b80cb
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 f361803405140056ab701a8560e4fcc8fbe3825b
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 d5c526f..ac74c78 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -925,11 +925,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 ();
@@ -1059,6 +1080,7 @@ sub FindScatteredParts {
 sub _CheckIfProtectedInline {
     my $self = shift;
     my $entity = shift;
+    my $check_for_signature = shift || 0;
 
     my $io = $entity->open('r');
     unless ( $io ) {
@@ -1066,8 +1088,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 7a7e813907743753773e7aade430f5e2869b2425
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 ac74c78..bddcd7a 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1032,7 +1032,7 @@ sub FindScatteredParts {
             push @res, {
                 Type      => 'signed',
                 Format    => 'Attachment',
-                Top       => $entity,
+                Top       => $args{'Parents'}{$data_part_in},
                 Data      => $data_part_in,
                 Signature => $sig_part,
             };
@@ -1052,7 +1052,7 @@ sub FindScatteredParts {
         push @res, {
             Type    => 'encrypted',
             Format  => 'Attachment',
-            Top     => $entity,
+            Top     => $args{'Parents'}{$part},
             Data    => $part,
         };
     }

commit 5d6ef3db4858816f7e1385aeb2d8296d2ddf6fb5
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 9431318dcafbba85b8a14938fd984dc479888f0d
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 ce35320..0405f86 100755
--- a/configure.ac
+++ b/configure.ac
@@ -321,6 +321,20 @@ fi
 AC_SUBST(RT_SSL_MAILGATE)
 
 
+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 ab6c8b0cfaea8c0dd4f93bb690809d0aeeca4abc
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 2d69b3b..dcd1449 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2072,8 +2072,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
@@ -2083,27 +2134,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 5777d1fcad11caa40d8db61ce37d22f4e07d7570
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 1e820cd..caf7795 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -609,8 +609,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');
@@ -625,7 +639,6 @@ our %META = (
                 return;
             }
 
-
             require RT::Crypt::GnuPG;
             unless (RT::Crypt::GnuPG->Probe()) {
                 $RT::Logger->debug(
@@ -633,6 +646,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."
+                );
+            }
         }
     },
     ReferrerWhitelist => { Type => 'ARRAY' },

commit 158f94266ea8a2b26ecddea7c5bd6879bee87432
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 4d8cf8f7ef60af8bd357ca496edd33500d45f848
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 dcd1449..6081c74 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2100,7 +2100,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 caf7795..f9441e0 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -613,11 +613,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];
             }
         },
     },
@@ -629,6 +634,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 32b28ad0ee57b03ec61d2280f22240878206759d
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 bddcd7a..89cba8e 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;
@@ -2083,7 +2084,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;
 
@@ -2234,12 +2235,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 78992047763140ac6f2083657274eeb0f2784055
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 80ac1cdf681420c654fcdd3927e07e549231aff6
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 3665b6143f41f803a370e5191566acefe86a1d7f
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 0e7e403e58447c2f40311c9b8657f24b16fc74a1
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 89cba8e..4a90987 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2075,7 +2075,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'};
 
@@ -2097,7 +2097,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 {
@@ -2166,29 +2166,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 eef2b7da98af932caa1bc5b4f56e34008f48c296
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 71b58cf..d8abee5 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,23 +80,27 @@
 </td></tr>
 % }
 
-</table>
 % }
-</&>
+
+% if ( @protocols ) {
+<tr><th colspan="2"> </th></tr>
+% }
+
+% }
+</table></&>
 
 <%ARGS>
 $EmailAddress
 $Type => 'public'
 </%ARGS>
 <%INIT>
-require RT::Crypt::GnuPG;
-my %res = RT::Crypt::GnuPG::GetKeyInfo( $EmailAddress, $Type );
+my @protocols = RT::Crypt->EnabledProtocols;
+return unless @protocols;
 
 my $title;
 unless ( $Type eq 'private' ) {
-    $title = loc('GnuPG public key(s) for [_1]', $EmailAddress);
+    $title = loc('Public key(s) for [_2]', $EmailAddress);
 } else {
-    $title = loc('GnuPG private key(s) for [_1]', $EmailAddress);
+    $title = loc('Private key(s) for [_2]', $EmailAddress);
 }
-
 </%INIT>

commit dfd83add1eeafdf6713691fa13411cfff00d0fcc
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 49736a4..ee0bb6e 100644
--- 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>
@@ -128,7 +128,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 0c00b29..a614b91 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -142,7 +142,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'};
     }
 }
@@ -165,13 +165,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 06637ee..748faaf 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 28e07f9..75e604a 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 362e89c5d8fe9092d6d6cd7469ef07c77febe695
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 48d2d9b..3e54aa8 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -21,6 +21,7 @@ use String::ShellQuote 'shell_quote';
 use IPC::Run3 'run3';
 use MIME::Base64;
 
+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 969a860f1f348de9ff107c903a6a5743122171c0
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 ce97e56..0c46855 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -734,9 +734,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 4a90987..1f79942 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'} || [] };
     }
 
@@ -2029,40 +2029,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.
@@ -2074,8 +2040,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'};
 
@@ -2096,8 +2062,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 {
@@ -2108,7 +2074,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;
@@ -2118,7 +2084,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,
@@ -2471,10 +2437,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.
@@ -2483,7 +2449,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",
@@ -2510,7 +2477,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 32c877e..783e45c 100644
--- a/lib/RT/Interface/Web/Handler.pm
+++ b/lib/RT/Interface/Web/Handler.pm
@@ -154,7 +154,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
 
@@ -181,10 +181,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 938e5b6..eea1c8d 100644
--- a/lib/RT/User.pm
+++ b/lib/RT/User.pm
@@ -1556,18 +1556,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);
@@ -1610,7 +1610,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 fbf1a3c..66a7573 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 );
+my %keys_meta = RT::Crypt->GetKeysForSigning( $email );
 my @potential_keys = map $_->{'Key'}, @{ $keys_meta{'info'} || [] };
 
 $ARGS{'PrivateKey'} = $m->comp('/Widgets/Form/Select:Process',
diff --git a/share/html/Elements/GnuPG/SelectKeyForEncryption b/share/html/Elements/GnuPG/SelectKeyForEncryption
index 6d28717..805064f 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 a614b91..801bc53 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>
@@ -138,11 +136,11 @@ if ( $self->{'Sign'} ) {
     if ($address ne $private and $address ne $queue) {
         push @{ $self->{'GnuPGCanNotSignAs'} ||= [] }, $address;
         $checks_failure = 1;
-    } elsif ( not RT::Crypt->DrySign( $address ) ) {
+    } elsif ( not 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'};
     }
 }
@@ -165,7 +163,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 ef22931714ce7bcbbe2f2a10aef52231a242c780
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 1f79942..648a27f 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2062,7 +2062,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 218025cfd0c96dd45f6d75979ba7652d86012acc
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 61576b83d7ce61ac53ebbaab4103792b2ef33ac8
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 3e54aa8..a25860c 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -74,6 +74,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"fnord\r\n",
     \$buf,
@@ -116,6 +117,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"clearfnord\r\n",
     \$buf,
@@ -158,6 +160,7 @@ run3(
         '--default-key' => 'recipient at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'recipient',
+        '--no-permission-warning',
     ),
     \"orzzzzzz\r\n",
     \$buf,
@@ -243,6 +246,7 @@ run3(
         '--default-key' => 'rt at example.com',
         '--homedir'     => $homedir,
         '--passphrase'  => 'test',
+        '--no-permission-warning',
     ),
     \"alright\r\n",
     \$buf,
@@ -278,6 +282,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,
@@ -315,6 +320,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 2ee41a3a6688be86e976c284ee610290f2fedd52
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 f9441e0..e7a92ed 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -613,13 +613,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 fb1b24e2504aff87ebc174a0260629715fd0d23a
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 e7a92ed..9d4401d 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -627,7 +627,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 92ef0d77f70a7a9863906201f81926180e745db8
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 f121719..5777fa9 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -271,6 +271,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 = @_;
@@ -278,11 +322,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,   "");
@@ -290,37 +335,37 @@ Set( \@LexiconLanguages, qw(en zh_TW 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";
 
         my $plugin_data = File::Spec->rel2abs("t/data/plugins");
         print $config qq[\$RT::PluginPath = "$plugin_data";\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;
 
@@ -333,13 +378,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;
 }
@@ -1159,6 +1204,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)) {
@@ -1181,34 +1241,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;
@@ -1334,6 +1379,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 c7cb0959d7ffd4a2137420d901549a6b13ddcd33
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 58f24afb1747089c0aec37a31c405c83932760a8
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 d63d3e3..344cda8 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -1413,6 +1413,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
@@ -1424,6 +1428,8 @@ sub Gateway {
         next unless $check_cb->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
         );
 
         $skip_plugin{ $class }++;
@@ -1435,6 +1441,8 @@ sub Gateway {
         my ($status, $msg) = $Code->(
             Message       => $Message,
             RawMessageRef => \$args{'message'},
+            Queue         => $SystemQueueObj,
+            Actions       => \@actions,
         );
         next if $status > 0;
 
@@ -1490,10 +1498,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 05a36081910be70fb602005d6cbb627afa53f394
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 a63db593bda55893bae7d85caca837eb42bb7f7e
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
+
+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
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/keys/recipient.crt b/t/data/smime/keys/recipient.crt
new file mode 100644
index 0000000..19768ac
--- /dev/null
+++ b/t/data/smime/keys/recipient.crt
@@ -0,0 +1,124 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 2 (0x2)
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=RU, ST=Some-State, L=St-Petersburg, O=Test, OU=test, CN=Tester/emailAddress=test at test.com
+        Validity
+            Not Before: May 23 07:41:48 2007 GMT
+            Not After : Jan 29 07:41:48 2021 GMT
+        Subject: C=RU, ST=Some-State, O=Test, CN=root/emailAddress=root at example.com
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+            RSA Public Key: (4096 bit)
+                Modulus (4096 bit):
+                    00:af:b6:6f:04:9f:81:cc:44:a4:b1:f1:0c:c1:7c:
+                    d7:41:9d:8c:10:df:dc:f2:8f:b5:13:ce:f6:03:8d:
+                    0b:b3:f0:bf:b8:dd:cc:f6:4d:26:8c:41:9a:cd:08:
+                    60:36:68:0d:4d:5c:c3:b5:2a:71:cc:39:de:19:19:
+                    61:fb:80:64:5c:ac:64:b8:66:c2:2d:77:2b:8e:5c:
+                    88:82:41:51:f8:d8:8b:35:4f:80:66:57:8c:2e:a4:
+                    cb:4b:4f:c3:38:69:ec:43:c7:00:00:b0:0c:db:01:
+                    50:d2:2c:6e:b0:a5:83:8a:dc:aa:0a:84:a4:4c:82:
+                    f3:77:e1:a9:a2:cc:20:36:80:65:86:62:2b:50:4a:
+                    2c:86:d0:c5:f0:52:7b:ca:5a:04:a9:ed:28:57:bc:
+                    07:e5:e3:a1:a7:60:01:69:09:eb:e5:2c:75:61:53:
+                    4a:e7:00:3b:b6:e9:3c:e2:d2:b3:99:76:d9:5f:70:
+                    a4:7f:3c:d1:46:1e:8e:ea:9d:a3:c0:95:d5:a5:45:
+                    ba:68:64:fb:92:5c:23:05:2e:36:50:aa:0a:68:ba:
+                    23:cd:92:29:42:f8:d6:d8:14:09:e7:f2:52:c5:f1:
+                    9a:c8:9a:aa:4e:bc:26:9f:19:00:bf:46:37:d4:3d:
+                    f3:b8:72:aa:b5:05:a5:94:31:2c:dc:9c:bb:e3:a1:
+                    1e:82:03:ca:29:b5:d4:64:43:51:4c:f2:77:04:bb:
+                    43:97:a3:a5:e7:56:13:47:4c:02:36:db:d9:53:20:
+                    da:bb:a6:d9:54:36:dc:76:15:e0:eb:d7:50:9c:72:
+                    66:6c:7d:59:2f:dd:51:d6:7c:b6:2d:f3:0e:f9:7a:
+                    cf:0f:63:59:3a:6b:8e:3f:c7:76:b0:11:1b:12:e5:
+                    b9:90:66:30:d2:f6:ba:04:de:33:27:58:61:8f:ed:
+                    ed:14:b8:df:2e:5a:b5:cc:ff:a3:72:1c:ef:46:09:
+                    37:1c:81:ca:13:e9:43:9f:38:fb:69:bb:64:21:9e:
+                    e4:b4:44:be:79:61:52:46:cf:a5:83:7f:b5:c7:91:
+                    5b:28:92:17:d2:5e:45:4b:ad:9f:c7:53:fb:1c:3c:
+                    e7:56:92:a7:9f:e7:e3:59:19:36:64:8c:8c:78:00:
+                    2a:f7:93:2d:79:90:91:e1:73:31:24:ca:4b:4d:9b:
+                    1e:35:61:8a:3f:0c:f8:c8:c0:76:6f:01:d8:2b:c1:
+                    e0:0a:f1:fb:c7:a0:2b:48:30:d4:30:bf:47:2b:e5:
+                    b9:8d:64:e1:d4:3a:80:71:60:1e:02:02:d2:1a:f2:
+                    2c:b2:88:d6:21:da:27:83:36:6c:51:45:e3:72:39:
+                    83:16:65:5c:58:e6:14:87:02:95:88:07:f9:b3:20:
+                    58:d9:91
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                OpenSSL Generated Certificate
+            X509v3 Subject Key Identifier: 
+                48:07:DC:D8:10:D8:20:53:F5:7E:23:52:84:81:E7:2C:F3:16:BC:DB
+            X509v3 Authority Key Identifier: 
+                keyid:93:36:60:EE:DE:2D:B2:D2:D2:92:A3:FA:B0:62:EC:37:7E:7E:2C:F2
+
+    Signature Algorithm: sha1WithRSAEncryption
+        74:de:e4:e3:4c:54:07:25:ed:d5:a1:e2:7e:8b:63:1d:a7:2a:
+        e9:73:84:08:dc:00:33:99:12:60:73:c1:a7:a8:01:fa:48:35:
+        87:81:97:7a:86:fc:6d:67:fc:9e:ce:79:52:7f:7e:86:bf:ca:
+        08:cc:2d:58:ac:9b:49:d0:97:bb:75:82:79:93:52:e8:f1:26:
+        eb:1a:90:c3:1c:c3:23:6b:d2:18:a4:d4:11:2c:ac:7a:60:ce:
+        e8:77:b1:73:1a:0a:31:41:02:76:38:78:ef:95:cb:0e:60:35:
+        9e:6d:1f:dc:3a:fc:77:e2:dc:67:34:65:f2:c5:78:c2:96:f7:
+        d7:1a:24:66:61:5c:fb:b9:14:13:7e:5d:81:9e:7f:44:99:33:
+        db:9b:e1:89:89:d8:98:88:02:04:83:63:8b:c2:c5:cf:83:f0:
+        00:c3:81:47:e4:6d:c5:11:cb:13:6d:de:ca:37:f1:68:51:45:
+        ac:c2:8a:69:43:32:de:65:f1:10:09:e1:1c:60:6f:3b:49:39:
+        47:a8:bd:34:f2:a5:12:ec:dd:21:32:8f:41:9c:04:26:33:1a:
+        b3:1b:35:94:2b:69:fe:6c:b6:c2:6e:d0:76:89:09:51:27:e3:
+        f9:35:78:ee:e8:7a:28:3b:f6:60:42:55:82:bb:b0:9d:91:5c:
+        38:0b:9f:2e:02:c6:54:3c:55:0f:09:3c:37:81:52:48:12:1e:
+        42:6c:d7:e1:dd:e1:cc:13:60:fa:7a:e0:02:2d:7d:d9:4e:bb:
+        04:29:f8:8f:c3:73:17:f5:44:76:1d:cd:dc:12:39:0a:2b:a3:
+        fe:19:9d:4f:ed:c6:96:11:96:5a:b2:7e:88:f0:a4:5a:05:63:
+        d6:26:17:d2:98:22:59:f9:32:33:6e:a6:d8:ed:a6:b0:03:c4:
+        d7:18:1c:a9:67:b9:4c:d0:cc:23:f5:d3:36:f2:f4:93:4b:f2:
+        aa:06:91:27:c2:d8:a1:79:ea:88:58:e0:d3:6e:41:2a:2e:58:
+        48:7c:d1:a9:11:a0:c9:93:6b:9b:d1:60:e9:de:eb:a5:92:1c:
+        e3:e3:4b:96:01:6a:e8:21:01:c8:c7:cd:0b:fe:81:6b:8e:33:
+        4e:f0:05:17:c7:a0:de:9d:e6:78:88:7e:3c:1c:6d:51:04:49:
+        a4:4a:62:75:50:dc:55:a4:65:00:2d:c2:55:0b:16:39:24:e2:
+        f7:62:e0:75:d2:45:a2:60:94:36:84:ce:ed:70:9f:f0:eb:96:
+        b8:27:49:4f:a6:e7:52:49:7d:d1:8e:11:12:80:38:0c:92:ca:
+        3f:a2:2a:7b:87:a5:97:7e:a2:ba:b6:d7:ce:82:14:6d:42:52:
+        d5:17:b9:55:d2:a2:f6:0a
+-----BEGIN CERTIFICATE-----
+MIIF3zCCA8egAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMCUlUx
+EzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0LVBldGVyc2J1cmcxDTAL
+BgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRlc3RlcjEcMBoG
+CSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbTAeFw0wNzA1MjMwNzQxNDhaFw0yMTAx
+MjkwNzQxNDhaMGExCzAJBgNVBAYTAlJVMRMwEQYDVQQIEwpTb21lLVN0YXRlMQ0w
+CwYDVQQKEwRUZXN0MQ0wCwYDVQQDEwRyb290MR8wHQYJKoZIhvcNAQkBFhByb290
+QGV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7Zv
+BJ+BzESksfEMwXzXQZ2MEN/c8o+1E872A40Ls/C/uN3M9k0mjEGazQhgNmgNTVzD
+tSpxzDneGRlh+4BkXKxkuGbCLXcrjlyIgkFR+NiLNU+AZleMLqTLS0/DOGnsQ8cA
+ALAM2wFQ0ixusKWDityqCoSkTILzd+GposwgNoBlhmIrUEoshtDF8FJ7yloEqe0o
+V7wH5eOhp2ABaQnr5Sx1YVNK5wA7tuk84tKzmXbZX3CkfzzRRh6O6p2jwJXVpUW6
+aGT7klwjBS42UKoKaLojzZIpQvjW2BQJ5/JSxfGayJqqTrwmnxkAv0Y31D3zuHKq
+tQWllDEs3Jy746EeggPKKbXUZENRTPJ3BLtDl6Ol51YTR0wCNtvZUyDau6bZVDbc
+dhXg69dQnHJmbH1ZL91R1ny2LfMO+XrPD2NZOmuOP8d2sBEbEuW5kGYw0va6BN4z
+J1hhj+3tFLjfLlq1zP+jchzvRgk3HIHKE+lDnzj7abtkIZ7ktES+eWFSRs+lg3+1
+x5FbKJIX0l5FS62fx1P7HDznVpKnn+fjWRk2ZIyMeAAq95MteZCR4XMxJMpLTZse
+NWGKPwz4yMB2bwHYK8HgCvH7x6ArSDDUML9HK+W5jWTh1DqAcWAeAgLSGvIssojW
+IdongzZsUUXjcjmDFmVcWOYUhwKViAf5syBY2ZECAwEAAaN7MHkwCQYDVR0TBAIw
+ADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUw
+HQYDVR0OBBYEFEgH3NgQ2CBT9X4jUoSB5yzzFrzbMB8GA1UdIwQYMBaAFJM2YO7e
+LbLS0pKj+rBi7Dd+fizyMA0GCSqGSIb3DQEBBQUAA4ICAQB03uTjTFQHJe3VoeJ+
+i2Mdpyrpc4QI3AAzmRJgc8GnqAH6SDWHgZd6hvxtZ/yeznlSf36Gv8oIzC1YrJtJ
+0Je7dYJ5k1Lo8SbrGpDDHMMja9IYpNQRLKx6YM7od7FzGgoxQQJ2OHjvlcsOYDWe
+bR/cOvx34txnNGXyxXjClvfXGiRmYVz7uRQTfl2Bnn9EmTPbm+GJidiYiAIEg2OL
+wsXPg/AAw4FH5G3FEcsTbd7KN/FoUUWswoppQzLeZfEQCeEcYG87STlHqL008qUS
+7N0hMo9BnAQmMxqzGzWUK2n+bLbCbtB2iQlRJ+P5NXju6HooO/ZgQlWCu7CdkVw4
+C58uAsZUPFUPCTw3gVJIEh5CbNfh3eHME2D6euACLX3ZTrsEKfiPw3MX9UR2Hc3c
+EjkKK6P+GZ1P7caWEZZasn6I8KRaBWPWJhfSmCJZ+TIzbqbY7aawA8TXGBypZ7lM
+0Mwj9dM28vSTS/KqBpEnwtiheeqIWODTbkEqLlhIfNGpEaDJk2ub0WDp3uulkhzj
+40uWAWroIQHIx80L/oFrjjNO8AUXx6DeneZ4iH48HG1RBEmkSmJ1UNxVpGUALcJV
+CxY5JOL3YuB10kWiYJQ2hM7tcJ/w65a4J0lPpudSSX3RjhESgDgMkso/oip7h6WX
+fqK6ttfOghRtQlLVF7lV0qL2Cg==
+-----END CERTIFICATE-----
diff --git a/t/data/smime/keys/recipient.csr b/t/data/smime/keys/recipient.csr
new file mode 100644
index 0000000..aeec080
--- /dev/null
+++ b/t/data/smime/keys/recipient.csr
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEvTCCAqUCAQAwYTELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUx
+DTALBgNVBAoTBFRlc3QxDTALBgNVBAMTBHJvb3QxHzAdBgkqhkiG9w0BCQEWEHJv
+b3RAZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCv
+tm8En4HMRKSx8QzBfNdBnYwQ39zyj7UTzvYDjQuz8L+43cz2TSaMQZrNCGA2aA1N
+XMO1KnHMOd4ZGWH7gGRcrGS4ZsItdyuOXIiCQVH42Is1T4BmV4wupMtLT8M4aexD
+xwAAsAzbAVDSLG6wpYOK3KoKhKRMgvN34amizCA2gGWGYitQSiyG0MXwUnvKWgSp
+7ShXvAfl46GnYAFpCevlLHVhU0rnADu26Tzi0rOZdtlfcKR/PNFGHo7qnaPAldWl
+RbpoZPuSXCMFLjZQqgpouiPNkilC+NbYFAnn8lLF8ZrImqpOvCafGQC/RjfUPfO4
+cqq1BaWUMSzcnLvjoR6CA8optdRkQ1FM8ncEu0OXo6XnVhNHTAI229lTINq7ptlU
+Ntx2FeDr11CccmZsfVkv3VHWfLYt8w75es8PY1k6a44/x3awERsS5bmQZjDS9roE
+3jMnWGGP7e0UuN8uWrXM/6NyHO9GCTccgcoT6UOfOPtpu2QhnuS0RL55YVJGz6WD
+f7XHkVsokhfSXkVLrZ/HU/scPOdWkqef5+NZGTZkjIx4ACr3ky15kJHhczEkyktN
+mx41YYo/DPjIwHZvAdgrweAK8fvHoCtIMNQwv0cr5bmNZOHUOoBxYB4CAtIa8iyy
+iNYh2ieDNmxRReNyOYMWZVxY5hSHApWIB/mzIFjZkQIDAQABoBcwFQYJKoZIhvcN
+AQkHMQgTBjEyMzQ1NjANBgkqhkiG9w0BAQUFAAOCAgEAbD64fjpgPl4mN18HfZQP
+7EyK7cJew7iI0dLmP4rQmVLqWjl+xlvvZdYTbO+Y2OZMU7ihWb2Axcxd/oR7CxZe
+UL43yCWX5eXI0c07/7LGWSNUMdIE7GvOVkRt6o91TLBTLtr/pBoRj368S7SgpajW
+7cykGZroK84+zd2oNjt2aj6j82M+X5limFpEmqzD+wMlSjyNHVVky1RjWQs8wqvr
+Dqmr638aodc7N0KCI/5dfpIYDiHvhUaI8JjvUivFmS6KkYXF7q5uO7poSqs6LL0A
+3FBEnKprFVWkUs19PLfkEoRC7XPoqP5hZfBFPe4eKJvI0hoVlpNtgCf8O9mwMSM0
+AQNtbzZnNWx3ol4eFw3c0MYUCcIMCShtma0oCGbQkgd6NRkg81iMfqw+OevDde1J
+WarhQUsnejt1EUdjpUse/PwoyPSW02Vh4fu+1xrCL8oafPmLy2mxLRJ/qolNvNnG
+Fg3/CM8c7CRnbqStvOD1LdRxIiu0cuPvmsYFQ861EN7cACuQpjL+7nVAffAqamvp
+I7f5F4Qa4//6nfkbxJZZPd2qZyLruhFSdV4kSN8azUOu3ZW+OE/j7TbEdIpzfXZL
+XneNneGvM1jhgcSiqWGG7U6XmU+XT8WbBTtLM+TNOWdFNR/ex6wXsEgatDqesin3
+mJ4yWERo/b9OHNKXP70P/ks=
+-----END CERTIFICATE REQUEST-----
diff --git a/t/data/smime/keys/recipient.key b/t/data/smime/keys/recipient.key
new file mode 100644
index 0000000..1c6e0af
--- /dev/null
+++ b/t/data/smime/keys/recipient.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,5260BC3BD106CEDF
+
+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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-----
+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
+fVryqg==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,D9CF9FD45D1E5DB6
+
+zVl1wndooucgnGLNMqlyKel4sHt682eFMAu6nUipUgOAgDherDUF8lrQk0clhWd1
+UiE7nvXb5QURmJoQ3VVxVYVNQbUj78JBiofLHhGIZxF7vt1raKj676zFP4QPNqQj
+mFu6BLYfKFXjf6HR9QN9TxuTse748jPh0bWuoW3hWS0S4hihaXqQXviw5qNeHnY8
+UxjQ2jC72mIOgN7lz/5E/86XZTa02nIOK/Y7wCikWAxPGwQYcx6eeKxFM8JS3Kde
+ACyctnsRq/GQkFn3q/E+GmCQSQLHUdH5dd6sZ90f25PvERpsP39QPg6hsat1MC44
+CBhl//7fY6uBVjsy2PDkhHp16M+ENBQJENHhkxj49peTKuWzqIzA+yKCELXQ/j5f
+hBnseSFkca0RppPrnV79PTtHuRdeQzeJOWK2tlkIPEaUqlEW5UaQ6VViuqDgop7E
+1cSrNhsnUwLTNlHFymLIcCodL7rmbR+h4YLZ54wWAo16aOZnFDiHCB6EufAWGR9C
+P9tpC+1e2a1FQkVGCPAptyPymF6Cbvzf4rJIPq/oDat9OZQZTvpTI+owIhImQIXH
+SgfXkbYrhOaIURLudvt1lmRHh5mfmNGC6djJ1DUEenlhQNO0RWYKcqMMVa308Bab
+bZVrhPl0l+f00LiWcbR6H2VHEpPIOnycZBxmrMzXMe9buI9QrzlsjV9RRYSA83rW
+frXI5S61ryimXxUYIMoCk0XCO+Y69W6cZ8f9yJpYocQzXaIF3uYBLb3h7NNFGk0t
+qGR26+0M2+Oqm7asfJ4hRRHIAaUtPP78qCPxo2xY7RtAQYidgIhX73dQQjN4vYfa
+BRvV9S8qAyQoNdSIqhPOI4tMFeP/eKlSOjEGkQnoScY2vhCZkdEWlyoZej1gpf+Y
+q2PhXQDSrVzmqskEUNWmhN5LUDVTnT+phxwyQfOthdWRIYg88lg0hKmgS6IV7fU3
+/fcDpPiklITVmtQ6FQnCQzFnnaIcexL3UPr+KVQkwxsGTShf+vgi/VnPzVVVK39x
+hgWeO+vb0YPuABylKsEXdURlyXehOIM+sFLNzz8s5XAYpE0QthmxHtUGHnDp+Zwx
+1jiwH+pXGV/7mZfRiON69o3fZxkrVYojd8rYHcQl2oRdT5vT9cm52VX/plksWLnX
+TW/d25nrx7fP/cW9FDdJoWrrouGC6B2/Gllfgtd0Tf3uXMV4M7ocVIv16U8W8UDW
+3Cb7cKfHHpbDFR2MJWkDhMz83KYs9oLKBvU9AXdCuOMnzqdpiMDiHOWeX3qf5v1S
+Fz3gxvmClp6igdpAjdMCVdb7vI5DqPa+ir5Sd5/dskkTU06BnzvDoysnYlTcp25C
+a9zgif0+jSPW6npGY7wvII28Tulgq/hJqlulU7IiI5CxIYUp2na7l4whwmKZMf50
+hj8RLuY/lru5mDNSdJyBPaAm2/4/DTor7M3IGDGQi6uusoxPVQ5KEJ9RQ2h5MuqU
+2gg1hdwsk1+Y6I9wbBLLrKktSiXsvekbC4HGzAUfSBS3o5Fy/SdXfOrkdglSpXzZ
+lZF7W+wyyZE0tT6Tc52AGq6vZPhkcXDScFdniT200TEYSADp+SDFV1B8sxdLIEyK
+lh6bhEuT859oeT9lzTr/Djr84Y3HMjdXchEU6RY1bHLovOTdZVE3PbfVzgf93VWy
+LqraYR/1zpxmCLCWTrOdjSn0MMHnTQSViKmfJyLpa5p2r6098ImZDDhowzbpU3nn
+UnARcvQIcS6Ci5GyZ9PwYbnaQWeM/lJO3vU53Guk2OjWeVuP8TQOi2eRPIqh3TOw
+df9EkPh4g7rddbEipesIIBDcS2QB5Em7WZ4i5XJ9/4XqdMmcT4ptkHOQVonzLFRQ
+ssZps5Hot0doydbclaJ8h6b6T7YgiUOivjH/cT4vjkmwOD4JT8ZXOlNLe6SoIMfX
+gAZD69K4ZnfLezZ15fO6l0WSFcbAG84G+fJiBcmwC1cc7fo8M7LJTxd2Inzqge1d
+JPeNPFPTayEY99uF+1P6li2EEexleJS0q+KHMoLCldL5DHOotzEoCejQCni1/YZc
+mBJb7DS+qcVU/Mzn/JKpPuZEZIJJGCu/oV3wu4DvCktfL+6JIwiBNWTTPyBY+M8e
+kEKK2fAYtLuAGVtGbbSh35BJiGBgxFUc7RC1fuGHFGoRzun4LcGossMRzYrU1jP5
+GQkbkJvBN4shCgdYCckLwSL+dZK4k3gmYqvzwZvIp+E1+5BgbwlBruAKMMB+A5id
+T1aOG/llQxbpeVk9sNuUZm10FZhTv6buaqM7uAQf4vSekLoTLlUmUkzzJRErJQiQ
+97IKe5vwj3XumyDTq5nGwXklIpRdX3BNN6O8edPQf5xxpeOhh/BZkB1pJdVcKbf7
+qGf2xoStC4KgZ7sJn7sn2ypJjrgNAk9JUtpxJOYF9Kylq1h3Z1EIC4yNGadcn8l7
+SEUsYO7Bw/Vaqq8JsQWfAEFwCmz37aaZpQmpfB2/OyKp+IbmU3og3aek2S21QfFy
+u7gWk6LEvfzVhh8OPmGlgCNJOopkxv5bwlFTQNFGI8YY0kQrWgdNdtlQcPFSl6kt
+fu12/s0T5k56SKvM5QdVfUa3c8dDPq6H4aHlBZnSK74KBS1tvdxWsh6lY6sZdt7c
+c/sBXxaS5AgtTuwjc8HOSOQhv/+hvUnRbDZROBzk6Hp/QWpX13yD9r91eCYysQ80
+10uc7dsH9mRpkaGQA3QPZlD6Ld/2p8h/Hsf/rKQsyOzswdZokmExLnRgVUhVoJ/d
+SoLVfBghHWAD/hvUKhBBPEwp8m9OKm8BF8jF5HsbTL12sBiUeOLpyrJTkRHuiZQn
+Pte4hEimyi835bCop4YbzCDVpbuh1RCgHDpkSSGwqpbiaVJbQCNwGPu9WLezDGl8
+IBwdJDmeg8obAhD+PVETY/wZOaAaE1zrDyoB6Stp5u6bWmO8r5xhrEkjJg2waNpb
+3R/vN6O7G49ZZn+f2D8AI50Fu6LWDuBXKA5IhAH0FQhex48f5Idklvazug+Wxaqp
+zJA1jo+DsmjWuUzcB1PLQr+2zi7PyuhjWKcJuPIz3JD2SYdi/wkyJWNotmiB9heL
+gBMMRpteVNXi0MfCwE5RELU6LUPG4MTon8F+k/I7Y78v4vTiX6/B3ouK9RHCa1d+
+-----END RSA PRIVATE KEY-----
diff --git a/t/data/smime/mails/README b/t/data/smime/mails/README
new file mode 100644
index 0000000..6bb0ee9
--- /dev/null
+++ b/t/data/smime/mails/README
@@ -0,0 +1,16 @@
+Files with MIME entities generated and encryted with testkeys/sender.crt
+in real MUA(look into files for User-Agent)
+
+* simple-txt-enc.eml
+    simple plain/text message with 'test' content
+
+* with-text-attachment.eml
+    multipart message with a text part and a text attachment.
+    Content of the text is 'test'. Name of the attachment
+    is 'attachment.txt' and content is 'text attachment'.
+
+* with-bin-attachment.eml
+    multipart message with a text part and a binary attachment.
+    Content of the text is 'test'. Name of the attachment
+    is 'attachment.bin' and content is 32 random bytes.
+
diff --git a/t/data/smime/mails/simple-txt-enc.eml b/t/data/smime/mails/simple-txt-enc.eml
new file mode 100644
index 0000000..df38c4a
--- /dev/null
+++ b/t/data/smime/mails/simple-txt-enc.eml
@@ -0,0 +1,36 @@
+Date: Fri, 22 Dec 2006 05:01:04 +0300
+From: root at localhost
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
+User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
+MIME-Version: 1.0
+To: sender at test.com
+Subject: test
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggQ8MIIBjgIBADB2MGIxCzAJBgNVBAYTAlpBMSUwIwYD
+VQQKExxUaGF3dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVy
+c29uYWwgRnJlZW1haWwgSXNzdWluZyBDQQIQBgzQJigV55Om1dc1u4CuYzANBgkqhkiG9w0B
+AQEFAASCAQBEI9JKBQgIqLcRTiYCwDHR1dVTnlOoAAVHoM10dHxzszYBNV5GBnQgPVzV5EbU
+kkjKPJy1Ipv6Eixoqed2u54/68fmvGSEC+zH8nu7noMbvF7nuspPfwawf9GQNq3jt3qZRuuk
+Us2EB5GGz8p9gxgFnv/GtrUQ+7HxCVvJRwFuyXkqwfqo2kxnE4C1jS05xjZ3ioo5gQlncC5f
+ib63YtU6Gvlnh9zq/LV0bMUg6SygRNAHoO+BTKBBHlzNMg0ixUESzRmxF1hJA3rbhCx4xPfe
+OHZh6NtSHPSD+88nzK3qBv1Gosz9In6O5/aYreQgLT6Vrbb+jlCPs3BJGaRPNwVKMIICpgIB
+ADCBjTCBhzELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxFjAUBgNVBAcTDVN0
+LVBldGVyc2J1cmcxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBHRlc3QxDzANBgNVBAMTBlRl
+c3RlcjEcMBoGCSqGSIb3DQEJARYNdGVzdEB0ZXN0LmNvbQIBAzANBgkqhkiG9w0BAQEFAASC
+AgAqLoqQoTHSNgSyp+8XuylkokpE8/zE8mQ0TVnVrP8LrK8ppxBAkVr8/GGG2BNtwKXJEaEP
+7cDoHYABQS4xQD3CrEBtq180rDR0yGunRYuCTlBrAAZWy8Nq3/KGbwxJJBaqHUomaqiRUeiD
+j+V42pU1hVhBVUR+dNlDrtnLyh1OhbR1/ddhU0WCioAAdbyVCntgyHQU0Sr55xVINP69RUte
+OCHQj+s5b6HnfN2pQjdjZf5pHJdCajO5IpKn68LaBY2Q0YZVZYs3PLnRe0yHpZvHa7T5KRNs
+XL4fkJ5n6wrP48UH2eHyopHCww28qZupbnSkhZCERa7mJ3niExaRxnZnS28IpaU4AlbMLZGu
+fz7woFxcxrcwkGeGXCTxZkj6UkXXFZK/s04iWoQdcN59A56yGc0RTHg29AHQshJ2d3Ydcm+N
+/Uv9OwSkDOG/V6f6BvmWjBldF6SI2sOmEEK+SQy0OC81TYJfqVHRfT0lyUfz43JGGwOhgb1j
+hyxBgugzGq2+KkIdyONMdBZXe4HuZOm4MStB/5NOdb1lPttB5zgkcdq9I/rVl+QRSh3wjZWf
+2JL9HvlljweVNbVoCiwQjm17u14PnNlW0797YTXizHlsLUpupMgI8N2eDKv53cKTGyT/z+yc
+AmqCYdph3oA7VWlXBvQbZUeDxOarpfTnq+aOEjCABgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcE
+CBUAZSknCBIhoIAEYHg/qMyTJn4qoHtG5PnCve9BA5m+02B8RFLe1EQ/4+S3r2tP8jSvCPvk
+jMrzK7wrza2xlXaOisFvakPMyjTqwCkup55n3LDELbXMe3eFt62L7mWSD1HI+bwCEM2d7v/5
+DAQIbHw1VJzs8tsAAAAAAAAAAAAA
diff --git a/t/data/smime/mails/with-bin-attachment.eml b/t/data/smime/mails/with-bin-attachment.eml
new file mode 100644
index 0000000..4f4f89d
--- /dev/null
+++ b/t/data/smime/mails/with-bin-attachment.eml
@@ -0,0 +1,45 @@
+Date: Fri, 22 Dec 2006 06:41:22 +0300
+From: root at localhost
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
+User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
+MIME-Version: 1.0
+To:  sender at test.com
+Subject: test
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+AA==
diff --git a/t/data/smime/mails/with-text-attachment.eml b/t/data/smime/mails/with-text-attachment.eml
new file mode 100644
index 0000000..e18c759
--- /dev/null
+++ b/t/data/smime/mails/with-text-attachment.eml
@@ -0,0 +1,44 @@
+Date: Fri, 22 Dec 2006 05:24:50 +0300
+From: root at localhost
+X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; uuencode=0
+User-Agent: Thunderbird 1.5.0.9 (X11/20061221)
+MIME-Version: 1.0
+To: sender at test.com
+Subject: test
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+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
+qA5aPVUAAAAAAAAAAAAA

commit b59246eb60e4525779db1d160c12d838f21cf88e
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 09c2acd944a71b9cfc460ca104a120feacf57124
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 783504def6f52896982ff136d7b7badd0288d0ac
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 aae0b359e098628e08539eda67f348f86fa71248
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 5777fa9..53da1ea 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -319,9 +319,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: $!";
@@ -333,6 +330,8 @@ Set( \$WebPort,   $port);
 Set( \$WebPath,   "");
 Set( \@LexiconLanguages, qw(en zh_TW 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 893fcc99f6543e2a2a6aeedb507fc49da0d01a6d
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 e0391a528c6aa9db49426b58b3e1c3b4fb99158d
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 648a27f..0f3b784 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2066,72 +2066,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 a2877b325b19c57afb797c45a3a42a838f5ea317
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 0f3b784..110c749 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2040,8 +2040,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 e6b8d81e24e98abed435c67760db61403e867b4f
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 110c749..efe55b9 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2164,7 +2164,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;
         }
@@ -2177,7 +2177,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;
         }
@@ -2185,7 +2185,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;
         }
@@ -2263,22 +2263,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 032cab5a18a2a9c57688af8827b848df054d39dc
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 d8abee5..eaccb19 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 8dd0a0a20d6adc5768a688e1e53c28c4322402f6
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 bf6bf7ed24f09d5c4d70e9837194b5d6c99901a9
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 344cda8..1fd8066 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -397,7 +397,7 @@ sub SendEmail {
         $TicketObj = $TransactionObj->Object;
     }
 
-    if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
+    if ( RT->Config->Get('Crypt')->{'Enable'} ) {
         %args = WillSignEncrypt(
             %args,
             Attachment => $TransactionObj ? $TransactionObj->Attachments->First : undef,

commit d941ed52e6402be81794401ac32bb8c82a169ab3
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 801bc53..307f14d 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/GnuPG/SignEncryptWidget
@@ -169,7 +169,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 75e604a..8a27c70 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 e06f5667d39fbe6968f9586eb676b5849389de78
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 a25860c..fe4d036 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -191,7 +191,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 1743a3a..202781c 100644
--- a/t/web/crypt-gnupg.t
+++ b/t/web/crypt-gnupg.t
@@ -101,7 +101,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'),
@@ -169,7 +169,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'),
@@ -241,7 +241,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'),
@@ -307,7 +307,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 8d58bfa308456a66e8008b0a0dab1b90208dd682
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 395b5c037f594a901b130e3ce328c24d7de3bc32
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 efe55b9..c4c7be8 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2364,31 +2364,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 
@@ -2458,6 +2433,8 @@ sub _make_gpg_handles {
 
 RT::Base->_ImportOverlays();
 
+1;
+
 # helper package to avoid using temp file
 package IO::Handle::CRLF;
 

commit 9840e4486cce4c7c89d33523d31ba7b3cf7c072a
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 50ea8da4dc867e3d45f117d15dd606d17e5097c7
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 5375f65082509d1dc41ae79ec1fa6951496d95ab
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
-
-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/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 f6a01ba1cd6f89a7123b2ebeecc5e3ac5f9169ef
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
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggHIMIHhAgEAMIGKMH0xCzAJBgNVBAYTAkFVMRMwEQYD
+VQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxETAP
+BgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBleGFtcGxlLmNvbQIJ
+AIpqzVG+lKAVMA0GCSqGSIb3DQEBAQUABEBaD8gD14bNN6JP7//sSTSKfd8xt9qWPMhY9bua
+KfkSNpEiV0NIcdnJLJxMfgQ3ox6+eHOt5PlU67FgdStmUoHZMIHhAgEAMIGKMH0xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRz
+IFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkBFhRjYS5vd25lckBl
+eGFtcGxlLmNvbQIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBAQUABEAdwlvCFxp7N94tveDDJs6Q
+9hDYS7AMp+tc2Z0SNCQmCW803P+iVkZdEPJ0VIDvefAoqKfZlXwZKYydsN041UGlMIAGCSqG
+SIb3DQEHATAaBggqhkiG9w0DAjAOAgIAoAQI0HAJB8sjnyaggASCA/jxFMDm7gsDSM2rSbqO
++bPNrSatAQTjkYcCnqsExZKCB+P4IwBIODEQ3pbYL3AMF3STZTFEIKxQG4+3lfO3qoIfSWPK
+HRvabwW+trH3tVsgf+KkdVSfCXBfSV9sIdmXHIAJOSKUZvmbJ8iuQy6543v0InIgNsGgmH9M
+I+i5bkgWrJHXVn8N0iAH3Unkf0qJgb6E6lvqcbfOpgKzYJ0cBSn9Z00NXo0qw8DuORAeev6i
+EABlIzv/v7P/7ptvxDzlu+EoiMIiuo9t/aL3lKT27UK1yPUfEZTqevhMwXYzRHi6AQcsZGKJ
+33xpaVDkZYZQolQInBIlx23o04K0dm+iAcOau6xpWSvGwlI590MryKg0GIjeNOuy7CkEbZXX
+P7gF/ExBMM03Xoa93ss7Q3CJHey2xwC+Ozf4Zbny1wvbs54bT//Oin22jdtSOxDIa72vYH+F
+DKRdqHdSr6RKg4vnIKOxoOys+P4oKbJP66SOKni86XovDR/iVClu0lxHqjJKsW9r1p0O1iOT
++aNGMKaXkIa7UEKfVDPdaRUPMszjo8vjqZDWOV6aO8mg52bg/nnbbviVNtToqKweEJA8ellJ
+sxCtrE/lUerWPZG6d073bMQ2yl9i/2pF8UnEwZNF0vP3hMZ+k8w4uQbAX9BEcDKMKGB1x3FL
+GvgdzypGzzX/yk5UvTAA1KVT3+HDWCQHmH3fSB+8XyTcnlX4WGJ/oqWMU01C7GWEjj17q2CS
+8c11m9/IC0dBgc1iffkdIGMwjHBdbUNmuAmCM0qTMmzrFWrHiahLXhvzz3+X/oqbltemmlpQ
+GD/v699pN1vp6ito4qSmJ6WOrS0Uud6V9UOPDHXI3nQNBnI+IijHQVVL/H9FxuUYFrKsYupt
+ssdGrdrKb8+CmiMpuDp8w3QoCfxBf/Y+FykX/rBF6T5MJtS90LQtq/7iYv3sTS9PDqXBHW4+
+SgLSOcFH4vmodrP3nu+TnLWU33aboPBvdNzGO2CpOzGRsvft0QanTq3vhSwLG5Nhqiv7XHKE
+MRphztDRdNlxNQjEyV5q0ER/bEltrMtksFIcPKEWXfsNjbW4PTgjlsjLVBnMOkOU/dX812UK
+byJW7l0SrWo6bFrooP1pht0IyGWLSxmRMb4CivKgqQuLq+z869I479RlReVBwoYJ+bxoOjD5
+5RQUkljjEg/ZCo9OvVVgA9LFUhT3nATjHQJTqJKx6jZxITiy9EfXymX71SxDXyPsM9sdqrdQ
+SYX217aHveMarK6v9V8smV+LtxKq9Bgkm2ZbUiqZ0/oBcsIIca9HYC7hUxIXkQRvJBcsi7BB
+Lk7Xm/Egkcc9ZfcnGevgIntzdVYod6LpUodzYavdewQIiEXE/XWx8fgAAAAAAAAAAAAA
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 bc3a9d63768825c8b79657a0822724c0cc331713
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 c5ee14fd689fa83cfbc84f487f2a3da15464ab30
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 c4c7be8..f2ddaba 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -2434,16 +2434,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 c8201b612f8649406bc8b85ef9e592a27c17bcc8
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 46b6dabd2d03918c5c48a2c0b60121cf7415de67
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 620ea4b767f54d7b7d65f1444e259c1f594598e5
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 2e7ea1771657914923be86186fbc5d4ac5581354
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 cccc390858f06e67a4e96a0883701d156c7a2345
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 dfd9f8c817b398aa7337c10c8aa984414a71a325
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 c16e3fcb31d9e1ff2973cafad4e7d0f9b34fbc36
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 7a1aa05c928bfa23668a26190a3365d548f1d651
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 d06cb7b0ff896f2cc0999ab748cb9ecbba5ea72b
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 f660690724aefd221477849db897c4629412144e
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 0bbb400750241927a0dd2a55a479d193f285d5ee
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 7e2235eab5d0c3ba263367c9644ac7ab43965e27
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 07a12d29aeca399f2b80c49c38fcd9a4591f865b
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 b88c8394ccc68cf680c12da7250555797679f44c
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 da7f85647082cd45b4428cc6b33c16479d08af0c
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 084db40a36947386d7357b1eb12bf112c2b7525f
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 e179d676f7de4d608e5647967bfee4cac8b0c1f6
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 c439235e926b21e6e7e4f350350d11940507dcf3
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 bd304ba863f036f10873da49f76696a29c125a8f
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 36364b53c56d0b1b60eb294d78d11a0e98b83993
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 b8d9599e39b6d3700e6e32861c27d505aa4592cc
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 f2ddaba..5fc902e 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 fc1498bd5fdbce29ac60fecfceeeec2b0c2b2fc7
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 0405f86..3425dc6 100755
--- a/configure.ac
+++ b/configure.ac
@@ -323,7 +323,7 @@ AC_SUBST(RT_SSL_MAILGATE)
 
 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 7c032db08bfb88193011f30c2e1bad5291791e2f
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 317ed269ad5fcce2512d9622667c9e40bbd44390
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 b3d7b2cc285eb1d1552671e68cb7142c7c8283bb
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 6081c74..821d3f6 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2085,9 +2085,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 9448a9d3f1b53c3ae9fdb421b95aff33a515350b
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 821d3f6..774d5f5 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2109,8 +2109,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 094ed705b43065b6c4a2c2c57c9eca52f11fd544
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 5893b5b30d45a19383efca047f709ae013011c91
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 a930a3e..0dd160d 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 307f14d..e17c73d 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 53ade4f1926216c0aae25a63d04d581c68e101da
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 5fc902e..b22eefa 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1065,7 +1065,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 7d08701173a2b150ddc4fa50c580c63d322aa82f
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 086ba5f..bd03587 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -105,7 +105,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 a2d06bbe55f165ef5a99599bf6e234e5f437e6c2
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 bd03587..d012499 100644
--- a/lib/RT/Test/GnuPG.pm
+++ b/lib/RT/Test/GnuPG.pm
@@ -274,7 +274,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";
@@ -283,7 +283,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";
@@ -292,7 +292,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 b802c08c367f2dea41301b1fd66f68d040f27472
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 e083480f6d14ae812ae0e11446aeb2c808ad4352
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

commit b30eda0dc18ff84ac24f25d52f5e82a962ea32e6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 28 22:31:57 2011 +0400

    delete backup files openssl creates in CA directory

diff --git a/t/data/smime/keys/demoCA/index.txt.attr.old b/t/data/smime/keys/demoCA/index.txt.attr.old
deleted file mode 100644
index 8f7e63a..0000000
--- a/t/data/smime/keys/demoCA/index.txt.attr.old
+++ /dev/null
@@ -1 +0,0 @@
-unique_subject = yes
diff --git a/t/data/smime/keys/demoCA/index.txt.old b/t/data/smime/keys/demoCA/index.txt.old
deleted file mode 100644
index 1433378..0000000
--- a/t/data/smime/keys/demoCA/index.txt.old
+++ /dev/null
@@ -1,2 +0,0 @@
-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/serial.old b/t/data/smime/keys/demoCA/serial.old
deleted file mode 100644
index 7c4c23b..0000000
--- a/t/data/smime/keys/demoCA/serial.old
+++ /dev/null
@@ -1 +0,0 @@
-8A6ACD51BE94A016

commit e6c9b2938076183062d990c713022fa4c6e6ea24
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Dec 28 22:34:28 2011 +0400

    for simplicity allow non-unique subjects

diff --git a/t/data/smime/keys/demoCA/index.txt.attr b/t/data/smime/keys/demoCA/index.txt.attr
index 8f7e63a..3a7e39e 100644
--- a/t/data/smime/keys/demoCA/index.txt.attr
+++ b/t/data/smime/keys/demoCA/index.txt.attr
@@ -1 +1 @@
-unique_subject = yes
+unique_subject = no

commit fe13bea542ec77956943c8661430ec4a2f25883c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:00:34 2011 +0400

    update expiration date on SMIME certs
    
    hopefuly next 25 years is ok

diff --git a/t/data/smime/keys/demoCA/index.txt b/t/data/smime/keys/demoCA/index.txt
index 0afe6c7..9793965 100644
--- a/t/data/smime/keys/demoCA/index.txt
+++ b/t/data/smime/keys/demoCA/index.txt
@@ -1,3 +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
+V	360818214620Z		8A6ACD51BE94A015	unknown	/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=sender/emailAddress=sender at example.com
+V	360818214642Z		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/newcerts/8A6ACD51BE94A015.pem b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A015.pem
index 657491f..e3a9165 100644
--- a/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A015.pem
+++ b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A015.pem
@@ -6,13 +6,13 @@ Certificate:
         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
+            Not Before: Dec 28 21:46:20 2011 GMT
+            Not After : Aug 18 21:46:20 2036 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):
+                Public-Key: (512 bit)
+                Modulus:
                     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:
@@ -30,28 +30,28 @@ Certificate:
                 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
+        93:a7:14:3d:7f:47:6c:ec:71:34:29:19:e4:e0:0e:17:79:50:
+        df:58:31:d6:fb:b1:93:4a:a7:3f:95:44:62:fb:ea:eb:86:dc:
+        94:46:ff:2d:e5:73:37:e4:d3:d5:60:29:86:ea:75:1f:77:dc:
+        db:ed:f2:30:a7:b9:8d:ba:5f:06:55:6c:f9:95:c8:a1:32:ab:
+        33:0a:21:a2:70:86:9d:22:5b:99:53:3f:1b:3f:15:54:61:f9:
+        d3:3c:da:fa:c8:82:43:79:67:e2:a8:7e:78:1c:73:24:b4:a3:
+        76:e5:15:4e:4c:9f:32:f5:45:71:c5:46:79:28:7b:8e:fa:56:
+        0d:53
 -----BEGIN CERTIFICATE-----
 MIICqzCCAhSgAwIBAgIJAIpqzVG+lKAVMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
 aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
-FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjI1NDJaFw0xMTAyMDgx
-NjI1NDJaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMTEyMjgyMTQ2MjBaFw0zNjA4MTgy
+MTQ2MjBaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
 VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnNlbmRlcjEh
 MB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEB
 BQADSwAwSAJBAKg4QZAd580ry2LPrf9w9kRd80t+IXW2XOF+wic7hetym1qUCmkd
 g8rFkbI/BHJh5LjrW861EHfYp9+LyVoUFWECAwEAAaN7MHkwCQYDVR0TBAIwADAs
 BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD
 VR0OBBYEFPM05sNReJKbk0mOTjC8GIgkaTQJMB8GA1UdIwQYMBaAFI0bLb29JOgZ
-Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAFOQ223Qqg/LMpQupbzRDSd8
-hdgIbFL+sU0YlMIQ/0+PcSuBv6OqET5ucl9KGjigP18sidavxWz/WWMTLftaL1iy
-dz5SgwcBxk7PGZya8xesY8IA8xi5J6bVGhQR1vDbm97krc0pAf04wSGc+qILXm1d
-FFTOCjz3rw/mf8c5ALMz
+Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAJOnFD1/R2zscTQpGeTgDhd5
+UN9YMdb7sZNKpz+VRGL76uuG3JRG/y3lczfk09VgKYbqdR933Nvt8jCnuY26XwZV
+bPmVyKEyqzMKIaJwhp0iW5lTPxs/FVRh+dM82vrIgkN5Z+KofngccyS0o3blFU5M
+nzL1RXHFRnkoe476Vg1T
 -----END CERTIFICATE-----
diff --git a/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem
index 3085e41..e8aa87c 100644
--- a/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem
+++ b/t/data/smime/keys/demoCA/newcerts/8A6ACD51BE94A016.pem
@@ -6,13 +6,13 @@ Certificate:
         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
+            Not Before: Dec 28 21:46:42 2011 GMT
+            Not After : Aug 18 21:46:42 2036 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):
+                Public-Key: (512 bit)
+                Modulus:
                     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:
@@ -30,28 +30,28 @@ Certificate:
                 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
+        95:58:dc:1e:2b:c2:92:18:af:ce:a5:24:19:5e:04:c9:a6:ca:
+        01:26:d0:65:6a:89:78:fa:a1:e3:94:fc:f3:b9:32:20:dc:9d:
+        91:aa:9f:44:8b:ef:86:5c:82:dc:99:ef:4a:c9:34:6f:f0:2e:
+        77:55:a4:ed:1f:b5:06:be:e2:7e:1c:22:76:08:cb:68:e0:e0:
+        4d:6c:86:8f:37:90:6d:1d:9c:0c:c7:6a:67:3f:3c:47:68:f4:
+        4e:22:78:80:11:7e:0a:08:8c:fe:df:32:db:a7:d3:6f:35:cb:
+        5a:ba:00:0f:a3:6b:1b:4f:05:1e:97:e2:c8:e0:7f:3f:7f:51:
+        c7:66
 -----BEGIN CERTIFICATE-----
 MIICrTCCAhagAwIBAgIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
 aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
-FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjMwMDhaFw0xMTAyMDgx
-NjMwMDhaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMTEyMjgyMTQ2NDJaFw0zNjA4MTgy
+MTQ2NDJaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
 VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMTCkVub2NoIFJv
 b3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
 AQEFAANLADBIAkEAsne5vAl9FI5rb34zqZUhXfM8kWHxvFwdfudUJejLX7cYDiMm
 AEIJvYnaXAbLUghD9k7+3fgKipU1j0olFtrmvwIDAQABo3sweTAJBgNVHRMEAjAA
 MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
 BgNVHQ4EFgQUl9eAUsq74QJgk1NCohQS6O0fwHowHwYDVR0jBBgwFoAUjRstvb0k
-6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlV0hT8xiREmt93917rDo
-7AwlOXJNnZiGmYGBAqDRKz4rXAEUd4Gqvqf/mtu0tpyyLtfrTjK4ad16Pzw4UCJG
-5SEc/NC/BHlaW6xTdzO+J2Nu10fnL3UVhNuaCMAKtQB3Uf5k35eV7n8q+INyZ0rJ
-dX6m3eXWg8WmuQ0OJDnKBtc=
+6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlVjcHivCkhivzqUkGV4E
+yabKASbQZWqJePqh45T887kyINydkaqfRIvvhlyC3JnvSsk0b/Aud1Wk7R+1Br7i
+fhwidgjLaODgTWyGjzeQbR2cDMdqZz88R2j0TiJ4gBF+CgiM/t8y26fTbzXLWroA
+D6NrG08FHpfiyOB/P39Rx2Y=
 -----END CERTIFICATE-----
diff --git a/t/data/smime/keys/root at example.com.crt b/t/data/smime/keys/root at example.com.crt
index 3085e41..e8aa87c 100644
--- a/t/data/smime/keys/root at example.com.crt
+++ b/t/data/smime/keys/root at example.com.crt
@@ -6,13 +6,13 @@ Certificate:
         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
+            Not Before: Dec 28 21:46:42 2011 GMT
+            Not After : Aug 18 21:46:42 2036 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):
+                Public-Key: (512 bit)
+                Modulus:
                     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:
@@ -30,28 +30,28 @@ Certificate:
                 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
+        95:58:dc:1e:2b:c2:92:18:af:ce:a5:24:19:5e:04:c9:a6:ca:
+        01:26:d0:65:6a:89:78:fa:a1:e3:94:fc:f3:b9:32:20:dc:9d:
+        91:aa:9f:44:8b:ef:86:5c:82:dc:99:ef:4a:c9:34:6f:f0:2e:
+        77:55:a4:ed:1f:b5:06:be:e2:7e:1c:22:76:08:cb:68:e0:e0:
+        4d:6c:86:8f:37:90:6d:1d:9c:0c:c7:6a:67:3f:3c:47:68:f4:
+        4e:22:78:80:11:7e:0a:08:8c:fe:df:32:db:a7:d3:6f:35:cb:
+        5a:ba:00:0f:a3:6b:1b:4f:05:1e:97:e2:c8:e0:7f:3f:7f:51:
+        c7:66
 -----BEGIN CERTIFICATE-----
 MIICrTCCAhagAwIBAgIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
 aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
-FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjMwMDhaFw0xMTAyMDgx
-NjMwMDhaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMTEyMjgyMTQ2NDJaFw0zNjA4MTgy
+MTQ2NDJaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
 VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMTCkVub2NoIFJv
 b3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
 AQEFAANLADBIAkEAsne5vAl9FI5rb34zqZUhXfM8kWHxvFwdfudUJejLX7cYDiMm
 AEIJvYnaXAbLUghD9k7+3fgKipU1j0olFtrmvwIDAQABo3sweTAJBgNVHRMEAjAA
 MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
 BgNVHQ4EFgQUl9eAUsq74QJgk1NCohQS6O0fwHowHwYDVR0jBBgwFoAUjRstvb0k
-6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlV0hT8xiREmt93917rDo
-7AwlOXJNnZiGmYGBAqDRKz4rXAEUd4Gqvqf/mtu0tpyyLtfrTjK4ad16Pzw4UCJG
-5SEc/NC/BHlaW6xTdzO+J2Nu10fnL3UVhNuaCMAKtQB3Uf5k35eV7n8q+INyZ0rJ
-dX6m3eXWg8WmuQ0OJDnKBtc=
+6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlVjcHivCkhivzqUkGV4E
+yabKASbQZWqJePqh45T887kyINydkaqfRIvvhlyC3JnvSsk0b/Aud1Wk7R+1Br7i
+fhwidgjLaODgTWyGjzeQbR2cDMdqZz88R2j0TiJ4gBF+CgiM/t8y26fTbzXLWroA
+D6NrG08FHpfiyOB/P39Rx2Y=
 -----END CERTIFICATE-----
diff --git a/t/data/smime/keys/root at example.com.pem b/t/data/smime/keys/root at example.com.pem
index 9c1591b..915eec5 100644
--- a/t/data/smime/keys/root at example.com.pem
+++ b/t/data/smime/keys/root at example.com.pem
@@ -6,13 +6,13 @@ Certificate:
         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
+            Not Before: Dec 28 21:46:42 2011 GMT
+            Not After : Aug 18 21:46:42 2036 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):
+                Public-Key: (512 bit)
+                Modulus:
                     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:
@@ -30,30 +30,30 @@ Certificate:
                 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
+        95:58:dc:1e:2b:c2:92:18:af:ce:a5:24:19:5e:04:c9:a6:ca:
+        01:26:d0:65:6a:89:78:fa:a1:e3:94:fc:f3:b9:32:20:dc:9d:
+        91:aa:9f:44:8b:ef:86:5c:82:dc:99:ef:4a:c9:34:6f:f0:2e:
+        77:55:a4:ed:1f:b5:06:be:e2:7e:1c:22:76:08:cb:68:e0:e0:
+        4d:6c:86:8f:37:90:6d:1d:9c:0c:c7:6a:67:3f:3c:47:68:f4:
+        4e:22:78:80:11:7e:0a:08:8c:fe:df:32:db:a7:d3:6f:35:cb:
+        5a:ba:00:0f:a3:6b:1b:4f:05:1e:97:e2:c8:e0:7f:3f:7f:51:
+        c7:66
 -----BEGIN CERTIFICATE-----
 MIICrTCCAhagAwIBAgIJAIpqzVG+lKAWMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
 aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
-FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjMwMDhaFw0xMTAyMDgx
-NjMwMDhaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMTEyMjgyMTQ2NDJaFw0zNjA4MTgy
+MTQ2NDJaMHsxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
 VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMTCkVub2NoIFJv
 b3QxHzAdBgkqhkiG9w0BCQEWEHJvb3RAZXhhbXBsZS5jb20wXDANBgkqhkiG9w0B
 AQEFAANLADBIAkEAsne5vAl9FI5rb34zqZUhXfM8kWHxvFwdfudUJejLX7cYDiMm
 AEIJvYnaXAbLUghD9k7+3fgKipU1j0olFtrmvwIDAQABo3sweTAJBgNVHRMEAjAA
 MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
 BgNVHQ4EFgQUl9eAUsq74QJgk1NCohQS6O0fwHowHwYDVR0jBBgwFoAUjRstvb0k
-6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlV0hT8xiREmt93917rDo
-7AwlOXJNnZiGmYGBAqDRKz4rXAEUd4Gqvqf/mtu0tpyyLtfrTjK4ad16Pzw4UCJG
-5SEc/NC/BHlaW6xTdzO+J2Nu10fnL3UVhNuaCMAKtQB3Uf5k35eV7n8q+INyZ0rJ
-dX6m3eXWg8WmuQ0OJDnKBtc=
+6BlirkzJKliQCBzRBSswDQYJKoZIhvcNAQEFBQADgYEAlVjcHivCkhivzqUkGV4E
+yabKASbQZWqJePqh45T887kyINydkaqfRIvvhlyC3JnvSsk0b/Aud1Wk7R+1Br7i
+fhwidgjLaODgTWyGjzeQbR2cDMdqZz88R2j0TiJ4gBF+CgiM/t8y26fTbzXLWroA
+D6NrG08FHpfiyOB/P39Rx2Y=
 -----END CERTIFICATE-----
 -----BEGIN RSA PRIVATE KEY-----
 Proc-Type: 4,ENCRYPTED
diff --git a/t/data/smime/keys/sender at example.com.crt b/t/data/smime/keys/sender at example.com.crt
index 657491f..e3a9165 100644
--- a/t/data/smime/keys/sender at example.com.crt
+++ b/t/data/smime/keys/sender at example.com.crt
@@ -6,13 +6,13 @@ Certificate:
         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
+            Not Before: Dec 28 21:46:20 2011 GMT
+            Not After : Aug 18 21:46:20 2036 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):
+                Public-Key: (512 bit)
+                Modulus:
                     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:
@@ -30,28 +30,28 @@ Certificate:
                 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
+        93:a7:14:3d:7f:47:6c:ec:71:34:29:19:e4:e0:0e:17:79:50:
+        df:58:31:d6:fb:b1:93:4a:a7:3f:95:44:62:fb:ea:eb:86:dc:
+        94:46:ff:2d:e5:73:37:e4:d3:d5:60:29:86:ea:75:1f:77:dc:
+        db:ed:f2:30:a7:b9:8d:ba:5f:06:55:6c:f9:95:c8:a1:32:ab:
+        33:0a:21:a2:70:86:9d:22:5b:99:53:3f:1b:3f:15:54:61:f9:
+        d3:3c:da:fa:c8:82:43:79:67:e2:a8:7e:78:1c:73:24:b4:a3:
+        76:e5:15:4e:4c:9f:32:f5:45:71:c5:46:79:28:7b:8e:fa:56:
+        0d:53
 -----BEGIN CERTIFICATE-----
 MIICqzCCAhSgAwIBAgIJAIpqzVG+lKAVMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
 aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
-FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjI1NDJaFw0xMTAyMDgx
-NjI1NDJaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMTEyMjgyMTQ2MjBaFw0zNjA4MTgy
+MTQ2MjBaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
 VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnNlbmRlcjEh
 MB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEB
 BQADSwAwSAJBAKg4QZAd580ry2LPrf9w9kRd80t+IXW2XOF+wic7hetym1qUCmkd
 g8rFkbI/BHJh5LjrW861EHfYp9+LyVoUFWECAwEAAaN7MHkwCQYDVR0TBAIwADAs
 BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD
 VR0OBBYEFPM05sNReJKbk0mOTjC8GIgkaTQJMB8GA1UdIwQYMBaAFI0bLb29JOgZ
-Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAFOQ223Qqg/LMpQupbzRDSd8
-hdgIbFL+sU0YlMIQ/0+PcSuBv6OqET5ucl9KGjigP18sidavxWz/WWMTLftaL1iy
-dz5SgwcBxk7PGZya8xesY8IA8xi5J6bVGhQR1vDbm97krc0pAf04wSGc+qILXm1d
-FFTOCjz3rw/mf8c5ALMz
+Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAJOnFD1/R2zscTQpGeTgDhd5
+UN9YMdb7sZNKpz+VRGL76uuG3JRG/y3lczfk09VgKYbqdR933Nvt8jCnuY26XwZV
+bPmVyKEyqzMKIaJwhp0iW5lTPxs/FVRh+dM82vrIgkN5Z+KofngccyS0o3blFU5M
+nzL1RXHFRnkoe476Vg1T
 -----END CERTIFICATE-----
diff --git a/t/data/smime/keys/sender at example.com.pem b/t/data/smime/keys/sender at example.com.pem
index 1001c9c..3da7b8b 100644
--- a/t/data/smime/keys/sender at example.com.pem
+++ b/t/data/smime/keys/sender at example.com.pem
@@ -6,13 +6,13 @@ Certificate:
         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
+            Not Before: Dec 28 21:46:20 2011 GMT
+            Not After : Aug 18 21:46:20 2036 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):
+                Public-Key: (512 bit)
+                Modulus:
                     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:
@@ -30,30 +30,30 @@ Certificate:
                 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
+        93:a7:14:3d:7f:47:6c:ec:71:34:29:19:e4:e0:0e:17:79:50:
+        df:58:31:d6:fb:b1:93:4a:a7:3f:95:44:62:fb:ea:eb:86:dc:
+        94:46:ff:2d:e5:73:37:e4:d3:d5:60:29:86:ea:75:1f:77:dc:
+        db:ed:f2:30:a7:b9:8d:ba:5f:06:55:6c:f9:95:c8:a1:32:ab:
+        33:0a:21:a2:70:86:9d:22:5b:99:53:3f:1b:3f:15:54:61:f9:
+        d3:3c:da:fa:c8:82:43:79:67:e2:a8:7e:78:1c:73:24:b4:a3:
+        76:e5:15:4e:4c:9f:32:f5:45:71:c5:46:79:28:7b:8e:fa:56:
+        0d:53
 -----BEGIN CERTIFICATE-----
 MIICqzCCAhSgAwIBAgIJAIpqzVG+lKAVMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV
 BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
 aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCENBIE93bmVyMSMwIQYJKoZIhvcNAQkB
-FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMDAyMDgxNjI1NDJaFw0xMTAyMDgx
-NjI1NDJaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
+FhRjYS5vd25lckBleGFtcGxlLmNvbTAeFw0xMTEyMjgyMTQ2MjBaFw0zNjA4MTgy
+MTQ2MjBaMHkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYD
 VQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnNlbmRlcjEh
 MB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUuY29tMFwwDQYJKoZIhvcNAQEB
 BQADSwAwSAJBAKg4QZAd580ry2LPrf9w9kRd80t+IXW2XOF+wic7hetym1qUCmkd
 g8rFkbI/BHJh5LjrW861EHfYp9+LyVoUFWECAwEAAaN7MHkwCQYDVR0TBAIwADAs
 BglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYD
 VR0OBBYEFPM05sNReJKbk0mOTjC8GIgkaTQJMB8GA1UdIwQYMBaAFI0bLb29JOgZ
-Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAFOQ223Qqg/LMpQupbzRDSd8
-hdgIbFL+sU0YlMIQ/0+PcSuBv6OqET5ucl9KGjigP18sidavxWz/WWMTLftaL1iy
-dz5SgwcBxk7PGZya8xesY8IA8xi5J6bVGhQR1vDbm97krc0pAf04wSGc+qILXm1d
-FFTOCjz3rw/mf8c5ALMz
+Yq5MySpYkAgc0QUrMA0GCSqGSIb3DQEBBQUAA4GBAJOnFD1/R2zscTQpGeTgDhd5
+UN9YMdb7sZNKpz+VRGL76uuG3JRG/y3lczfk09VgKYbqdR933Nvt8jCnuY26XwZV
+bPmVyKEyqzMKIaJwhp0iW5lTPxs/FVRh+dM82vrIgkN5Z+KofngccyS0o3blFU5M
+nzL1RXHFRnkoe476Vg1T
 -----END CERTIFICATE-----
 -----BEGIN RSA PRIVATE KEY-----
 Proc-Type: 4,ENCRYPTED

commit ee18683f831f4153cc037c7ee2ce940064c28ecf
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:01:52 2011 +0400

    ignore a few smime files CA operations create

diff --git a/.gitignore b/.gitignore
index 9707931..1b7d012 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
 /lib/RT/Generated.pm
 /Makefile
 /t/data/gnupg/keyrings/random_seed
+/t/data/smime/keys/demoCA/*.old
 /t/data/configs/apache2.2+fastcgi.conf
 /t/data/configs/apache2.2+mod_perl.conf
 /t/security/embargo/

commit 9f996e8f25dd674b7968a35647c4b787fb994736
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:02:55 2011 +0400

    accept user in RT::Test->import_smime_key
    
    imports key into user's CF, instead of "keyring"

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 53da1ea..2ebba23 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1381,22 +1381,32 @@ sub trust_gnupg_key {
 sub import_smime_key {
     my $self = shift;
     my $key  = shift;
+    my $user = 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";
+    $key .= ".pem" unless $key =~ /\.(pem|crt|key)$/;
 
     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;
+
+    if ( $user ) {
+        my ($status, $msg) = $user->AddCustomFieldValue(
+            Field => 'SMIME Key',
+            Value => $content,
+        );
+        die "Couldn't set CF: $msg" unless $status;
+    } else {
+        my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
+        die "SMIME keyring '$keyring' doesn't exist"
+            unless $keyring && -e $keyring;
+
+        open my $fh, '>:raw', File::Spec->catfile($keyring, $key)
+            or die "can't open file: $!";
+        print $fh $content;
+        close $fh;
+    }
 
     return;
 }

commit dc6aa74a6eda03c2c0bfd8f5fcebb61074d3d49f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:04:54 2011 +0400

    add_mail_catcher was dropped, we always catch mail in tests

diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index ecf449b..b2ea3f6 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -12,9 +12,6 @@ use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
 use RT::Tickets;
 
-# 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),
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index 1d3ac23..bf9ea6d 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -12,9 +12,6 @@ plan skip_all => 'openssl executable is required.'
 use IPC::Run3 'run3';
 use RT::Interface::Email;
 
-# 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),

commit baf5dcd0dff724dff2dd43faa87f624ca8fc5222
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:06:06 2011 +0400

    use root at example.com for testing as he has a key

diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index b2ea3f6..c4c5f3e 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -67,6 +67,13 @@ my $queue = RT::Test->load_or_create_queue(
 );
 ok $queue && $queue->id, 'loaded or created queue';
 
+my $user = RT::Test->load_or_create_user(
+    Name => 'root at example.com',
+    EmailAddress => 'root at example.com',
+);
+RT::Test->import_smime_key('root at example.com.crt', $user);
+RT::Test->add_rights( Principal => $user, Right => 'SuperUser', Object => RT->System );
+
 my $mail = RT::Test->open_mailgate_ok($url);
 print $mail <<EOF;
 From: root\@localhost
@@ -99,7 +106,7 @@ RT::Test->close_mailgate_ok($mail);
     run3(
         shell_quote(
             qw(openssl smime -encrypt  -des3),
-            -from    => 'root at localhost',
+            -from    => 'root at example.com',
             -to      => 'rt@' . $RT::rtname,
             -subject => "Encrypted message for queue",
             File::Spec->catfile( $keys, 'sender at example.com.crt' ),
@@ -110,7 +117,7 @@ RT::Test->close_mailgate_ok($mail);
     );
 
     my ($status, $tid) = RT::Test->send_via_mailgate( $buf );
-    ok !$status, "executed gate";
+    is ($status >> 8, 0, "The mail gateway exited normally");
 
     my $tick = RT::Ticket->new( $RT::SystemUser );
     $tick->Load( $tid );
@@ -148,7 +155,7 @@ RT::Test->close_mailgate_ok($mail);
             '|',
             shell_quote(
                 qw(openssl smime -encrypt -des3),
-                -from    => 'root at localhost',
+                -from    => 'root at example.com',
                 -to      => 'rt@' . RT->Config->Get('rtname'),
                 -subject => "Encrypted and signed message for queue",
                 File::Spec->catfile( $keys, 'sender at example.com.crt' ),

commit 67ef999a693a8c6eee463c54991b7322287b2f1e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:07:07 2011 +0400

    use new feature in ->import_smime_key

diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index bf9ea6d..6c76491 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -78,13 +78,7 @@ my $user;
     ok($user->Load('root'), "Loaded user 'root'");
     is($user->EmailAddress, 'root at localhost');
 
-    open my $fh, '<:raw', File::Spec->catfile($keys, 'root at example.com.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->import_smime_key( 'root at example.com.crt' => $user );
 }
 
 RT::Test->clean_caught_mails;

commit 50e62cc50c86c1c31b18a46e890f2573c6bc18b9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Dec 29 04:07:51 2011 +0400

    follow pattern in testing result of send_via_mailgate
    
    it's not correct test, but it should be fixed in a
    different branch. Bug reported.

diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index 6c76491..b108456 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -97,7 +97,7 @@ END
     my ($status, $id) = RT::Test->send_via_mailgate(
         $mail, queue => $queue->Name,
     );
-    is $status, 0, "successfuly executed mailgate";
+    is $status >> 8, 0, "successfuly executed mailgate";
 
     my $ticket = RT::Ticket->new($RT::SystemUser);
     $ticket->Load( $id );

commit 61be067d3815cb74cadd4a994f6a237ed0848bd9
Author: Jason May <jasonmay at bestpractical.com>
Date:   Fri Dec 30 14:37:00 2011 -0500

    Create the SMIME key CF
    
    This silences warnings about the missing 'SMIME Key' custom field.

diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
index 1d710c4..fb45c00 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 => 89;
+use RT::Test tests => 92;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
@@ -34,6 +34,22 @@ RT->Config->Set( SMIME =>
 );
 RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
 
+{
+    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,
+    );
+}
+
 RT::Test->import_smime_key('root at example.com');
 RT::Test->import_smime_key('sender at example.com');
 

commit faec917e107829b94554e1c409df85aece0eba5e
Author: Jason May <jasonmay at bestpractical.com>
Date:   Fri Dec 30 14:39:55 2011 -0500

    Correct the test count in the SMIME tests

diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index c4c5f3e..50aa382 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 => 19;
+use RT::Test tests => 21;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index b108456..d0a9407 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 14;
+use RT::Test tests => 15;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'

commit 2a67e8d1394bbc3cc325cec2af576074a2ba22ec
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 10 15:14:12 2012 +0400

    Fill in SMIME docs in config and mail plugin

diff --git a/TODO.SMIME b/TODO.SMIME
index bfb7c0a..464fccc 100644
--- a/TODO.SMIME
+++ b/TODO.SMIME
@@ -1,5 +1,3 @@
-* 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
diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 774d5f5..73eddd9 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2076,8 +2076,8 @@ Set($DefaultTimeUnitsToHours, 0);
 
 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.
+directory). At this momement GnuPG (PGP) and SMIME security protocols
+are supported.
 
 =over 4
 
@@ -2126,12 +2126,26 @@ lib/RT/Crypt/SMIME.pm` from your RT install directory).
 
 =item C<%SMIME>
 
+Set C<Enable> to false or true value to disable or enable SMIME interfaces
+for encryptng and signing outgoing messages.
+
+Set C<OpenSSL> to path to F<openssl> executable.
+
+Set C<Keyring> to directory with key files.
+
+Set C<Passphrase> to passphrase(s) for keys in the keyring.
+
+More details in L<RT::Crypt::SMIME>.
+
 =back
 
 =cut
 
 Set( %SMIME,
     Enable => @RT_SMIME@,
+    OpenSSL => '/usr/bin/openssl',
+    Keyring => q{@RT_VAR_PATH@/data/gpg},
+    Passphrase => '',
 );
 
 =head2 GnuPG configuration
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 4ca9405..4ea999f 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -69,8 +69,8 @@ You should start from reading L<RT::Crypt>.
 
     Set( %SMIME,
         Enable => 1,
-        OpenSSL => '/opt/local/bin/openssl',
-        Keyring => '/opt/rt3/var/data/smime',
+        OpenSSL => '/usr/bin/openssl',
+        Keyring => '/opt/rt4/var/data/smime',
         Passphrase => {
             'queue.address at exampl.com' => 'passphrase',
         },
@@ -87,7 +87,8 @@ 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.
+Either scalar with one passphrase for all keys or hash with address
+and passphrase pairs for keys in the keyring.
 
 =cut
 
diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index deb84e0..429b72b 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -74,11 +74,25 @@ 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
 
+To use the SMIME-secured mail gateway, you need to do the following:
+
+Set up a SMIME key directory with files containing keys for queues'
+addresses and specify the following in your SiteConfig.pm
+
+    Set(%SMIME,
+        Enable => 1,
+        OpenSSL => '/usr/bin/openssl',
+        Keyring => '/opt/rt4/var/data/smime',
+        Passphrase => {
+            'queue.address at exampl.com' => 'passphrase',
+        },
+    );
+
 Read also: L<RT::Crypt> and L<RT::Crypt::SMIME>.
 
 =cut

commit 0ccad4abe2b62ce2c4645aaacdff88e0dd7b8d24
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 10 16:22:26 2012 +0400

    drop mail catcher call, we always set it

diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index 4fce381..566885e 100644
--- a/t/web/smime/outgoing.t
+++ b/t/web/smime/outgoing.t
@@ -11,8 +11,6 @@ plan skip_all => 'openssl executable is required.'
 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(

commit dd125f372a945589604e6512a357908114200640
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 10 16:23:00 2012 +0400

    fix smime web tests

diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index 566885e..6a5ff3c 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 => 494;
+use RT::Test tests => 495;
 
 my $openssl = RT::Test->find_executable('openssl');
 plan skip_all => 'openssl executable is required.'
@@ -188,7 +188,12 @@ foreach my $queue_set ( @variants ) {
 # ------------------------------------------------------------------------------
 
 unlink $_ foreach glob( $keyring ."/*" );
-RT::Test->import_smime_key('sender at example.com', 'public');
+{
+    my $sender = 'sender at example.com';
+    my $user = RT::Test->load_or_create_user( Name => $sender, EmailAddress => $sender );
+    ok $user && $user->id, 'loaded or created user';
+    RT::Test->import_smime_key($sender, $user);
+}
 RT::Test->import_smime_key($user_email);
 
 $queue = RT::Test->load_or_create_queue(

commit eeb869d64f10c7a7864c8a364607837d2be0a873
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 10 16:23:22 2012 +0400

    run more tests that are deeper

diff --git a/Makefile.in b/Makefile.in
index 46905de..64da744 100755
--- a/Makefile.in
+++ b/Makefile.in
@@ -222,7 +222,7 @@ DB_RT_PASS		=	@DB_RT_PASS@
 
 
 
-TEST_FILES = t/*.t t/*/*.t
+TEST_FILES = t/*.t t/*/*.t t/*/*/*.t
 TEST_VERBOSE = 0
 
 

commit 2616ac9dabd16a441ea8be5705e9d9b5080ee82c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 10 18:26:00 2012 +0400

    first pass at generic attachments encrypt/decrypt in DB

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index 0c46855..53e3c40 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -788,7 +788,7 @@ sub Decrypt {
     $self->SetHeader( 'Content-Type' => $type );
 
     my $content = $self->Content;
-    my %res = RT::Crypt::GnuPG::DecryptContent( Content => \$content, );
+    my %res = RT::Crypt->DecryptContent( Content => \$content );
     if ( $res{'exit_code'} ) {
         return (0, $self->loc('GnuPG error. Contact with administrator'));
     }
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index f3f6503..70f7501 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -203,6 +203,23 @@ sub SignEncrypt {
     return %res;
 }
 
+sub SignEncryptContent {
+    my $self = shift;
+    my %args = (@_);
+
+    if ( $args{'Sign'} && !defined $args{'Signer'} ) {
+        $args{'Signer'} = $self->UseKeyForSigning;
+    }
+    if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
+        $args{'Recipients'} = [ RT->Config->Get('CorrespondAddress') ];
+    }
+
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my %res = $self->LoadImplementation( $protocol )->SignEncryptContent( %args );
+    $res{'Protocol'} = $protocol;
+    return %res;
+}
+
 sub DrySign {
     my $self = shift;
     my %args = ( Protocol => undef, Signer => undef, @_ );
@@ -233,6 +250,16 @@ sub VerifyDecrypt {
     return @res;
 }
 
+sub DecryptContent {
+    my $self = shift;
+    my %args = (@_);
+
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+    my %res = $self->LoadImplementation( $protocol )->DecryptContent( %args );
+    $res{'Protocol'} = $protocol;
+    return %res;
+}
+
 sub ParseStatus {
     my $self = shift;
     my %args = (
diff --git a/t/crypt/gnupg/attachments-in-db.t b/t/crypt/gnupg/attachments-in-db.t
new file mode 100644
index 0000000..4f98c28
--- /dev/null
+++ b/t/crypt/gnupg/attachments-in-db.t
@@ -0,0 +1,49 @@
+#!/usr/bin/perl -w
+use strict;
+
+use RT::Test::GnuPG
+    tests         => 12,
+    gnupg_options => {
+        passphrase    => 'recipient',
+        'trust-model' => 'always',
+    }
+;
+
+RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'} = 1;
+
+RT::Test->import_gnupg_key('general at example.com', 'public');
+RT::Test->import_gnupg_key('general at example.com', 'secret');
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'General',
+    CorrespondAddress => 'general at example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue->id,
+        Subject => 'test',
+        Content => 'test',
+    );
+
+    my $txn = $ticket->Transactions->First;
+    ok $txn && $txn->id, 'found first transaction';
+    is $txn->Type, 'Create', 'it is Create transaction';
+
+    my $attach = $txn->Attachments->First;
+    ok $attach && $attach->id, 'found attachment';
+    is $attach->Content, 'test', 'correct content';
+
+    my ($status, $msg) = $attach->Encrypt;
+    ok $status, 'encrypted attachment';
+
+    isnt $attach->Content, 'test', 'correct content';
+
+    ($status, $msg) = $attach->Decrypt;
+    ok $status, 'decrypted attachment';
+
+    is $attach->Content, 'test', 'correct content';
+}
+
+
+

commit 1a868b501a92b0931863e60214a775c8c69e3254
Author: Jason May <jasonmay at bestpractical.com>
Date:   Tue Jan 10 11:10:29 2012 -0500

    Defend against undefined configurations when checking crypt protocols
    
    This gets each SMIME test to run without errors.

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 70f7501..3c2eaca 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -76,7 +76,7 @@ sub Protocols {
 
 sub EnabledProtocols {
     my $self = shift;
-    return grep RT->Config->Get($_)->{'Enable'}, $self->Protocols;
+    return grep { (RT->Config->Get($_) || {})->{'Enable'} } $self->Protocols;
 }
 
 sub UseForOutgoing {

commit eebfe48945a3ec2d33ecf22137ef517845664b01
Author: Jason May <jasonmay at bestpractical.com>
Date:   Tue Jan 10 11:17:03 2012 -0500

    Factor sending templated errors for convenient future use

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 429b72b..38cee41 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -232,20 +232,11 @@ sub CheckNoPrivateKey {
 
     $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,
+    return EmailErrorToSender(
+        %args,
         Template  => 'Error: no private key',
-        Arguments => {
-            Message   => $args{'Message'},
-            TicketObj => $args{'Ticket'},
-        },
-        InReplyTo => $args{'Message'},
+        Arguments => { Message   => $args{'Message'} },
     );
-    unless ( $status ) {
-        $RT::Logger->error("Couldn't send 'Error: no private key'");
-    }
-    return 0;
 }
 
 sub CheckBadData {
@@ -258,14 +249,25 @@ sub CheckBadData {
 
     $RT::Logger->error("Couldn't process a message: ". join ', ', @bad_data_messages );
 
+    return EmailErrorToSender(
+        %args,
+        Template  => 'Error: bad GnuPG data',
+        Arguments => { Messages  => [ @bad_data_messages ] },
+    );
+}
+
+sub EmailErrorToSender {
+    my $self = shift;
+    my %args = (@_);
+
+    $args{'Arguments'} ||= {};
+    $args{'Arguments'}{'TicketObj'} ||= $args{'Ticket'};
+
     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'},
-        },
+        Template  => $args{'Template'},
+        Arguments => $args{'Arguments'},
         InReplyTo => $args{'Message'},
     );
     unless ( $status ) {

commit deeaed23a6d84b4dae4e0305c963de2f8fa732ce
Author: Jason May <jasonmay at bestpractical.com>
Date:   Tue Jan 10 11:17:03 2012 -0500

    Factor sending templated errors for convenient future use

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 38cee41..5ee30fb 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -257,7 +257,6 @@ sub CheckBadData {
 }
 
 sub EmailErrorToSender {
-    my $self = shift;
     my %args = (@_);
 
     $args{'Arguments'} ||= {};
@@ -271,7 +270,7 @@ sub EmailErrorToSender {
         InReplyTo => $args{'Message'},
     );
     unless ( $status ) {
-        $RT::Logger->error("Couldn't send 'Error: bad GnuPG data'");
+        $RT::Logger->error("Couldn't send '$args{'Template'}''");
     }
     return 0;
 }

commit 465ec1a3bca63946d08eb1f897fbf3e58989e7d7
Author: Jason May <jasonmay at bestpractical.com>
Date:   Tue Jan 10 16:46:13 2012 -0500

    Error and abort if an unencrypted message is received in Strict mode
    
    A templated reply is sent and the ticket creation is aborted when Strict
    is set in the config.

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 5ee30fb..3c9c957 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -133,9 +133,19 @@ sub GetCurrentUser {
         AddStatus => 1,
     );
     if ( $status && !@res ) {
-        $args{'Message'}->head->replace(
-            'X-RT-Incoming-Encryption' => 'Not encrypted'
-        );
+        if (RT->Config->Get('Crypt')->{'Strict'}) {
+            EmailErrorToSender(
+                %args,
+                Template  => 'NotEncryptedMessage',
+                Arguments => { Message  => $args{'Message'} },
+            );
+            return (-1, 'rejected because the message is unencrypted with Strict mode enabled');
+        }
+        else {
+            $args{'Message'}->head->replace(
+                'X-RT-Incoming-Encryption' => 'Not encrypted'
+            );
+        }
         return 1;
     }
 

commit a517f624b7c9d3a8453552394635158f4215bb51
Author: Jason May <jasonmay at bestpractical.com>
Date:   Tue Jan 10 16:55:05 2012 -0500

    Test sending (un)encrypted mail with strict auth enabled

diff --git a/t/mail/smime/strict.t b/t/mail/smime/strict.t
new file mode 100644
index 0000000..c91b438
--- /dev/null
+++ b/t/mail/smime/strict.t
@@ -0,0 +1,198 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test tests => 22;
+
+my $openssl = RT::Test->find_executable('openssl');
+plan skip_all => 'openssl executable is required.'
+    unless $openssl;
+
+use IPC::Run3 'run3';
+use String::ShellQuote 'shell_quote';
+use RT::Tickets;
+
+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,
+    Strict   => 1,
+    Incoming => ['SMIME'],
+    Outgoing => 'SMIME',
+);
+RT->Config->Set( GnuPG => Enable => 0 );
+RT->Config->Set( SMIME =>
+    Enable => 1,
+    Strict => 1,
+    OutgoingMessagesFormat => 'RFC',
+    Passphrase => {
+        'sender at example.com' => '123456',
+    },
+    OpenSSL => $openssl,
+    Keyring => $keyring,
+);
+
+RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
+
+{
+    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 $template = RT::Template->new($RT::SystemUser);
+    $template->Create(
+        Name => 'NotEncryptedMessage',
+        Queue => 0,
+        Content => <<EOF,
+
+Subject: Failed to send unencrypted message
+
+This message was not sent since it is unencrypted:
+EOF
+    );
+}
+
+my ($url, $m) = RT::Test->started_ok;
+ok $m->login, "logged in";
+
+# configure key for General queue
+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 $user = RT::Test->load_or_create_user(
+    Name => 'root at example.com',
+    EmailAddress => 'root at example.com',
+);
+RT::Test->import_smime_key('root at example.com.crt', $user);
+RT::Test->add_rights( Principal => $user, Right => 'SuperUser', Object => RT->System );
+
+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);
+
+{
+    ok(!RT::Test->last_ticket, 'A ticket was not created');
+    my ($mail) = RT::Test->fetch_caught_mails;
+    like(
+        $mail,
+        qr/^Subject: Failed to send unencrypted message/m,
+        'recorded incoming mail that is not encrypted'
+    );
+    my ($warning) = $m->get_warnings;
+    like($warning, qr/rejected because the message is unencrypted with Strict mode enabled/);
+}
+
+{
+    # test for encrypted mail
+    my $buf = '';
+    run3(
+        shell_quote(
+            qw(openssl smime -encrypt  -des3),
+            -from    => 'root at example.com',
+            -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 );
+    is ($status >> 8, 0, "The mail gateway exited normally");
+
+    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 $buf = '';
+
+    run3(
+        join(
+            ' ',
+            shell_quote(
+                RT->Config->Get('SMIME')->{'OpenSSL'},
+                qw( smime -sign -nodetach -passin pass:123456),
+                -signer => File::Spec->catfile( $keys, 'root at example.com.crt' ),
+                -inkey  => File::Spec->catfile( $keys, 'root at example.com.key' ),
+            ),
+            '|',
+            shell_quote(
+                qw(openssl smime -encrypt -des3),
+                -from    => 'root at example.com',
+                -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');
+}
+

commit 689bf89be2dfcc7b3718f4cdd1a8c685f08dd52d
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 10 21:23:59 2012 +0400

    delay setting content type until all error checks are passed

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index 53e3c40..f017b51 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -744,9 +744,6 @@ sub Encrypt {
         return (0, $self->loc('No key suitable for encryption'));
     }
 
-    $self->__Set( Field => 'ContentType', Value => $type );
-    $self->SetHeader( 'Content-Type' => $type );
-
     my $content = $self->Content;
     my %res = RT::Crypt->SignEncryptContent(
         Content => \$content,
@@ -762,6 +759,9 @@ sub Encrypt {
     unless ( $status ) {
         return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
     }
+    $self->__Set( Field => 'ContentType', Value => $type );
+    $self->SetHeader( 'Content-Type' => $type );
+
     return (1, $self->loc('Successfuly encrypted data'));
 }
 
@@ -784,8 +784,6 @@ sub Decrypt {
     } else {
         return (1, $self->loc('Is not encrypted'));
     }
-    $self->__Set( Field => 'ContentType', Value => $type );
-    $self->SetHeader( 'Content-Type' => $type );
 
     my $content = $self->Content;
     my %res = RT::Crypt->DecryptContent( Content => \$content );
@@ -797,6 +795,9 @@ sub Decrypt {
     unless ( $status ) {
         return ($status, $self->loc("Couldn't replace content with decrypted data: [_1]", $msg));
     }
+    $self->__Set( Field => 'ContentType', Value => $type );
+    $self->SetHeader( 'Content-Type' => $type );
+
     return (1, $self->loc('Successfuly decrypted data'));
 }
 

commit ed0bd0543785a544c73dd95538ea015c7ffcc018
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 11 01:49:18 2012 +0400

    refactor common code

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 3c2eaca..69ceb8e 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -97,6 +97,19 @@ sub LoadImplementation {
     return $class;
 } }
 
+sub SimpleImplementationCall {
+    my $self = shift;
+    my %args = (@_);
+    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
+
+    my $method = (caller(1))[3];
+    $method =~ s/.*:://;
+
+    my %res = $self->LoadImplementation( $protocol )->$method( %args );
+    $res{'Protocol'} = $protocol if keys %res;
+    return %res;
+}
+
 # encryption and signatures can be nested one over another, for example:
 # GPG inline signed text can be signed with SMIME
 
@@ -196,11 +209,7 @@ sub SignEncrypt {
             qw(To Cc Bcc)
         ];
     }
-
-    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-    my %res = $self->LoadImplementation( $protocol )->SignEncrypt( %args );
-    $res{'Protocol'} = $protocol;
-    return %res;
+    return $self->SimpleImplementationCall( %args );
 }
 
 sub SignEncryptContent {
@@ -214,10 +223,7 @@ sub SignEncryptContent {
         $args{'Recipients'} = [ RT->Config->Get('CorrespondAddress') ];
     }
 
-    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-    my %res = $self->LoadImplementation( $protocol )->SignEncryptContent( %args );
-    $res{'Protocol'} = $protocol;
-    return %res;
+    return $self->SimpleImplementationCall( %args );
 }
 
 sub DrySign {
@@ -241,23 +247,15 @@ sub VerifyDecrypt {
 
     my @protected = $self->FindProtectedParts( Entity => $args{'Entity'} );
     foreach my $protected ( @protected ) {
-        my $protocol = $protected->{'Protocol'};
-        my $class = $self->LoadImplementation( $protocol );
-        my %res = $class->VerifyDecrypt( %args, Info => $protected );
-        $res{'Protocol'} = $protocol;
-        push @res, \%res;
+        push @res, { $self->SimpleImplementationCall(
+            %args, Protocol => $protected->{'Protocol'}, Info => $protected
+        ) };
     }
     return @res;
 }
 
 sub DecryptContent {
-    my $self = shift;
-    my %args = (@_);
-
-    my $protocol = delete $args{'Protocol'} || $self->UseForOutgoing;
-    my %res = $self->LoadImplementation( $protocol )->DecryptContent( %args );
-    $res{'Protocol'} = $protocol;
-    return %res;
+    return shift->SimpleImplementationCall( @_ );
 }
 
 sub ParseStatus {
@@ -376,19 +374,13 @@ sub CheckRecipients {
 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( %args );
-    $res{'Protocol'} = $protocol;
-    return %res;
+    return $self->SimpleImplementationCall( %args );
 }
 
 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( %args );
-    $res{'Protocol'} = $protocol;
-    return %res;
+    return $self->SimpleImplementationCall( %args );
 }
 
 sub GetPublicKeyInfo {
@@ -409,10 +401,7 @@ sub GetKeyInfo {
 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( %args );
-    $res{'Protocol'} = $protocol;
-    return %res;
+    return $self->SimpleImplementationCall( %args );
 }
 
 1;

commit f5ca8e8889dace6fd01e93b2b2af2b228b4b4eb6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 11 03:46:06 2012 +0400

    use x-application-rt\/$protocol-encrypted content type
    
    Use protocol name instead of storing 'gpg' in content type
    when we encrypt attachments in DB. Get it from result of
    SignEncryptContent.

diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index f017b51..1ce0f6d 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -718,12 +718,10 @@ sub Encrypt {
     require RT::Crypt::GnuPG;
 
     my $type = $self->ContentType;
-    if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+    if ( $type =~ /^x-application-rt\/[^-]+-encrypted/i ) {
         return (1, $self->loc('Already encrypted'));
     } elsif ( $type =~ /^multipart\//i ) {
         return (1, $self->loc('No need to encrypt'));
-    } else {
-        $type = qq{x-application-rt\/gpg-encrypted; original-type="$type"};
     }
 
     my $queue = $txn->TicketObj->QueueObj;
@@ -759,6 +757,8 @@ sub Encrypt {
     unless ( $status ) {
         return ($status, $self->loc("Couldn't replace content with encrypted data: [_1]", $msg));
     }
+
+    $type = qq{x-application-rt\/$res{'Protocol'}-encrypted; original-type="$type"};
     $self->__Set( Field => 'ContentType', Value => $type );
     $self->SetHeader( 'Content-Type' => $type );
 
@@ -778,7 +778,10 @@ sub Decrypt {
     require RT::Crypt::GnuPG;
 
     my $type = $self->ContentType;
-    if ( $type =~ /^x-application-rt\/gpg-encrypted/i ) {
+    my $protocol;
+    if ( $type =~ /^x-application-rt\/([^-]+)-encrypted/i ) {
+        $protocol = $1;
+        $protocol =~ s/gpg/gnupg/; # backwards compatibility
         ($type) = ($type =~ /original-type="(.*)"/i);
         $type ||= 'application/octet-stream';
     } else {
@@ -786,7 +789,7 @@ sub Decrypt {
     }
 
     my $content = $self->Content;
-    my %res = RT::Crypt->DecryptContent( Content => \$content );
+    my %res = RT::Crypt->DecryptContent( Protocol => $protocol, Content => \$content );
     if ( $res{'exit_code'} ) {
         return (0, $self->loc('GnuPG error. Contact with administrator'));
     }

commit 5215ffd9fa9788c76cd117c8f52a69082ba7be12
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 11 14:42:49 2012 +0400

    Move AllowEncryptDataInDB into %Crypt option

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 73eddd9..5e430c5 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2104,6 +2104,9 @@ 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.
 
+If you want to allow people to encrypt attachments inside the DB then
+set C<AllowEncryptDataInDB> to 1.
+
 =back
 
 =cut
@@ -2114,6 +2117,8 @@ Set( %Crypt,
 
     RejectOnMissingPrivateKey => 1,
     RejectOnBadData           => 1,
+
+    AllowEncryptDataInDB      => 0,
 );
 
 =head2 SMIME configuration
@@ -2164,9 +2169,6 @@ 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.
-
 =cut
 
 Set(%GnuPG,
diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index 1ce0f6d..534ef87 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -710,10 +710,10 @@ sub Encrypt {
     return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
     return (0, $self->loc('Permission Denied'))
         unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
-    return (0, $self->loc('GnuPG integration is disabled'))
-        unless RT->Config->Get('GnuPG')->{'Enable'};
+    return (0, $self->loc('Cryptography is disabled'))
+        unless RT->Config->Get('Crypt')->{'Enable'};
     return (0, $self->loc('Attachments encryption is disabled'))
-        unless RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'};
+        unless RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'};
 
     require RT::Crypt::GnuPG;
 
@@ -772,8 +772,8 @@ sub Decrypt {
     return (0, $self->loc('Permission Denied')) unless $txn->CurrentUserCanSee;
     return (0, $self->loc('Permission Denied'))
         unless $txn->TicketObj->CurrentUserHasRight('ModifyTicket');
-    return (0, $self->loc('GnuPG integration is disabled'))
-        unless RT->Config->Get('GnuPG')->{'Enable'};
+    return (0, $self->loc('Cryptography is disabled'))
+        unless RT->Config->Get('Crypt')->{'Enable'};
 
     require RT::Crypt::GnuPG;
 
diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 69ceb8e..c19bdee 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -65,6 +65,11 @@ first one value from above list.
         ...
     );
 
+You can allow users to encrypt data in the database using
+option C<AllowEncryptDataInDB>. By default it's disabled.
+Users must have rights to see and modify tickets to use
+this feature.
+
 =cut
 
 our @PROTOCOLS = ('GnuPG', 'SMIME');
diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index b22eefa..03d4473 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -138,13 +138,6 @@ it well.
 
 =back
 
-=head3 Encrypting data in the database
-
-You can allow users to encrypt data in the database using
-option C<AllowEncryptDataInDB>. By default it's disabled.
-Users must have rights to see and modify tickets to use
-this feature.
-
 =head2 %GnuPGOptions
 
 Use this hash to set options of the 'gnupg' program. You can define almost any
diff --git a/share/html/Ticket/Elements/ShowTransaction b/share/html/Ticket/Elements/ShowTransaction
index 2c217b4..ffc06b9 100644
--- a/share/html/Ticket/Elements/ShowTransaction
+++ b/share/html/Ticket/Elements/ShowTransaction
@@ -223,8 +223,8 @@ else {
               . "</a>]";
         }
         if ( $can_modify
-            && RT->Config->Get('GnuPG')->{'Enable'}
-            && RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'}
+            && RT->Config->Get('Crypt')->{'Enable'}
+            && RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'}
             && $ticket->CurrentUserHasRight('ForwardMessage')
         ) {
             $titlebar_commands .=
diff --git a/t/crypt/gnupg/attachments-in-db.t b/t/crypt/gnupg/attachments-in-db.t
index 4f98c28..dc0d87b 100644
--- a/t/crypt/gnupg/attachments-in-db.t
+++ b/t/crypt/gnupg/attachments-in-db.t
@@ -9,7 +9,7 @@ use RT::Test::GnuPG
     }
 ;
 
-RT->Config->Get('GnuPG')->{'AllowEncryptDataInDB'} = 1;
+RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} = 1;
 
 RT::Test->import_gnupg_key('general at example.com', 'public');
 RT::Test->import_gnupg_key('general at example.com', 'secret');

commit ebb82113a471e9a189d82f6ad2a1de4c4b4c37f6
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jan 11 19:37:41 2012 +0400

    encrypting/decrypting attachments in DB with SMIME

diff --git a/TODO.SMIME b/TODO.SMIME
index 464fccc..949abca 100644
--- a/TODO.SMIME
+++ b/TODO.SMIME
@@ -1,5 +1,3 @@
-* port RT::Attachment::{Encrypt,Decrypt} over new API with SMIME support
-
 * continue with share//html/Ticket/Elements/ShowGnuPGStatus
 
 * work harder on re-verification
diff --git a/lib/RT/Attachment.pm b/lib/RT/Attachment.pm
index 534ef87..8adfd2d 100644
--- a/lib/RT/Attachment.pm
+++ b/lib/RT/Attachment.pm
@@ -788,8 +788,20 @@ sub Decrypt {
         return (1, $self->loc('Is not encrypted'));
     }
 
+    my $queue = $txn->TicketObj->QueueObj;
+    my @addresses =
+        $queue->CorrespondAddress,
+        $queue->CommentAddress,
+        RT->Config->Get('CorrespondAddress'),
+        RT->Config->Get('CommentAddress')
+    ;
+
     my $content = $self->Content;
-    my %res = RT::Crypt->DecryptContent( Protocol => $protocol, Content => \$content );
+    my %res = RT::Crypt->DecryptContent(
+        Protocol => $protocol,
+        Content => \$content,
+        Recipients => \@addresses,
+    );
     if ( $res{'exit_code'} ) {
         return (0, $self->loc('GnuPG error. Contact with administrator'));
     }
diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 4ea999f..9a61163 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -114,16 +114,67 @@ sub SignEncrypt {
 
     my $entity = $args{'Entity'};
 
+    if ( $args{'Encrypt'} ) {
+        my %seen;
+        $args{'Recipients'} = [
+            grep !$seen{$_}++, map $_->address, map Email::Address->parse($_),
+            grep defined && length, map $entity->head->get($_), qw(To Cc Bcc)
+        ];
+    }
+
+    $entity->make_multipart('mixed', Force => 1);
+    my ($buf, %res) = $self->_SignEncrypt(
+        %args,
+        Content => \$entity->parts(0)->stringify,
+    );
+    unless ( $buf ) {
+        $entity->make_singlepart;
+        return %res;
+    }
+
+    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 SignEncryptContent {
+    my $self = shift;
+    my %args = (
+        Content => undef,
+        @_
+    );
+
+    my ($buf, %res) = $self->_SignEncrypt(%args);
+    ${ $args{'Content'} } = $$buf if $buf;
+    return %res;
+}
+
+sub _SignEncrypt {
+    my $self = shift;
+    my %args = (
+        Content => undef,
+
+        Sign => 1,
+        Signer => undef,
+        Passphrase => undef,
+
+        Encrypt => 1,
+        Recipients => [],
+
+        @_
+    );
+
     my %res = (exit_code => 0, status => '');
 
     my @keys;
     if ( $args{'Encrypt'} ) {
-        my @addresses =
-            map $_->address,
-            map Email::Address->parse($_),
-            grep defined && length,
-            map $entity->head->get($_),
-            qw(To Cc Bcc);
+        my @addresses = @{ $args{'Recipients'} };
 
         foreach my $address ( @addresses ) {
             $RT::Logger->debug( "Considering encrypting message to " . $address );
@@ -163,7 +214,7 @@ sub SignEncrypt {
             push @keys, $key_info{'info'}[0]{'Content'};
         }
     }
-    return %res if $res{'exit_code'};
+    return (undef, %res) if $res{'exit_code'};
 
     my $opts = RT->Config->Get('SMIME');
 
@@ -194,28 +245,19 @@ sub SignEncrypt {
         );
     }
 
-    $entity->make_multipart('mixed', Force => 1);
     my ($buf, $err) = ('', '');
     {
         local $ENV{'SMIME_PASS'} = $args{'Passphrase'};
         local $SIG{'CHLD'} = 'DEFAULT';
         safe_run_child { run3(
             join( ' | ', @command ),
-            \$entity->parts(0)->stringify,
+            $args{'Content'},
             \$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;
+    return (\$buf, %res);
 }
 
 sub VerifyDecrypt {
@@ -362,37 +404,52 @@ sub DecryptRFC3851 {
     my $self = shift;
     my %args = (Data => undef, Queue => undef, @_ );
 
-    my %res;
-
     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'}||[] };
-    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'};
+    push @{ $args{'Recipients'} ||= [] },
+        $args{'Queue'}->CorrespondAddress, RT->Config->Get('CorrespondAddress'),
+        $args{'Queue'}->CommentAddress, RT->Config->Get('CommentAddress')
+    ;
 
-    my $buf;
+    my ($buf, %res) = $self->_Decrypt( %args, Content => \$args{'Data'}->as_string );
+    return %res unless $buf;
+
+    my $res_entity = _extract_msg_from_buf( $buf, 1 );
+    $res_entity->make_multipart( 'mixed', Force => 1 );
+
+    $args{'Data'}->make_multipart( 'mixed', Force => 1 );
+    $args{'Data'}->parts([ $res_entity->parts ]);
+    $args{'Data'}->make_singlepart;
+
+    return %res;
+}
+
+sub DecryptContent {
+    my $self = shift;
+    my %args = (
+        Content => undef,
+        @_
+    );
+
+    my ($buf, %res) = $self->_Decrypt( %args );
+    ${ $args{'Content'} } = $$buf if $buf;
+    return %res;
+}
+
+sub _Decrypt {
+    my $self = shift;
+    my %args = (Content => undef, @_ );
+
+    my %seen;
+    my @addresses =
+        grep !$seen{lc $_}++, map $_->address, map Email::Address->parse($_),
+        grep length && defined, @{$args{'Recipients'}};
+
+    my ($buf, $encrypted_to, %res);
+
+    my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
     my $found_key = 0;
-    my $encrypted_to;
-    foreach my $address ( sort { $addresses{$b} <=> $addresses{$a} } grep length, keys %addresses ) {
+    foreach my $address ( @addresses ) {
         my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
         next unless -e $key_file && -r _;
 
@@ -407,7 +464,7 @@ sub DecryptRFC3851 {
                 ? (qw(-passin env:SMIME_PASS))
                 : (),
         ) );
-        safe_run_child { run3( $cmd, \$msg, \$buf, \$res{'stderr'} ) };
+        safe_run_child { run3( $cmd, $args{'Content'}, \$buf, \$res{'stderr'} ) };
         unless ( $? ) {
             $encrypted_to = $address;
             last;
@@ -424,7 +481,7 @@ sub DecryptRFC3851 {
             Message => 'Decryption failed',
             EncryptedTo => $address,
         });
-        return %res;
+        return (undef, %res);
     }
     unless ( $found_key ) {
         $res{'exit_code'} = 1;
@@ -434,23 +491,16 @@ sub DecryptRFC3851 {
             Message   => "Secret key is not available",
             KeyType   => 'secret',
         });
-        return %res;
+        return (undef, %res);
     }
 
-    my $res_entity = _extract_msg_from_buf( \$buf, 1 );
-    $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'} = $self->FormatStatus({
         Operation => 'Decrypt', Status => 'DONE',
         Message => 'Decryption process succeeded',
         EncryptedTo => $encrypted_to,
     });
 
-    return %res;
+    return (\$buf, %res);
 }
 
 sub FormatStatus {

commit 6f303840d14406b00c89d25dcb2d740e3f5ef7c7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 12 23:48:17 2012 +0400

    extract common code into RT::Test::SMIME

diff --git a/lib/RT/Test/SMIME.pm b/lib/RT/Test/SMIME.pm
new file mode 100644
index 0000000..c12816b
--- /dev/null
+++ b/lib/RT/Test/SMIME.pm
@@ -0,0 +1,101 @@
+use strict;
+use warnings;
+
+package RT::Test::SMIME;
+
+use Test::More;
+use base qw(RT::Test);
+use File::Temp qw(tempdir);
+
+sub import {
+    my $class = shift;
+    my %args  = @_;
+    my $t     = $class->builder;
+
+    $t->plan( skip_all => 'openssl executable is required.' )
+        unless RT::Test->find_executable('openssl');
+
+    require RT::Crypt;
+    $class->SUPER::import(%args);
+
+    RT::Test::diag "GnuPG --homedir " . RT->Config->Get('GnuPGOptions')->{'homedir'};
+
+    $class->set_rights(
+        Principal => 'Everyone',
+        Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+    );
+
+    $class->bootstrap_key_cf;
+
+    $class->export_to_level(1);
+}
+
+sub bootstrap_more_config {
+    my $self = shift;
+    my $handle = shift;
+    my $args = shift;
+
+    $self->SUPER::bootstrap_more_config($handle, $args, @_);
+
+    my $openssl = $self->find_executable('openssl');
+    my $keyring = $self->keyring_path;
+
+    print $handle qq{
+        Set(\%GnuPG, Enable => 0);
+        Set(\%SMIME =>
+            Enable => 1,
+            Passphrase => {
+                'root\@example.com' => '123456',
+                'sender\@example.com' => '123456',
+            },
+            OpenSSL => q{$openssl},
+            Keyring => q{$keyring},
+        );
+        Set(\@MailPlugins => qw(Auth::MailFrom Auth::Crypt));
+    };
+
+}
+
+sub bootstrap_key_cf {
+    my $self = shift;
+
+    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 $cache;
+sub keyring_path {
+    return $cache ||= shift->new_temp_dir(
+        crypt => smime => 'smime_keyring'
+    );
+} }
+
+sub key_path {
+    my $self = shift;
+    my $keys = RT::Test::get_abs_relocatable_dir(
+        (File::Spec->updir()) x 2,
+        qw(data smime keys),
+    );
+    return File::Spec->catfile( $keys => @_ ),
+}
+
+sub mail_set_path {
+    my $self = shift;
+    return RT::Test::get_abs_relocatable_dir(
+        (File::Spec->updir()) x 2,
+        qw(data smime mails),
+    );
+}
+
+1;
diff --git a/t/mail/smime/incoming.t b/t/mail/smime/incoming.t
index 50aa382..7039cfe 100644
--- a/t/mail/smime/incoming.t
+++ b/t/mail/smime/incoming.t
@@ -2,59 +2,13 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 21;
-
-my $openssl = RT::Test->find_executable('openssl');
-plan skip_all => 'openssl executable is required.'
-    unless $openssl;
+use RT::Test::SMIME tests => 21;
+my $test = 'RT::Test::SMIME';
 
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
 use RT::Tickets;
 
-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' );
-
-{
-    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";
 
@@ -109,7 +63,7 @@ RT::Test->close_mailgate_ok($mail);
             -from    => 'root at example.com',
             -to      => 'rt@' . $RT::rtname,
             -subject => "Encrypted message for queue",
-            File::Spec->catfile( $keys, 'sender at example.com.crt' ),
+            $test->key_path('sender at example.com.crt'),
         ),
         \"Subject: test\n\norzzzzzz",
         \$buf,
@@ -149,8 +103,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, 'root at example.com.crt' ),
-                -inkey  => File::Spec->catfile( $keys, 'root at example.com.key' ),
+                -signer => $test->key_path('root at example.com.crt'),
+                -inkey  => $test->key_path('root at example.com.key'),
             ),
             '|',
             shell_quote(
@@ -158,7 +112,7 @@ RT::Test->close_mailgate_ok($mail);
                 -from    => 'root at example.com',
                 -to      => 'rt@' . RT->Config->Get('rtname'),
                 -subject => "Encrypted and signed message for queue",
-                File::Spec->catfile( $keys, 'sender at example.com.crt' ),
+                $test->key_path('sender at example.com.crt'),
             )),
             \"Subject: test\n\norzzzzzz",
             \$buf,
diff --git a/t/mail/smime/outgoing.t b/t/mail/smime/outgoing.t
index d0a9407..418881c 100644
--- a/t/mail/smime/outgoing.t
+++ b/t/mail/smime/outgoing.t
@@ -2,43 +2,12 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 15;
-
-my $openssl = RT::Test->find_executable('openssl');
-plan skip_all => 'openssl executable is required.'
-    unless $openssl;
-
+use RT::Test::SMIME tests => 16;
+my $test = 'RT::Test::SMIME';
 
 use IPC::Run3 'run3';
 use RT::Interface::Email;
 
-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' );
-
 my ($url, $m) = RT::Test->started_ok;
 ok $m->login, "logged in";
 
@@ -112,9 +81,9 @@ END
     local $@;
     ok(eval {
         run3([
-            $openssl, qw(smime -decrypt -passin pass:123456),
-            '-inkey', File::Spec->catfile($keys, 'root at example.com.key'),
-            '-recip', File::Spec->catfile($keys, 'root at example.com.crt')
+            qw(openssl smime -decrypt -passin pass:123456),
+            '-inkey', $test->key_path('root at example.com.key'),
+            '-recip', $test->key_path('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
index fb45c00..dc9722d 100644
--- a/t/mail/smime/realmail.t
+++ b/t/mail/smime/realmail.t
@@ -2,53 +2,11 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 92;
-
-my $openssl = RT::Test->find_executable('openssl');
-plan skip_all => 'openssl executable is required.'
-    unless $openssl;
-
+use RT::Test::SMIME tests => 93;
 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' );
-
-{
-    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 $test = 'RT::Test::SMIME';
+my $mails = $test->mail_set_path;
 
 RT::Test->import_smime_key('root at example.com');
 RT::Test->import_smime_key('sender at example.com');
diff --git a/t/mail/smime/strict.t b/t/mail/smime/strict.t
index c91b438..df71677 100644
--- a/t/mail/smime/strict.t
+++ b/t/mail/smime/strict.t
@@ -2,60 +2,14 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 22;
-
-my $openssl = RT::Test->find_executable('openssl');
-plan skip_all => 'openssl executable is required.'
-    unless $openssl;
+use RT::Test::SMIME tests => 22;
+my $test = 'RT::Test::SMIME';
 
 use IPC::Run3 'run3';
 use String::ShellQuote 'shell_quote';
 use RT::Tickets;
 
-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,
-    Strict   => 1,
-    Incoming => ['SMIME'],
-    Outgoing => 'SMIME',
-);
-RT->Config->Set( GnuPG => Enable => 0 );
-RT->Config->Set( SMIME =>
-    Enable => 1,
-    Strict => 1,
-    OutgoingMessagesFormat => 'RFC',
-    Passphrase => {
-        'sender at example.com' => '123456',
-    },
-    OpenSSL => $openssl,
-    Keyring => $keyring,
-);
-
-RT->Config->Set( 'MailPlugins' => 'Auth::MailFrom', 'Auth::Crypt' );
-
-{
-    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,
-    );
-}
+RT->Config->Get('Crypt')->{'Strict'} = 1;
 
 {
     my $template = RT::Template->new($RT::SystemUser);
@@ -122,7 +76,7 @@ RT::Test->close_mailgate_ok($mail);
             -from    => 'root at example.com',
             -to      => 'rt@' . $RT::rtname,
             -subject => "Encrypted message for queue",
-            File::Spec->catfile( $keys, 'sender at example.com.crt' ),
+            $test->key_path('sender at example.com.crt' ),
         ),
         \"Subject: test\n\norzzzzzz",
         \$buf,
@@ -162,8 +116,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, 'root at example.com.crt' ),
-                -inkey  => File::Spec->catfile( $keys, 'root at example.com.key' ),
+                -signer => $test->key_path('root at example.com.crt' ),
+                -inkey  => $test->key_path('root at example.com.key' ),
             ),
             '|',
             shell_quote(
@@ -171,7 +125,7 @@ RT::Test->close_mailgate_ok($mail);
                 -from    => 'root at example.com',
                 -to      => 'rt@' . RT->Config->Get('rtname'),
                 -subject => "Encrypted and signed message for queue",
-                File::Spec->catfile( $keys, 'sender at example.com.crt' ),
+                $test->key_path('sender at example.com.crt' ),
             )),
             \"Subject: test\n\norzzzzzz",
             \$buf,
diff --git a/t/web/smime/outgoing.t b/t/web/smime/outgoing.t
index 6a5ff3c..bfa24d5 100644
--- a/t/web/smime/outgoing.t
+++ b/t/web/smime/outgoing.t
@@ -2,78 +2,23 @@
 use strict;
 use warnings;
 
-use RT::Test tests => 495;
-
-my $openssl = RT::Test->find_executable('openssl');
-plan skip_all => 'openssl executable is required.'
-    unless $openssl;
+use RT::Test::SMIME tests => 497;
+my $test = 'RT::Test::SMIME';
 
 use RT::Action::SendEmail;
 use File::Temp qw(tempdir);
 
 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',
-        'root 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']),
+    my $user = RT::Test->load_or_create_user(
+        Name => $user_email, EmailAddress => $user_email
     );
-    ok $status, "set key for the user" or diag "error: $msg";
+    ok $user && $user->id, 'loaded or created user';
+    RT::Test->import_smime_key($user_email, $user);
 }
 
 my $queue = RT::Test->load_or_create_queue(
@@ -187,6 +132,7 @@ foreach my $queue_set ( @variants ) {
 # like we are on another side recieving emails
 # ------------------------------------------------------------------------------
 
+my $keyring = $test->keyring_path;
 unlink $_ foreach glob( $keyring ."/*" );
 {
     my $sender = 'sender at example.com';

commit 9a66f8d64327bbd068bcc55fd66de401469c6b1a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 12 23:49:11 2012 +0400

    test encrypting attachments in DB with SMIME

diff --git a/t/crypt/smime/attachments-in-db.t b/t/crypt/smime/attachments-in-db.t
new file mode 100644
index 0000000..da18786
--- /dev/null
+++ b/t/crypt/smime/attachments-in-db.t
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test::SMIME tests => 13;
+
+use IPC::Run3 'run3';
+use String::ShellQuote 'shell_quote';
+use RT::Tickets;
+
+RT->Config->Get('Crypt')->{'AllowEncryptDataInDB'} = 1;
+
+RT::Test->import_smime_key('sender at example.com');
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'General',
+    CorrespondAddress => 'sender at example.com',
+);
+ok $queue && $queue->id, 'loaded or created queue';
+
+{
+    my $ticket = RT::Test->create_ticket(
+        Queue   => $queue->id,
+        Subject => 'test',
+        Content => 'test',
+    );
+
+    my $txn = $ticket->Transactions->First;
+    ok $txn && $txn->id, 'found first transaction';
+    is $txn->Type, 'Create', 'it is Create transaction';
+
+    my $attach = $txn->Attachments->First;
+    ok $attach && $attach->id, 'found attachment';
+    is $attach->Content, 'test', 'correct content';
+
+    my ($status, $msg) = $attach->Encrypt;
+    ok $status, 'encrypted attachment' or diag "error: $msg";
+
+    isnt $attach->Content, 'test', 'correct content';
+
+    ($status, $msg) = $attach->Decrypt;
+    ok $status, 'decrypted attachment' or diag "error: $msg";
+
+    is $attach->Content, 'test', 'correct content';
+}
+
+
+

commit 8baf2802e33db474b0900f4e6ee4dc8cc19b4c63
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu Jan 12 23:49:51 2012 +0400

    reference proper argument in loc string

diff --git a/share/html/Admin/Elements/ShowKeyInfo b/share/html/Admin/Elements/ShowKeyInfo
index eaccb19..fd796dc 100644
--- a/share/html/Admin/Elements/ShowKeyInfo
+++ b/share/html/Admin/Elements/ShowKeyInfo
@@ -101,8 +101,8 @@ return unless @protocols;
 
 my $title;
 unless ( $Type eq 'private' ) {
-    $title = loc('Public key(s) for [_2]', $EmailAddress);
+    $title = loc('Public key(s) for [_1]', $EmailAddress);
 } else {
-    $title = loc('Private key(s) for [_2]', $EmailAddress);
+    $title = loc('Private key(s) for [_1]', $EmailAddress);
 }
 </%INIT>

commit ae62e048218fe3479e0c4579f7591ce5c3d30a0e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 13 01:58:03 2012 +0400

    adjust number of tests

diff --git a/t/mail/smime/realmail.t b/t/mail/smime/realmail.t
index dc9722d..f84f475 100644
--- a/t/mail/smime/realmail.t
+++ b/t/mail/smime/realmail.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => 93;
+use RT::Test::SMIME tests => 92;
 use Digest::MD5 qw(md5_hex);
 
 my $test = 'RT::Test::SMIME';

commit 9f13ad9fadf0be157ad20e4025d2847873466bed
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 15:05:02 2012 +0400

    return back some wording to make patching old RT easier

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 2ebba23..bd0a2f3 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -319,12 +319,12 @@ sub bootstrap_config {
     my $self = shift;
     my %args = @_;
 
-    my $config = $self->new_temp_file( config => RT => 'RT_SiteConfig.pm' );
-    open( my $config_fh, '>', $config )
-        or die "Couldn't open $config: $!";
+    my $config_file = $self->new_temp_file( config => RT => 'RT_SiteConfig.pm' );
+    open( my $config, '>', $config_file )
+        or die "Couldn't open $config_file: $!";
 
     my $dbname = $ENV{RT_TEST_PARALLEL}? "rt4test_$port" : "rt4test";
-    print $config_fh qq{
+    print $config qq{
 Set( \$WebDomain, "localhost");
 Set( \$WebPort,   $port);
 Set( \$WebPath,   "");
@@ -334,37 +334,37 @@ 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";
-        print $config_fh "Set( \$DatabaseUser , '$dbname');\n";
+        print $config "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n";
+        print $config "Set( \$DatabaseUser , '$dbname');\n";
     } else {
-        print $config_fh "Set( \$DatabaseName , '$dbname');\n";
-        print $config_fh "Set( \$DatabaseUser , 'u${dbname}');\n";
+        print $config "Set( \$DatabaseName , '$dbname');\n";
+        print $config "Set( \$DatabaseUser , 'u${dbname}');\n";
     }
 
     if ( $args{'plugins'} ) {
-        print $config_fh "Set( \@Plugins, qw(". join( ' ', @{ $args{'plugins'} } ) .") );\n";
+        print $config "Set( \@Plugins, qw(". join( ' ', @{ $args{'plugins'} } ) .") );\n";
 
         my $plugin_data = File::Spec->rel2abs("t/data/plugins");
         print $config qq[\$RT::PluginPath = "$plugin_data";\n];
     }
 
     if ( $INC{'Devel/Cover.pm'} ) {
-        print $config_fh "Set( \$DevelMode, 0 );\n";
+        print $config "Set( \$DevelMode, 0 );\n";
     }
     elsif ( $ENV{RT_TEST_DEVEL} ) {
-        print $config_fh "Set( \$DevelMode, 1 );\n";
+        print $config "Set( \$DevelMode, 1 );\n";
     }
     else {
-        print $config_fh "Set( \$DevelMode, 0 );\n";
+        print $config "Set( \$DevelMode, 0 );\n";
     }
 
-    $self->bootstrap_logging( $config_fh );
+    $self->bootstrap_logging( $config );
 
     # set mail catcher
     my $mail_catcher = $tmp{'mailbox'} = File::Spec->catfile(
         $tmp{'directory'}->dirname, 'mailbox.eml'
     );
-    print $config_fh <<END;
+    print $config <<END;
 Set( \$MailCommand, sub {
     my \$MIME = shift;
 
@@ -377,15 +377,15 @@ Set( \$MailCommand, sub {
 } );
 END
 
-    $self->bootstrap_more_config($config_fh, \%args);
+    $self->bootstrap_more_config($config, \%args);
 
-    print $config_fh $args{'config'} if $args{'config'};
+    print $config $args{'config'} if $args{'config'};
 
-    print $config_fh "\n1;\n";
+    print $config "\n1;\n";
     $ENV{'RT_SITE_CONFIG'} = $config;
-    close $config_fh;
+    close $config;
 
-    return $config;
+    return $config_file;
 }
 
 sub bootstrap_more_config { }

commit 73924fa645d9c5523b055f934b1c574c31039d65
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 15:43:10 2012 +0400

    move SMIME upgrade script from 3.8.8 to 4.1.0

diff --git a/TODO.SMIME b/TODO.SMIME
index 949abca..a82110d 100644
--- a/TODO.SMIME
+++ b/TODO.SMIME
@@ -9,5 +9,3 @@
 * 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
diff --git a/etc/upgrade/3.8.8/content b/etc/upgrade/3.8.8/content
index e7748e7..cad77e9 100644
--- a/etc/upgrade/3.8.8/content
+++ b/etc/upgrade/3.8.8/content
@@ -34,36 +34,5 @@
             $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;
-    },
 );
 
diff --git a/etc/upgrade/4.1.0/content b/etc/upgrade/4.1.0/content
new file mode 100644
index 0000000..b00591c
--- /dev/null
+++ b/etc/upgrade/4.1.0/content
@@ -0,0 +1,35 @@
+ at Initial = (
+    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 CF. 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 922cdb3c4a987f7dc8fc19948a4cba64955642c1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 15:59:07 2012 +0400

    rename *GnuPG* components to *Crypt*

diff --git a/share/html/Elements/GnuPG/KeyIssues b/share/html/Elements/Crypt/KeyIssues
similarity index 98%
rename from share/html/Elements/GnuPG/KeyIssues
rename to share/html/Elements/Crypt/KeyIssues
index f19e026..f566e8d 100644
--- a/share/html/Elements/GnuPG/KeyIssues
+++ b/share/html/Elements/Crypt/KeyIssues
@@ -74,7 +74,7 @@ There is a problem with key(s) for address <% $issue->{'EmailAddress'} %>, but t
 <% $issue->{'Message'} %>
 <br />
 Select a key you want to use for encryption:
-<& /Elements/GnuPG/SelectKeyForEncryption,
+<& /Elements/Crypt/SelectKeyForEncryption,
     Name         => 'UseKey-'. $issue->{'EmailAddress'},
     EmailAddress => $issue->{'EmailAddress'},
     Default      => ( $issue->{'User'}? $issue->{'User'}->PreferredKey : undef ),
diff --git a/share/html/Elements/GnuPG/SelectKeyForEncryption b/share/html/Elements/Crypt/SelectKeyForEncryption
similarity index 100%
rename from share/html/Elements/GnuPG/SelectKeyForEncryption
rename to share/html/Elements/Crypt/SelectKeyForEncryption
diff --git a/share/html/Elements/GnuPG/SelectKeyForSigning b/share/html/Elements/Crypt/SelectKeyForSigning
similarity index 100%
rename from share/html/Elements/GnuPG/SelectKeyForSigning
rename to share/html/Elements/Crypt/SelectKeyForSigning
diff --git a/share/html/Elements/GnuPG/SignEncryptWidget b/share/html/Elements/Crypt/SignEncryptWidget
similarity index 99%
rename from share/html/Elements/GnuPG/SignEncryptWidget
rename to share/html/Elements/Crypt/SignEncryptWidget
index e17c73d..6a1b15e 100644
--- a/share/html/Elements/GnuPG/SignEncryptWidget
+++ b/share/html/Elements/Crypt/SignEncryptWidget
@@ -91,7 +91,7 @@ $self => undef,
 <%INIT>
 return unless $self;
 
-return $m->comp( '/Elements/GnuPG/KeyIssues',
+return $m->comp( '/Elements/Crypt/KeyIssues',
     Issues => $self->{'GnuPGRecipientsKeyIssues'} || [],
     SignAddresses => $self->{'GnuPGCanNotSignAs'} || [],
 );
diff --git a/share/html/Prefs/Other.html b/share/html/Prefs/Other.html
index 9a04cc2..fc4e1b3 100644
--- a/share/html/Prefs/Other.html
+++ b/share/html/Prefs/Other.html
@@ -69,7 +69,7 @@
 
 % if ( RT->Config->Get('GnuPG')->{'Enable'} ) {
 <&|/Widgets/TitleBox, title => loc( 'Cryptography' ) &>
-<&|/l&>Preferred key</&>: <& /Elements/GnuPG/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
+<&|/l&>Preferred key</&>: <& /Elements/Crypt/SelectKeyForEncryption, EmailAddress => $UserObj->EmailAddress, Default => $UserObj->PreferredKey &>
 </&>
 % }
 
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index ad0023f..b98e3ac 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -58,7 +58,7 @@
 % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 
 % if ($gnupg_widget) {
-  <& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+  <& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
 % }
 
 <div id="Ticket-Create-basics">
@@ -164,7 +164,7 @@
 
 % if ( $gnupg_widget ) {
 <tr><td> </td><td colspan="5">
-<& /Elements/GnuPG/SignEncryptWidget, self => $gnupg_widget, QueueObj => $QueueObj &>
+<& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, QueueObj => $QueueObj &>
 </td></tr>
 % }
 
@@ -389,15 +389,15 @@ unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
 
 my $checks_failure = 0;
 
-my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
-$m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
+my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS );
+$m->comp( '/Elements/Crypt/SignEncryptWidget:Process',
     self      => $gnupg_widget,
     QueueObj  => $QueueObj,
 );
 
 
 if ( !exists $ARGS{'AddMoreAttach'} && ($ARGS{'id'}||'') eq 'new' ) {
-    my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
+    my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check',
         self      => $gnupg_widget,
         Operation => 'Create',
         QueueObj  => $QueueObj,
diff --git a/share/html/Ticket/GnuPG.html b/share/html/Ticket/Crypt.html
similarity index 100%
rename from share/html/Ticket/GnuPG.html
rename to share/html/Ticket/Crypt.html
diff --git a/share/html/Ticket/Elements/ShowGnuPGStatus b/share/html/Ticket/Elements/ShowCryptStatus
similarity index 100%
rename from share/html/Ticket/Elements/ShowGnuPGStatus
rename to share/html/Ticket/Elements/ShowCryptStatus
diff --git a/share/html/Ticket/Elements/ShowTransaction b/share/html/Ticket/Elements/ShowTransaction
index ffc06b9..919c2f5 100644
--- a/share/html/Ticket/Elements/ShowTransaction
+++ b/share/html/Ticket/Elements/ShowTransaction
@@ -85,7 +85,7 @@ $DisplayPath => RT->Config->Get('WebPath')."/Ticket/Display.html?id=".$Ticket->i
 $AttachPath => RT->Config->Get('WebPath')."/Ticket/Attachment"
 $UpdatePath => RT->Config->Get('WebPath')."/Ticket/Update.html"
 $ForwardPath => RT->Config->Get('WebPath')."/Ticket/Forward.html"
-$EncryptionPath => RT->Config->Get('WebPath')."/Ticket/GnuPG.html"
+$EncryptionPath => RT->Config->Get('WebPath')."/Ticket/Crypt.html"
 $EmailRecordPath => RT->Config->Get('WebPath')."/Ticket/ShowEmailRecord.html"
 $Attachments => undef
 $AttachmentContent => undef
diff --git a/share/html/Ticket/Elements/ShowTransactionAttachments b/share/html/Ticket/Elements/ShowTransactionAttachments
index 9ff6d10..029108c 100644
--- a/share/html/Ticket/Elements/ShowTransactionAttachments
+++ b/share/html/Ticket/Elements/ShowTransactionAttachments
@@ -51,7 +51,7 @@
 foreach my $message ( grep $_->__Value('Parent') == $Parent, @$Attachments ) {
 
     if (RT->Config->Get('GnuPG')->{'Enable'}) {
-        $m->comp( 'ShowGnuPGStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
+        $m->comp( 'ShowCryptStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
     }
 
     $m->comp( 'ShowMessageHeaders',
diff --git a/share/html/Ticket/Update.html b/share/html/Ticket/Update.html
index a23bb2f..5d6f4b2 100644
--- a/share/html/Ticket/Update.html
+++ b/share/html/Ticket/Update.html
@@ -58,7 +58,7 @@
 <input type="hidden" class="hidden" name="DefaultStatus" value="<% $DefaultStatus ||''%>" />
 <input type="hidden" class="hidden" name="Action" value="<% $ARGS{Action}||'' %>" />
 
-<& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
 
 <div id="ticket-update-metadata">
   <&|/Widgets/TitleBox, title => loc('Ticket and Transaction') &>
@@ -151,7 +151,7 @@
 
 % if ( $gnupg_widget ) {
 <tr><td> </td><td>
-<& /Elements/GnuPG/SignEncryptWidget,
+<& /Elements/Crypt/SignEncryptWidget,
     self => $gnupg_widget,
     TicketObj => $TicketObj,
 &>
@@ -268,8 +268,8 @@ unless (keys %{$session{'Attachments'}} and $ARGS{'UpdateAttach'}) {
     delete $session{'Attachments'};
 }
 
-my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
-$m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
+my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS );
+$m->comp( '/Elements/Crypt/SignEncryptWidget:Process',
     self => $gnupg_widget,
     TicketObj => $TicketObj,
 );
@@ -297,7 +297,7 @@ if ( $ARGS{'SubmitTicket'} ) {
             push @results, loc($CF->Name) . ': ' . $msg;
         }
     }
-    my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
+    my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check',
         self      => $gnupg_widget,
         TicketObj => $TicketObj,
     );
diff --git a/share/html/m/ticket/create b/share/html/m/ticket/create
index de3eb7f..a68d3dc 100644
--- a/share/html/m/ticket/create
+++ b/share/html/m/ticket/create
@@ -183,15 +183,15 @@ unless (keys %{$session{'Attachments'}} and $ARGS{'id'} eq 'new') {
 
 my $checks_failure = 0;
 
-my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
-$m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
+my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS );
+$m->comp( '/Elements/Crypt/SignEncryptWidget:Process',
     self      => $gnupg_widget,
     QueueObj  => $QueueObj,
 );
 
 
 if ( !exists $ARGS{'AddMoreAttach'} && ($ARGS{'id'}||'') eq 'new' ) {
-    my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
+    my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check',
         self      => $gnupg_widget,
         Operation => 'Create',
         QueueObj  => $QueueObj,
@@ -244,7 +244,7 @@ if ((!exists $ARGS{'AddMoreAttach'}) and (defined($ARGS{'id'}) and $ARGS{'id'} e
 <input type="hidden" class="hidden" name="id" value="new" />
 % $m->callback( CallbackName => 'FormStart', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 % if ($gnupg_widget) {
-<& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
 % }
 
 
@@ -350,7 +350,7 @@ $showrows->(
 
 
 % if ( $gnupg_widget ) {
-<& /Elements/GnuPG/SignEncryptWidget, self => $gnupg_widget, QueueObj => $QueueObj &>
+<& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, QueueObj => $QueueObj &>
 % }
 
 
diff --git a/share/html/m/ticket/reply b/share/html/m/ticket/reply
index 45ae6fa..70175cd 100644
--- a/share/html/m/ticket/reply
+++ b/share/html/m/ticket/reply
@@ -56,7 +56,7 @@
 <input type="hidden" class="hidden" name="Action" value="<% $ARGS{Action}||'' %>" />
 
 % if ($gnupg_widget) {
-<& /Elements/GnuPG/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
+<& /Elements/Crypt/SignEncryptWidget:ShowIssues, self => $gnupg_widget &>
 % }
 
 <div class="entry"><span class="label"><&|/l&>Status</&>:</span>
@@ -136,7 +136,7 @@
 </div>
 
 % if ( $gnupg_widget ) {
-<& /Elements/GnuPG/SignEncryptWidget, self => $gnupg_widget, QueueObj => $t->QueueObj &>
+<& /Elements/Crypt/SignEncryptWidget, self => $gnupg_widget, QueueObj => $t->QueueObj &>
 % }
 
 <& /Elements/Submit, Label => loc('Update Ticket'), Name => 'SubmitTicket' &>
@@ -235,8 +235,8 @@ unless ( keys %{ $session{'Attachments'} }
     }
 }
 
-my $gnupg_widget = $m->comp('/Elements/GnuPG/SignEncryptWidget:new', Arguments => \%ARGS );
-$m->comp( '/Elements/GnuPG/SignEncryptWidget:Process',
+my $gnupg_widget = $m->comp('/Elements/Crypt/SignEncryptWidget:new', Arguments => \%ARGS );
+$m->comp( '/Elements/Crypt/SignEncryptWidget:Process',
     self => $gnupg_widget,
     TicketObj => $t,
 );
@@ -246,7 +246,7 @@ $m->callback( CallbackName => 'BeforeUpdate', ARGSRef => \%ARGS, skip_update =>
               checks_failure => $checks_failure, results => \@results, TicketObj => $t );
 
 if ( !$checks_failure && !$skip_update && exists $ARGS{SubmitTicket} ) {
-    my $status = $m->comp('/Elements/GnuPG/SignEncryptWidget:Check',
+    my $status = $m->comp('/Elements/Crypt/SignEncryptWidget:Check',
         self      => $gnupg_widget,
         TicketObj => $t,
     );

commit 3c96c9b7465bd4f219800b1a63fe2e4e4c9e65b3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 18:12:58 2012 +0400

    return more of master's RT::Test

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index bd0a2f3..a46a7b4 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -382,10 +382,10 @@ END
     print $config $args{'config'} if $args{'config'};
 
     print $config "\n1;\n";
-    $ENV{'RT_SITE_CONFIG'} = $config;
+    $ENV{'RT_SITE_CONFIG'} = $config_file;
     close $config;
 
-    return $config_file;
+    return $config;
 }
 
 sub bootstrap_more_config { }

commit 1f28664d7be442a14363c9a6f5b39d60610833c5
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 18:13:43 2012 +0400

    we do the same in bootstrap_logging

diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index a46a7b4..0bd0f2c 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -330,8 +330,6 @@ Set( \$WebPort,   $port);
 Set( \$WebPath,   "");
 Set( \@LexiconLanguages, qw(en zh_TW 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 "Set( \$DatabaseName , '$ENV{'RT_TEST_DB_SID'}' );\n";

commit 78f062fce230b4b12abaa560ceda74b8fbc8d808
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 18:26:43 2012 +0400

    separate smime keyring dir from gpg's

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 5e430c5..9143ab9 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2149,7 +2149,7 @@ More details in L<RT::Crypt::SMIME>.
 Set( %SMIME,
     Enable => @RT_SMIME@,
     OpenSSL => '/usr/bin/openssl',
-    Keyring => q{@RT_VAR_PATH@/data/gpg},
+    Keyring => q{@RT_VAR_PATH@/data/smime},
     Passphrase => '',
 );
 

commit fb0b760efe2199ee7009e10230185cf7ec298678
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Tue Jan 17 19:24:07 2012 +0400

    AllowEncryptDataInDB is in %Crypt now, not %GnuPG

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index 9143ab9..f0d58f2 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2174,7 +2174,6 @@ signatures instead of 'RFC' (GPG/MIME: RFC3156 and RFC1847) format.
 Set(%GnuPG,
     Enable                 => @RT_GPG@,
     OutgoingMessagesFormat => "RFC", # Inline
-    AllowEncryptDataInDB   => 0,
 );
 
 =item C<%GnuPGOptions>

commit a20ecfaec8eab1115cec78d023ab46db9d666f58
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Apr 20 12:15:00 2012 -0400

    Switch to using the FormatStatus method
    
    The Decryption methods use this, and the verify version was just
    slightly different, so use the helper.

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 9a61163..2a26166 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -391,11 +391,11 @@ sub VerifyRFC3851 {
     $args{'Data'}->parts([ $res_entity->parts ]);
     $args{'Data'}->make_singlepart;
 
-    $res{'status'} =
-        "Operation: Verify\nStatus: DONE\n"
-        ."Message: The signature is good\n"
-        ."UserString: ". $signers[0]{'User'}[0]{'String'} ."\n"
-    ;
+    $res{'status'} = $self->FormatStatus({
+            Operation   => 'Verify', Status => 'DONE',
+            Message     => 'The signature is good',
+            UserString  => $signers[0]{'User'}[0]{'String'},
+        });
 
     return %res;
 }

commit 0cbd0c18e9217529a3a6cf514fb20119c13406c7
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Apr 20 12:16:49 2012 -0400

    Show status if you're only using S/MIME
    
    If you didn't have GnuPG enabled, you wouldn't see S/MIME status
    messages.

diff --git a/share/html/Ticket/Elements/ShowTransactionAttachments b/share/html/Ticket/Elements/ShowTransactionAttachments
index 029108c..040fff1 100644
--- a/share/html/Ticket/Elements/ShowTransactionAttachments
+++ b/share/html/Ticket/Elements/ShowTransactionAttachments
@@ -50,7 +50,7 @@
 # For each of these attachments
 foreach my $message ( grep $_->__Value('Parent') == $Parent, @$Attachments ) {
 
-    if (RT->Config->Get('GnuPG')->{'Enable'}) {
+    if (RT::Crypt->EnabledProtocols) {
         $m->comp( 'ShowCryptStatus', Attachment => $message, WarnUnsigned => $WarnUnsigned );
     }
 

commit a687c946aa45d474311d69c1fa5bf0d04930066e
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Apr 20 12:17:18 2012 -0400

    Switch the label because there can be S/MIME messages
    
    I don't know enough about the structure of @messages to know what
    *should* be in the list in this spot, but it probably isn't one of the
    X-RT-SMIME-Status headers since the GnuPG code must return just a plain
    string.
    
    That header is also unfortunately being squished in the DB (losing
    newlines) so we don't parse it back out for display properly.

diff --git a/share/html/Ticket/Elements/ShowCryptStatus b/share/html/Ticket/Elements/ShowCryptStatus
index 8a27c70..964ea45 100644
--- a/share/html/Ticket/Elements/ShowCryptStatus
+++ b/share/html/Ticket/Elements/ShowCryptStatus
@@ -46,7 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 <table class="crypt-runs">
-<tr><td align="right" class="labeltop" rowspan="<% scalar @messages %>">GnuPG:</td>
+<tr><td align="right" class="labeltop" rowspan="<% scalar @messages %>">Encryption:</td>
 <td><% shift @messages %></td></tr>
 
 % foreach my $msg( @messages ) {

commit e08669ffd1ec6e4eb0a211a4530931ae898718fc
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Apr 20 23:29:02 2012 +0400

    don't load crypt implementations right away
    
    loading GnuPG module fails if you don't have required
    modules

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index c19bdee..8f348d1 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -4,9 +4,6 @@ use warnings;
 
 package RT::Crypt;
 
-require RT::Crypt::GnuPG;
-require RT::Crypt::SMIME;
-
 =head1 NAME
 
 RT::Crypt - encrypt/decrypt and sign/verify subsystem for RT

commit 0cea2b44020b5249cb85eba3a541363d326a35f3
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Apr 20 23:29:52 2012 +0400

    more protection, make sure crypt modules are laoded

diff --git a/lib/RT/Config.pm b/lib/RT/Config.pm
index 9d4401d..222cdc3 100644
--- a/lib/RT/Config.pm
+++ b/lib/RT/Config.pm
@@ -617,6 +617,15 @@ our %META = (
             require RT::Crypt;
             my @enabled = RT::Crypt->EnabledProtocols;
 
+            foreach my $proto (splice @enabled) {
+                local $@;
+                eval "require RT::Crypt::$proto; 1" or do {
+                    $RT::Logger->error("You enabled $proto cryptography, but we couldn't load module RT::Crypt::$proto: $@");
+                    next;
+                };
+                push @enabled, $proto;
+            }
+
             my $opt = $self->Get('Crypt');
             $opt->{'Enable'} = scalar @enabled;;
             unless ( $opt->{'Incoming'} && @{ $opt->{'Incoming'} } ) {

commit 80b0e2e3f7e3590188a00bb4873280cf77ebf3d9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Apr 30 22:28:38 2012 +0400

    drop shift() call, shouldn't be there
    
    it was a typo, sometimes harmless, sometimes can shift
    defined value from @ARGV. Depends on web server environment.

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 2a26166..45c4eef 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -92,7 +92,7 @@ and passphrase pairs for keys in the keyring.
 
 =cut
 
-{ my $cache = shift;
+{ my $cache = '';
 sub OpenSSLPath {
     return $cache ||= RT->Config->Get('SMIME')->{'OpenSSL'};
 } }

commit 610a191e77cf506324e474048c460e1fb0593be9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Apr 30 22:31:48 2012 +0400

    be double sure openssl path is set to something
    
    we do it in a few places, it's ok do it here as well

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 45c4eef..b875b8f 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -94,7 +94,7 @@ and passphrase pairs for keys in the keyring.
 
 { my $cache = '';
 sub OpenSSLPath {
-    return $cache ||= RT->Config->Get('SMIME')->{'OpenSSL'};
+    return $cache ||= RT->Config->Get('SMIME')->{'OpenSSL'} || 'openssl';
 } }
 
 sub SignEncrypt {

commit 3526f30a0cfc0edc04a2bab4c75b360f98767662
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Apr 30 22:34:19 2012 +0400

    special case of how openssl prints SMIME certs
    
    It's possible to have the following structure:
    
        <key><separator>
            <value with separator>
    
    Fix is sort of workaround

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index b875b8f..e4d1291 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -830,7 +830,9 @@ sub ParseCertificateInfo {
         # Validity # no trailing ':'
         # Not After : XXXXXX # space before ':'
         # countryName=RU # '=' as separator
-        my ($prefix, $key, $value) = ($line =~ /^(\s*)(.*?)\s*(?:[:=]\s*(.*?)|)\s*$/);
+        # Serial Number:
+        #     he:xv:al:ue
+        my ($prefix, $key, $value) = ($line =~ /^(\s*)(.*?)\s*(?:(?:=\s*|:\s+)(\S.*?)|:|)\s*$/);
         if ( $first_line ) {
             $prefix{$prefix} = \%res;
             $first_line = 0;
@@ -854,6 +856,26 @@ sub ParseCertificateInfo {
         ($prev_prefix, $prev_key) = ($prefix, $key);
     }
 
+    my ($filter_out, $wfilter_out);
+    $filter_out = $wfilter_out = sub {
+        my $h = shift;
+        foreach my $e ( keys %$h ) {
+            next unless ref $h->{$e};
+            if ( 1 == keys %{$h->{$e}} ) {
+                my $sube = (keys %{$h->{$e}})[0];
+                if ( ref $h->{$e}{$sube} && !keys %{ $h->{$e}{$sube} } ) {
+                    $h->{$e} = $sube;
+                    next;
+                }
+            }
+
+            $filter_out->( $h->{$e} );
+        }
+    };
+    Scalar::Util::weaken($wfilter_out);
+
+    $filter_out->(\%res);
+
     return %res;
 }
 

commit 9e36131c6699ceac5f523ddef8406461961eed56
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Mon Apr 30 22:37:43 2012 +0400

    special case of how openssl prints SMIME certs
    
    openssl 0.9.8 and openssl 1.0.1 print the same cert
    very differently. 1.0.1 prints the following thing:
    
        <key>:
            ... nested structure ...
        <otherkey>: <value>
            ... nested structure continues ...
    
    Example:
    
        Data:
            ...
            Serial Number: 9974010075738841110 (0x8a6acd51be94a016)
        Signature Algorithm: sha1WithRSAEncryption
            Issuer: ...
            ...
    
    So it's hard to say where Issuer belongs.
    
    Now "<key>: <value>" strings don't delete pointers to existing
    placeholders for nested structures. Hope output wouldn't get
    more messier.

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index e4d1291..7375594 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -848,12 +848,12 @@ sub ParseCertificateInfo {
         }
         else {
             $put_into->{$key} = {};
-        }
-        delete $prefix{$_} foreach
-            grep length($_) > length($prefix),
-            keys %prefix;
+            delete $prefix{$_} foreach
+                grep length($_) > length($prefix),
+                keys %prefix;
 
-        ($prev_prefix, $prev_key) = ($prefix, $key);
+            ($prev_prefix, $prev_key) = ($prefix, $key);
+        }
     }
 
     my ($filter_out, $wfilter_out);

commit 542d544e53e1f3f67378c0a436393f1b8b7661f2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Thu May 3 00:18:52 2012 +0400

    newlines don't survive in header fields
    
    use similar to GnuPG format

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 7375594..711f07a 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -509,9 +509,9 @@ sub FormatStatus {
 
     my $res = '';
     foreach ( @status ) {
-        $res .= "\n" if $res;
+        $res .= "[SMIME:]\n" if $res;
         while ( my ($k, $v) = each %$_ ) {
-            $res .= $k .": ". $v ."\n";
+            $res .= "[SMIME:]". $k .": ". $v ."\n";
         }
     }
 
@@ -523,10 +523,10 @@ sub ParseStatus {
     my $status = shift;
     return () unless $status;
 
-    my @status = split /\n\n/, $status;
+    my @status = split /\s*(?:\[SMIME:\]\s*){2}/, $status;
     foreach my $block ( grep length, @status ) {
         chomp $block;
-        $block = { map { s/^\s+//; s/\s+$//; $_ } map split(/:/, $_, 2), split /\n+/, $block };
+        $block = { map { s/^\s+//; s/\s+$//; $_ } map split(/:/, $_, 2), split /\s*\[SMIME:\]/, $block };
     }
     foreach my $block ( grep $_->{'EncryptedTo'}, @status ) {
         $block->{'EncryptedTo'} = [{

commit 8416f33f29af691725d5e28ac183a27a45815f4f
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed May 23 21:34:58 2012 +0400

    avoid scrip death when correspond address is not set
    
    It's misconfiguration when correspond address is not
    set in the config and for a queue, but still stack
    trace from Crypt.pm into scrip and further is not
    helpful.

diff --git a/lib/RT/Crypt.pm b/lib/RT/Crypt.pm
index 8f348d1..87d6889 100644
--- a/lib/RT/Crypt.pm
+++ b/lib/RT/Crypt.pm
@@ -201,7 +201,10 @@ sub SignEncrypt {
     if ( $args{'Sign'} && !defined $args{'Signer'} ) {
         $args{'Signer'} =
             $self->UseKeyForSigning
-            || (Email::Address->parse( $entity->head->get( 'From' ) ))[0]->address;
+            || do {
+                my $addr = (Email::Address->parse( $entity->head->get( 'From' ) ))[0];
+                $addr? $addr->address : undef
+            };
     }
     if ( $args{'Encrypt'} && !$args{'Recipients'} ) {
         my %seen;
diff --git a/t/crypt/no-signer-address.t b/t/crypt/no-signer-address.t
new file mode 100644
index 0000000..57c2591
--- /dev/null
+++ b/t/crypt/no-signer-address.t
@@ -0,0 +1,35 @@
+use strict;
+use warnings;
+
+use RT::Test::GnuPG
+  tests         => 5,
+  gnupg_options => {
+    passphrase    => 'rt-test',
+    'trust-model' => 'always',
+  }
+;
+
+my $queue;
+{
+    $queue = RT::Test->load_or_create_queue(
+        Name => 'Regression',
+        Sign => 1,
+    );
+    ok $queue && $queue->id, 'loaded or created queue';
+    ok !$queue->CorrespondAddress, 'address not set';
+}
+
+{
+    my $ticket = RT::Ticket->new( RT->SystemUser );
+    my ($status, undef, $msg) = $ticket->Create(
+        Queue => $queue->id,
+        Subject => 'test',
+        Requestor => 'root at localhost',
+    );
+    ok $status, "created ticket" or diag "error: $msg";
+
+    my $log = RT::Test->file_content([RT::Test->temp_directory, 'rt.debug.log']);
+    like $log, qr{secret key not available}, 'error in the log';
+    unlike $log, qr{Scrip .*? died}m, "scrip didn't die";
+}
+

commit 1ca7c7f95998e40ae45df89846b9874a31dc9324
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jul 6 23:28:57 2012 +0300

    processes Sign/Encrypt values later on update
    
    Undef value is indication of "do whatever queue's options say".
    Code in Interface::Web was changed to fix some bug when value is not
    a boolean but an array (d3341b787e3a734c441596b84ce621339f327851).
    This was correct thing to do, but in a wrong place.
    
    Also, we don't need Encode now as we just turn value into ascii
    scalar without UTF8 flag.
    
    The bug shouldn't affect system unless admin wants to hide GnuPG
    widget that allows people to change crypt preferences on reply.

diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm
index 1eaefc7..d08631c 100644
--- a/lib/RT/Interface/Web.pm
+++ b/lib/RT/Interface/Web.pm
@@ -1973,8 +1973,8 @@ sub ProcessUpdateMessage {
     }
 
     my %message_args = (
-        Sign         => ( $args{ARGSRef}->{'Sign'} ? 1 : 0 ),
-        Encrypt      => ( $args{ARGSRef}->{'Encrypt'} ? 1 : 0 ),
+        Sign         => $args{ARGSRef}->{'Sign'},
+        Encrypt      => $args{ARGSRef}->{'Encrypt'},
         MIMEObj      => $Message,
         TimeTaken    => $args{ARGSRef}->{'UpdateTimeWorked'}
     );
diff --git a/lib/RT/Ticket.pm b/lib/RT/Ticket.pm
index 48d1442..908fcc2 100644
--- a/lib/RT/Ticket.pm
+++ b/lib/RT/Ticket.pm
@@ -2259,7 +2259,7 @@ sub _RecordNote {
 
     foreach my $argument (qw(Encrypt Sign)) {
         $args{'MIMEObj'}->head->replace(
-            "X-RT-$argument" => Encode::encode_utf8( $args{ $argument } )
+            "X-RT-$argument" => $args{ $argument } ? 1 : 0
         ) if defined $args{ $argument };
     }
 

commit 0673951183494f72d60627a628f59d359d524615
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 11 18:56:13 2012 +0300

    format status string so multiple can be appended together

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 711f07a..71104c2 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -509,10 +509,10 @@ sub FormatStatus {
 
     my $res = '';
     foreach ( @status ) {
-        $res .= "[SMIME:]\n" if $res;
         while ( my ($k, $v) = each %$_ ) {
             $res .= "[SMIME:]". $k .": ". $v ."\n";
         }
+        $res .= "[SMIME:]\n";
     }
 
     return $res;
diff --git a/t/crypt/smime/status-string.t b/t/crypt/smime/status-string.t
new file mode 100644
index 0000000..07e7989
--- /dev/null
+++ b/t/crypt/smime/status-string.t
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test::SMIME tests => 3;
+
+note "simple round trip";
+{
+    my %data = (Foo => 'bar', Baz => 'zoo');
+    is_deeply(
+        [ RT::Crypt::SMIME->ParseStatus( RT::Crypt::SMIME->FormatStatus( \%data, \%data ) ) ],
+        [ \%data, \%data ],
+    );
+}
+
+note "status appendability";
+{
+    my %data = (Foo => 'bar', Baz => 'zoo');
+    is_deeply(
+        [ RT::Crypt::SMIME->ParseStatus(
+            RT::Crypt::SMIME->FormatStatus( \%data )
+            . RT::Crypt::SMIME->FormatStatus( \%data )
+        ) ],
+        [ \%data, \%data ],
+    );
+}

commit e35622820d93fe3c852460e87ba162507dc6cc44
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 11 19:03:07 2012 +0300

    fix detecting of bad recipients during SMIME encryption
    
    we have to use FormatStatus method rather than building
    string ourself.
    
    test things

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 71104c2..0dda369 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -183,12 +183,13 @@ sub _SignEncrypt {
             unless ( defined $key_info{'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",
-                ;
+                $res{'status'} .= $self->FormatStatus({
+                    Operation => 'RecipientsCheck',
+                    Status => 'ERROR',
+                    Message => "Recipient '$address' is unusable, the reason is '$reason'",
+                    Recipient => $address,
+                    Reason => $reason,
+                } );
                 next;
             }
 
@@ -203,12 +204,12 @@ sub _SignEncrypt {
             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",
-                ;
+                $res{'status'} .= $self->FormatStatus({
+                    Operation => 'RecipientsCheck', Status => 'ERROR',
+                    Message => "Recipient '$address' is unusable, the reason is '$reason'",
+                    Recipient => $address,
+                    Reason => $reason,
+                });
                 next;
             }
             push @keys, $key_info{'info'}[0]{'Content'};
diff --git a/t/crypt/smime/bad-recipients.t b/t/crypt/smime/bad-recipients.t
new file mode 100644
index 0000000..ddbc7cc
--- /dev/null
+++ b/t/crypt/smime/bad-recipients.t
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test::SMIME tests => 10;
+
+use RT::Tickets;
+
+RT::Test->import_smime_key('sender at example.com');
+my $queue = RT::Test->load_or_create_queue(
+    Name              => 'General',
+    CorrespondAddress => '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";
+}
+
+{
+    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 $root;
+{
+    $root = RT::User->new($RT::SystemUser);
+    ok($root->LoadByEmail('root at localhost'), "Loaded user 'root'");
+    ok($root->Load('root'), "Loaded user 'root'");
+    is($root->EmailAddress, 'root at localhost');
+
+    RT::Test->import_smime_key( 'root at example.com.crt' => $root );
+}
+
+my $bad_user;
+{
+    $bad_user = RT::Test->load_or_create_user(
+        Name => 'bad_user',
+        EmailAddress => 'baduser at example.com',
+    );
+    ok $bad_user && $bad_user->id, 'created a user without key';
+}
+
+RT::Test->clean_caught_mails;
+
+{
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    my ($status, undef, $msg) = $ticket->Create( Queue => $queue->id, Requestor => [$root->id, $bad_user->id] );
+    ok $status, "created a ticket" or "error: $msg";
+
+    my @mails = RT::Test->fetch_caught_mails;
+    is scalar @mails, 3, "autoreply, to bad user, to RT owner";
+}

commit 7cba2e77bba0dba542ea3bdc016cc330ff811243
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 11 19:05:41 2012 +0300

    test how we parse certificate information
    
    openssl changed output at least once during development
    of this branch.

diff --git a/t/crypt/smime/cert-parser.t b/t/crypt/smime/cert-parser.t
new file mode 100644
index 0000000..7720f79
--- /dev/null
+++ b/t/crypt/smime/cert-parser.t
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use RT::Test::SMIME tests => 3;
+
+{ # OpenSSL 0.9.8r 8 Feb 2011
+    my $cert = <<'END';
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number:
+            8a:6a:cd:51:be:94:a0:16
+        Signature Algorithm: sha1WithRSAEncryption
+        Issuer:
+            countryName=AU
+            stateOrProvinceName=Some-State
+            organizationName=Internet Widgits Pty Ltd
+            commonName=CA Owner
+            emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Dec 28 21:46:42 2011 GMT
+            Not After : Aug 18 21:46:42 2036 GMT
+        Subject:
+            countryName=AU
+            stateOrProvinceName=Some-State
+            organizationName=Internet Widgits Pty Ltd
+            commonName=Enoch Root
+            emailAddress=root at example.com
+SHA1 Fingerprint=3C:CC:22:59:BA:65:41:7D:75:CE:99:54:7F:B9:9B:75:0C:8C:74:B0
+END
+    my $expected = {
+        'Certificate' => {
+            'Data' => {
+                'Version' => '3 (0x2)',
+                'Subject' => {
+                               'commonName' => 'Enoch Root',
+                               'emailAddress' => 'root at example.com',
+                               'organizationName' => 'Internet Widgits Pty Ltd',
+                               'stateOrProvinceName' => 'Some-State',
+                               'countryName' => 'AU'
+                             },
+                'Serial Number' => '8a:6a:cd:51:be:94:a0:16',
+                'Issuer' => {
+                              'commonName' => 'CA Owner',
+                              'emailAddress' => 'ca.owner at example.com',
+                              'organizationName' => 'Internet Widgits Pty Ltd',
+                              'stateOrProvinceName' => 'Some-State',
+                              'countryName' => 'AU'
+                            },
+                'Validity' => {
+                                'Not Before' => 'Dec 28 21:46:42 2011 GMT',
+                                'Not After' => 'Aug 18 21:46:42 2036 GMT'
+                              },
+                'Signature Algorithm' => 'sha1WithRSAEncryption',
+            },
+        },
+        'SHA1 Fingerprint' => '3C:CC:22:59:BA:65:41:7D:75:CE:99:54:7F:B9:9B:75:0C:8C:74:B0'
+    };
+
+    my %info = RT::Crypt::SMIME->ParseCertificateInfo( $cert );
+    is_deeply(
+        \%info,
+        $expected,
+    );
+}
+
+{ # OpenSSL 1.0.1 14 Mar 2012
+    my $cert = <<'END';
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 9974010075738841110 (0x8a6acd51be94a016)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer:
+            countryName=AU
+            stateOrProvinceName=Some-State
+            organizationName=Internet Widgits Pty Ltd
+            commonName=CA Owner
+            emailAddress=ca.owner at example.com
+        Validity
+            Not Before: Dec 28 21:46:42 2011 GMT
+            Not After : Aug 18 21:46:42 2036 GMT
+        Subject:
+            countryName=AU
+            stateOrProvinceName=Some-State
+            organizationName=Internet Widgits Pty Ltd
+            commonName=Enoch Root
+            emailAddress=root at example.com
+SHA1 Fingerprint=3C:CC:22:59:BA:65:41:7D:75:CE:99:54:7F:B9:9B:75:0C:8C:74:B0
+END
+    my $expected = {
+        'Certificate' => {
+            'Data' => {
+                'Version' => '3 (0x2)',
+                'Subject' => {
+                               'commonName' => 'Enoch Root',
+                               'emailAddress' => 'root at example.com',
+                               'organizationName' => 'Internet Widgits Pty Ltd',
+                               'stateOrProvinceName' => 'Some-State',
+                               'countryName' => 'AU'
+                             },
+                'Serial Number' => '9974010075738841110 (0x8a6acd51be94a016)',
+                'Issuer' => {
+                              'commonName' => 'CA Owner',
+                              'emailAddress' => 'ca.owner at example.com',
+                              'organizationName' => 'Internet Widgits Pty Ltd',
+                              'stateOrProvinceName' => 'Some-State',
+                              'countryName' => 'AU'
+                            },
+                'Validity' => {
+                                'Not Before' => 'Dec 28 21:46:42 2011 GMT',
+                                'Not After' => 'Aug 18 21:46:42 2036 GMT'
+                              },
+            },
+            'Signature Algorithm' => 'sha1WithRSAEncryption',
+        },
+        'SHA1 Fingerprint' => '3C:CC:22:59:BA:65:41:7D:75:CE:99:54:7F:B9:9B:75:0C:8C:74:B0'
+    };
+
+    my %info = RT::Crypt::SMIME->ParseCertificateInfo( $cert );
+    is_deeply(
+        \%info,
+        $expected,
+    );
+}
+

commit a34e80e0afdfd93a67b7166392e7cfa7d4cbd2fa
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 18 01:27:18 2012 +0400

    adjust wording in KeyIssues to suite GnuPG and SMIME

diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
index f566e8d..d0a868f 100644
--- a/share/html/Elements/Crypt/KeyIssues
+++ b/share/html/Elements/Crypt/KeyIssues
@@ -46,7 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 % if ( @$Issues || @$SignAddresses ) {
-<&| /Widgets/TitleBox, title => loc('GnuPG issues') &>
+<&| /Widgets/TitleBox, title => loc('[_1] issues', RT->Config->Get('Crypt')->{'Outgoing'}) &>
 
 % if ( @$SignAddresses ) {
 <% loc("The system is unable to sign outgoing email messages. This usually indicates that the passphrase was mis-set, or that GPG Agent is down. Please alert your system administrator immediately. The problem addresses are:") %>
@@ -58,9 +58,9 @@
 % }
 
 % if (@$Issues == 1) {
-<% loc("You are going to encrypt outgoing email messages, but there is a problem with a recipient's public key. You have to fix the problem with the key, disable sending a message to that recipient, or disable encryption.") %>
+<% loc("You are going to encrypt outgoing email messages, but there is a problem with a recipient's public key/certificate. You have to fix the problem with the key/certificate, disable sending a message to that recipient, or disable encryption.") %>
 % } elsif (@$Issues > 1) {
-<% loc("You are going to encrypt outgoing email messages, but there are problems with recipients' public keys. You have to fix the problems with the keys, disable sending a message to the recipients with key problems, or disable encryption.") %>
+<% loc("You are going to encrypt outgoing email messages, but there are problems with recipients' public keys/certificates. You have to fix the problems with the keys/certificates, disable sending a message to the recipients with problems, or disable encryption.") %>
 % }
 
 <ul>
@@ -69,11 +69,11 @@
 % if ( $issue->{'User'} ) {
 User <a href="<% RT->Config->Get('WebPath') %>/Admin/Users/Modify.html?id=<% $issue->{'User'}->id %>"><&/Elements/ShowUser, User => $issue->{'User'} &></a> has a problem.
 % } else {
-There is a problem with key(s) for address <% $issue->{'EmailAddress'} %>, but there is no user in the DB for this address.
+There is a problem with key/certificate(s) for address <% $issue->{'EmailAddress'} %>, but there is no user in the DB for this address.
 % }
 <% $issue->{'Message'} %>
 <br />
-Select a key you want to use for encryption:
+Select a key/certificate you want to use for encryption:
 <& /Elements/Crypt/SelectKeyForEncryption,
     Name         => 'UseKey-'. $issue->{'EmailAddress'},
     EmailAddress => $issue->{'EmailAddress'},

commit 3d16888f7dca3bea11f42d0879363550beb52fe7
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 18 01:41:41 2012 +0400

    show GnuPG/SMIME issues box in yellow like results

diff --git a/share/html/Elements/Crypt/KeyIssues b/share/html/Elements/Crypt/KeyIssues
index d0a868f..7ae1b2c 100644
--- a/share/html/Elements/Crypt/KeyIssues
+++ b/share/html/Elements/Crypt/KeyIssues
@@ -46,6 +46,7 @@
 %#
 %# END BPS TAGGED BLOCK }}}
 % if ( @$Issues || @$SignAddresses ) {
+<div class="results">
 <&| /Widgets/TitleBox, title => loc('[_1] issues', RT->Config->Get('Crypt')->{'Outgoing'}) &>
 
 % if ( @$SignAddresses ) {
@@ -83,6 +84,7 @@ Select a key/certificate you want to use for encryption:
 % }
 </ul>
 </&>
+</div>
 % }
 
 <%ARGS>

commit ee92df1192954120d584ccde3e9e87414678b6d2
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 18 02:55:05 2012 +0400

    make it possible to encrypt/sign dashboards
    
    an option in %Crypt config

diff --git a/etc/RT_Config.pm.in b/etc/RT_Config.pm.in
index f0d58f2..ce05049 100755
--- a/etc/RT_Config.pm.in
+++ b/etc/RT_Config.pm.in
@@ -2107,6 +2107,10 @@ with incorrect data.
 If you want to allow people to encrypt attachments inside the DB then
 set C<AllowEncryptDataInDB> to 1.
 
+Set C<Dashboards> to a hash with Encrypt and Sign keys to control
+whether dashboards should be encrypted and/or signed correspondingly.
+By default they are not encrypted or signed.
+
 =back
 
 =cut
@@ -2119,6 +2123,11 @@ Set( %Crypt,
     RejectOnBadData           => 1,
 
     AllowEncryptDataInDB      => 0,
+
+    Dashboards => {
+        Encrypt => 0,
+        Sign    => 0,
+    },
 );
 
 =head2 SMIME configuration
diff --git a/lib/RT/Dashboard/Mailer.pm b/lib/RT/Dashboard/Mailer.pm
index 0ad2fb2..e3b8409 100644
--- a/lib/RT/Dashboard/Mailer.pm
+++ b/lib/RT/Dashboard/Mailer.pm
@@ -351,6 +351,7 @@ sub EmailDashboard {
     $RT::Logger->debug('Mailing dashboard "'.$dashboard->Name.'" to user '.$currentuser->Name." <$email>");
 
     my $ok = RT::Interface::Email::SendEmail(
+        %{ RT->Config->Get('Crypt')->{'Dashboards'} || {} },
         Entity => $entity,
     );
 

commit 1a42a7f8506f06b3401a7bf7630efec79c0e5626
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Wed Jul 18 19:45:51 2012 +0400

    test that notification to RT owner has correct info

diff --git a/t/crypt/smime/bad-recipients.t b/t/crypt/smime/bad-recipients.t
index ddbc7cc..3e61361 100644
--- a/t/crypt/smime/bad-recipients.t
+++ b/t/crypt/smime/bad-recipients.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => 10;
+use RT::Test::SMIME tests => 13;
 
 use RT::Tickets;
 
@@ -63,4 +63,9 @@ RT::Test->clean_caught_mails;
 
     my @mails = RT::Test->fetch_caught_mails;
     is scalar @mails, 3, "autoreply, to bad user, to RT owner";
+
+    like $mails[0], qr{To: baduser\@example\.com}, "notification to bad user";
+    like $mails[1], qr{To: root}, "notification to RT owner";
+    like $mails[1], qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'},
+        "notification to owner has error";
 }

commit c7d33cff611b0effd011c903cfd450dc4e313753
Merge: 1a42a7f 27cd7a8
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Fri Jan 25 17:46:15 2013 +0400

    Merge branch '4.0/gnupg-refactor-backported'
    
    Conflicts:
    	lib/RT/Crypt/GnuPG.pm

diff --cc lib/RT/Crypt/GnuPG.pm
index 03d4473,3c99899..15b7bf8
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@@ -50,9 -50,9 +50,10 @@@ use strict
  use warnings;
  
  package RT::Crypt::GnuPG;
 +use base 'RT::Crypt::Base';
  
  use IO::Handle;
+ use RT::Crypt::GnuPG::CRLFHandle;
  use GnuPG::Interface;
  use RT::EmailParser ();
  use RT::Util 'safe_run_child', 'mime_recommended_filename';
@@@ -358,6 -365,129 +359,129 @@@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i
  #            ...
  #        );
  
+ =head2 CallGnuPG
+ 
+ =cut
+ 
+ sub CallGnuPG {
+     my %args = (
+         Options     => undef,
+         Signer      => undef,
+         Recipients  => [],
+         Passphrase  => undef,
+ 
+         Command     => undef,
+         CommandArgs => [],
+ 
+         Content     => undef,
+         Handles     => {},
+         Direct      => undef,
+         Output      => undef,
+         @_
+     );
+ 
+     my %handle = %{$args{Handles}};
+     my ($handles, $handle_list) = _make_gpg_handles( %handle );
+     $handles->options( $_ )->{'direct'} = 1
+         for @{$args{Direct} || [keys %handle] };
+     %handle = %$handle_list;
+ 
+     my $content = $args{Content};
+     my $command = $args{Command};
+ 
+     my %GnuPGOptions = RT->Config->Get('GnuPGOptions');
+     my %opt = (
+         'digest-algo' => 'SHA1',
+         %GnuPGOptions,
+         %{ $args{Options} || {} },
+     );
+     my $gnupg = GnuPG::Interface->new;
+     $gnupg->options->hash_init(
+         _PrepareGnuPGOptions( %opt ),
+     );
+     $gnupg->options->armor( 1 );
+     $gnupg->options->meta_interactive( 0 );
+     $gnupg->options->default_key( $args{Signer} )
+         if defined $args{Signer};
+ 
+     my %seen;
+     $gnupg->options->push_recipients( $_ ) for
 -        map { UseKeyForEncryption($_) || $_ }
++        map { RT::Crypt->UseKeyForEncryption($_) || $_ }
+         grep { !$seen{ $_ }++ }
+             @{ $args{Recipients} || [] };
+ 
+     $args{Passphrase} = $GnuPGOptions{passphrase}
+         unless defined $args{'Passphrase'};
 -    $args{Passphrase} = GetPassphrase( Address => $args{Signer} )
++    $args{Passphrase} = $self->( Address => $args{Signer} )
+         unless defined $args{'Passphrase'};
+     $gnupg->passphrase( $args{'Passphrase'} )
+         if defined $args{Passphrase};
+ 
+     eval {
+         local $SIG{'CHLD'} = 'DEFAULT';
+         my $pid = safe_run_child {
+             if ($command =~ /^--/) {
+                 $gnupg->wrap_call(
+                     handles      => $handles,
+                     commands     => [$command],
+                     command_args => $args{CommandArgs},
+                 );
+             } else {
+                 $gnupg->$command(
+                     handles      => $handles,
+                     command_args => $args{CommandArgs},
+                 );
+             }
+         };
+         {
+             local $SIG{'PIPE'} = 'IGNORE';
+             if (Scalar::Util::blessed($content) and $content->can("print")) {
+                 $content->print( $handle{'stdin'} );
+             } elsif (ref($content) eq "SCALAR") {
+                 $handle{'stdin'}->print( ${ $content } );
+             } elsif (defined $content) {
+                 $handle{'stdin'}->print( $content );
+             }
+             close $handle{'stdin'} or die "Can't close gnupg input handle: $!";
+             $args{Callback}->(%handle) if $args{Callback};
+         }
+         waitpid $pid, 0;
+     };
+     my $err = $@;
+     if ($args{Output}) {
+         push @{$args{Output}}, readline $handle{stdout};
+         if (not close $handle{stdout}) {
+             $err ||= "Can't close gnupg output handle: $!";
+         }
+     }
+ 
+     return Finalize( $err, \%handle );
+ }
+ 
+ sub Finalize {
+     my ($err, $handle) = @_;
+     my %handle = %{ $handle };
+ 
+     my %res;
+     $res{'exit_code'} = $?;
+ 
+     foreach ( qw(stderr logger status) ) {
+         $res{$_} = do { local $/ = undef; readline $handle{$_} };
+         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
+         if (not close $handle{$_}) {
+             $err ||= "Can't close gnupg $_ handle: $!";
+         }
+     }
+     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
+     $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
+     $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
+     if ( $err || $res{'exit_code'} ) {
+         $res{'message'} = $err? $err : "gpg exited with error code ". ($res{'exit_code'} >> 8);
+     }
+ 
+     return %res;
+ }
+ 
  =head2 SignEncrypt Entity => MIME::Entity, [ Encrypt => 1, Sign => 1, ... ]
  
  Signs and/or encrypts an email message with GnuPG utility.
@@@ -1168,26 -1039,15 +1085,18 @@@ sub VerifyDecrypt 
              );
              $status_on->head->modify($modify);
          }
 +    } else {
 +        die "Unknow type '". $item->{'Type'} ."' of protected item";
      }
 -    return @res;
 +    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();
-     my %opt = RT->Config->Get('GnuPGOptions');
-     $opt{'digest-algo'} ||= 'SHA1';
-     $gnupg->options->hash_init(
-         _PrepareGnuPGOptions( %opt ),
-         meta_interactive => 0,
-     );
- 
      foreach ( $args{'Data'}, $args{'Signature'} ) {
          next unless $_->bodyhandle->is_encoded;
  
@@@ -1232,17 -1069,8 +1118,9 @@@
  }
  
  sub VerifyRFC3156 {
 +    my $self = shift;
      my %args = ( Data => undef, Signature => undef, Top => undef, @_ );
  
-     my $gnupg = GnuPG::Interface->new();
-     my %opt = RT->Config->Get('GnuPGOptions');
-     $opt{'digest-algo'} ||= 'SHA1';
-     $gnupg->options->hash_init(
-         _PrepareGnuPGOptions( %opt ),
-         meta_interactive => 0,
-     );
- 
      my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
      binmode $tmp_fh, ':raw:eol(CRLF?)';
      $args{'Data'}->print( $tmp_fh );
@@@ -1412,9 -1160,8 +1212,8 @@@ 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,
              );
              return %res unless $res_fh;
@@@ -1470,9 -1216,7 +1268,8 @@@
  }
  
  sub _DecryptInlineBlock {
 +    my $self = shift;
      my %args = (
-         GnuPG => undef,
          BlockHandle => undef,
          Passphrase => undef,
          @_
@@@ -1555,9 -1260,8 +1314,8 @@@ 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,
      );
      return %res unless $res_fh;
@@@ -2059,65 -1832,29 +1777,35 @@@ sub GetKeysForSigning 
  }
  
  sub GetKeysInfo {
 -    my $email = shift;
 -    my $type = shift || 'public';
 -    my $force = shift;
 +    my $self = 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();
-     my %opt = RT->Config->Get('GnuPGOptions');
-     $opt{'digest-algo'} ||= 'SHA1';
-     $opt{'with-colons'} = undef; # parseable format
-     $opt{'fingerprint'} = undef; # show fingerprint
-     $opt{'fixed-list-mode'} = undef; # don't merge uid with keys
-     $gnupg->options->hash_init(
-         _PrepareGnuPGOptions( %opt ),
-         armor => 1,
-         meta_interactive => 0,
+     my @info;
+     my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
+     my %res = CallGnuPG(
+         Options     => {
+             'with-colons'     => undef, # parseable format
+             'fingerprint'     => undef, # show fingerprint
+             'fixed-list-mode' => undef, # don't merge uid with keys
+         },
+         Command     => $method,
+         ( $email ? (CommandArgs => ['--', $email]) : () ),
+         Output      => \@info,
      );
- 
-     my %res;
- 
-     my ($handles, $handle_list) = _make_gpg_handles();
-     my %handle = %$handle_list;
- 
-     eval {
-         local $SIG{'CHLD'} = 'DEFAULT';
-         my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
-         my $pid = safe_run_child { $gnupg->$method( handles => $handles, $email
-                                                         ? (command_args => [ "--", $email])
-                                                         : () ) };
-         close $handle{'stdin'};
-         waitpid $pid, 0;
-     };
- 
-     my @info = readline $handle{'stdout'};
-     close $handle{'stdout'};
- 
-     $res{'exit_code'} = $?;
-     foreach ( qw(stderr logger status) ) {
-         $res{$_} = do { local $/; readline $handle{$_} };
-         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-         close $handle{$_};
-     }
-     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-     $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-     $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-     if ( $@ || $? ) {
-         $res{'message'} = $@? $@: "gpg exitted with error code ". ($? >> 8);
-         return %res;
-     }
+     return %res if $res{'message'};
  
 -    @info = ParseKeysInfo( @info );
 +    @info = $self->ParseKeysInfo( @info );
      $res{'info'} = \@info;
      return %res;
  }
@@@ -2256,101 -1992,52 +1944,38 @@@ 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;
  
-     my $gnupg = GnuPG::Interface->new();
-     my %opt = RT->Config->Get('GnuPGOptions');
-     $gnupg->options->hash_init(
-         _PrepareGnuPGOptions( %opt ),
-         meta_interactive => 0,
-     );
- 
-     my ($handles, $handle_list) = _make_gpg_handles();
-     my %handle = %$handle_list;
- 
-     eval {
-         local $SIG{'CHLD'} = 'DEFAULT';
-         my $pid = safe_run_child { $gnupg->wrap_call(
-             handles => $handles,
-             commands => ['--delete-secret-and-public-key'],
-             command_args => ["--", $key],
-         ) };
-         close $handle{'stdin'};
-         while ( my $str = readline $handle{'status'} ) {
-             if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
-                 print { $handle{'command'} } "y\n";
+     return CallGnuPG(
+         Command     => "--delete-secret-and-public-key",
+         CommandArgs => [\--', $key],
+         Callback    => sub {
+             my %handle = @_;
+             while ( my $str = readline $handle{'status'} ) {
+                 if ( $str =~ /^\[GNUPG:\]\s*GET_BOOL delete_key\..*/ ) {
+                     print { $handle{'command'} } "y\n";
+                 }
              }
-         }
-         waitpid $pid, 0;
-     };
-     my $err = $@;
-     close $handle{'stdout'};
- 
-     my %res;
-     $res{'exit_code'} = $?;
-     foreach ( qw(stderr logger status) ) {
-         $res{$_} = do { local $/; readline $handle{$_} };
-         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-         close $handle{$_};
-     }
-     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-     $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-     $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-     if ( $err || $res{'exit_code'} ) {
-         $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-     }
-     return %res;
+         },
+     );
  }
  
  sub ImportKey {
 +    my $self = shift;
      my $key = shift;
  
-     my $gnupg = GnuPG::Interface->new();
-     my %opt = RT->Config->Get('GnuPGOptions');
-     $gnupg->options->hash_init(
-         _PrepareGnuPGOptions( %opt ),
-         meta_interactive => 0,
+     return CallGnuPG(
+         Command     => "import_keys",
+         Content     => $key,
      );
- 
-     my ($handles, $handle_list) = _make_gpg_handles();
-     my %handle = %$handle_list;
- 
-     eval {
-         local $SIG{'CHLD'} = 'DEFAULT';
-         my $pid = safe_run_child { $gnupg->wrap_call(
-             handles => $handles,
-             commands => ['--import'],
-         ) };
-         print { $handle{'stdin'} } $key;
-         close $handle{'stdin'};
-         waitpid $pid, 0;
-     };
-     my $err = $@;
-     close $handle{'stdout'};
- 
-     my %res;
-     $res{'exit_code'} = $?;
-     foreach ( qw(stderr logger status) ) {
-         $res{$_} = do { local $/; readline $handle{$_} };
-         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-         close $handle{$_};
-     }
-     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-     $RT::Logger->warning( $res{'stderr'} ) if $res{'stderr'};
-     $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-     if ( $err || $res{'exit_code'} ) {
-         $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-     }
-     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.
@@@ -2366,14 -2077,11 +1991,12 @@@ properly (and false otherwise)
  
  
  sub Probe {
 +    my $self = shift;
-     my $gnupg = GnuPG::Interface->new();
-     my %opt = RT->Config->Get('GnuPGOptions');
+     my $gnupg = GnuPG::Interface->new;
      $gnupg->options->hash_init(
-         _PrepareGnuPGOptions( %opt ),
-         armor => 1,
-         meta_interactive => 0,
+         _PrepareGnuPGOptions( RT->Config->Get('GnuPGOptions') )
      );
+     $gnupg->options->meta_interactive( 0 );
  
      my ($handles, $handle_list) = _make_gpg_handles();
      my %handle = %$handle_list;
diff --cc lib/RT/Test.pm
index 0bd0f2c,21283ce..2415901
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@@ -1312,101 -1233,30 +1275,63 @@@ sub trust_gnupg_key 
      my $self = shift;
      my $key = shift;
  
-     require RT::Crypt::GnuPG; require GnuPG::Interface;
-     my $gnupg = GnuPG::Interface->new();
-     my %opt = RT->Config->Get('GnuPGOptions');
-     $gnupg->options->hash_init(
-         RT::Crypt::GnuPG::_PrepareGnuPGOptions( %opt ),
-         meta_interactive => 0,
-     );
- 
-     my %handle; 
-     my $handles = GnuPG::Handles->new(
-         stdin   => ($handle{'input'}   = IO::Handle->new()),
-         stdout  => ($handle{'output'}  = IO::Handle->new()),
-         stderr  => ($handle{'error'}   = IO::Handle->new()),
-         logger  => ($handle{'logger'}  = IO::Handle->new()),
-         status  => ($handle{'status'}  = IO::Handle->new()),
-         command => ($handle{'command'} = IO::Handle->new()),
-     );
+     require RT::Crypt::GnuPG;
  
-     eval {
-         local $SIG{'CHLD'} = 'DEFAULT';
-         local @ENV{'LANG', 'LC_ALL'} = ('C', 'C');
-         my $pid = $gnupg->wrap_call(
-             handles => $handles,
-             commands => ['--edit-key'],
-             command_args => [$key],
-         );
-         close $handle{'input'};
- 
-         my $done = 0;
-         while ( my $str = readline $handle{'status'} ) {
-             if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) {
-                 if ( $done ) {
-                     print { $handle{'command'} } "quit\n";
-                 } else {
-                     print { $handle{'command'} } "trust\n";
+     return RT::Crypt::GnuPG::CallGnuPG(
+         Command     => '--edit-key',
+         CommandArgs => [$key],
+         Callback    => sub {
+             my %handle = @_;
+             my $done = 0;
+             while ( my $str = readline $handle{'status'} ) {
+                 if ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE keyedit.prompt/ ) {
+                     if ( $done ) {
+                         print { $handle{'command'} } "quit\n";
+                     } else {
+                         print { $handle{'command'} } "trust\n";
+                     }
+                 } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) {
+                     print { $handle{'command'} } "5\n";
+                 } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) {
+                     print { $handle{'command'} } "y\n";
+                     $done = 1;
                  }
-             } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_LINE edit_ownertrust.value/ ) {
-                 print { $handle{'command'} } "5\n";
-             } elsif ( $str =~ /^\[GNUPG:\]\s*\QGET_BOOL edit_ownertrust.set_ultimate.okay/ ) {
-                 print { $handle{'command'} } "y\n";
-                 $done = 1;
              }
-         }
-         waitpid $pid, 0;
-     };
-     my $err = $@;
-     close $handle{'output'};
- 
-     my %res;
-     $res{'exit_code'} = $?;
-     foreach ( qw(error logger status) ) {
-         $res{$_} = do { local $/; readline $handle{$_} };
-         delete $res{$_} unless $res{$_} && $res{$_} =~ /\S/s;
-         close $handle{$_};
-     }
-     $RT::Logger->debug( $res{'status'} ) if $res{'status'};
-     $RT::Logger->warning( $res{'error'} ) if $res{'error'};
-     $RT::Logger->error( $res{'logger'} ) if $res{'logger'} && $?;
-     if ( $err || $res{'exit_code'} ) {
-         $res{'message'} = $err? $err : "gpg exitted with error code ". ($res{'exit_code'} >> 8);
-     }
-     return %res;
+         },
+     );
 +}
 +
 +sub import_smime_key {
 +    my $self = shift;
 +    my $key  = shift;
 +    my $user = shift;
 +
 +    my $path = find_relocatable_path( 'data', 'smime', 'keys' );
 +    die "can't find the dir where smime keys are stored"
 +        unless $path;
 +
 +    $key .= ".pem" unless $key =~ /\.(pem|crt|key)$/;
 +
 +    my $content = RT::Test->file_content( [ $path, $key ] );
 +
 +    if ( $user ) {
 +        my ($status, $msg) = $user->AddCustomFieldValue(
 +            Field => 'SMIME Key',
 +            Value => $content,
 +        );
 +        die "Couldn't set CF: $msg" unless $status;
 +    } else {
 +        my $keyring = RT->Config->Get('SMIME')->{'Keyring'};
 +        die "SMIME keyring '$keyring' doesn't exist"
 +            unless $keyring && -e $keyring;
 +
 +        open my $fh, '>:raw', File::Spec->catfile($keyring, $key)
 +            or die "can't open file: $!";
 +        print $fh $content;
 +        close $fh;
 +    }
 +
 +    return;
  }
  
  sub started_ok {

commit 933b8917d63f0d9a3efcc4bcf64b39720b128024
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Fri Sep 28 13:24:23 2012 -0400

    Adapt merged 4.2/gnupg-refactor for changes on the branch
    
    UseKeyForEncryption was elevated into RT::Crypt and all of the
    subroutines became methods, so we need $self in CallGnuPG

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 15b7bf8..5ef20a1 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -364,6 +364,7 @@ our $RE_FILE_EXTENSIONS = qr/pgp|asc/i;
 =cut
 
 sub CallGnuPG {
+    my $self = shift;
     my %args = (
         Options     => undef,
         Signer      => undef,
@@ -412,7 +413,7 @@ sub CallGnuPG {
 
     $args{Passphrase} = $GnuPGOptions{passphrase}
         unless defined $args{'Passphrase'};
-    $args{Passphrase} = $self->( Address => $args{Signer} )
+    $args{Passphrase} = $self->GetPassphrase( Address => $args{Signer} )
         unless defined $args{'Passphrase'};
     $gnupg->passphrase( $args{'Passphrase'} )
         if defined $args{Passphrase};
@@ -557,7 +558,7 @@ sub SignEncryptRFC3156 {
         my @signature;
         # We use RT::Crypt::GnuPG::CRLFHandle to canonicalize the
         # MIME::Entity output to use \r\n instead of \n for its newlines
-        %res = CallGnuPG(
+        %res = $self->CallGnuPG(
             Signer     => $args{'Signer'},
             Command    => "detach_sign",
             Handles    => { stdin => RT::Crypt::GnuPG::CRLFHandle->new },
@@ -590,7 +591,7 @@ sub SignEncryptRFC3156 {
         binmode $tmp_fh, ':raw';
 
         $entity->make_multipart( 'mixed', Force => 1 );
-        %res = CallGnuPG(
+        %res = $self->CallGnuPG(
             Signer     => $args{'Signer'},
             Recipients => \@recipients,
             Command    => ( $args{'Sign'} ? "sign_and_encrypt" : "encrypt" ),
@@ -664,7 +665,7 @@ sub _SignEncryptTextInline {
     binmode $tmp_fh, ':raw';
 
     my $entity = $args{'Entity'};
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Command    => ( $args{'Sign'} && $args{'Encrypt'}
@@ -706,7 +707,7 @@ sub _SignEncryptAttachmentInline {
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Command    => ( $args{'Sign'} && $args{'Encrypt'}
@@ -760,7 +761,7 @@ sub SignEncryptContent {
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Signer     => $args{'Signer'},
         Recipients => $args{'Recipients'},
         Command    => ( $args{'Sign'} && $args{'Encrypt'}
@@ -1109,7 +1110,7 @@ sub VerifyAttachment {
     $args{'Data'}->bodyhandle->print( $tmp_fh );
     $tmp_fh->flush;
 
-    return CallGnuPG(
+    return $self->CallGnuPG(
         Command     => "verify",
         CommandArgs => [ '-', $tmp_fn ],
         Passphrase  => $args{'Passphrase'},
@@ -1126,7 +1127,7 @@ sub VerifyRFC3156 {
     $args{'Data'}->print( $tmp_fh );
     $tmp_fh->flush;
 
-    return CallGnuPG(
+    return $self->CallGnuPG(
         Command     => "verify",
         CommandArgs => [ '-', $tmp_fn ],
         Passphrase  => $args{'Passphrase'},
@@ -1152,7 +1153,7 @@ sub DecryptRFC3156 {
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Command     => "decrypt",
         Handles     => { stdout => $tmp_fh },
         Passphrase  => $args{'Passphrase'},
@@ -1278,7 +1279,7 @@ sub _DecryptInlineBlock {
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Command     => "decrypt",
         Handles     => { stdout => $tmp_fh, stdin => $args{'BlockHandle'} },
         Passphrase  => $args{'Passphrase'},
@@ -1349,7 +1350,7 @@ sub DecryptContent {
     my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( UNLINK => 1 );
     binmode $tmp_fh, ':raw';
 
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Command     => "decrypt",
         Handles     => { stdout => $tmp_fh },
         Passphrase  => $args{'Passphrase'},
@@ -1793,7 +1794,7 @@ sub GetKeysInfo {
 
     my @info;
     my $method = $type eq 'private'? 'list_secret_keys': 'list_public_keys';
-    my %res = CallGnuPG(
+    my %res = $self->CallGnuPG(
         Options     => {
             'with-colons'     => undef, # parseable format
             'fingerprint'     => undef, # show fingerprint
@@ -1948,7 +1949,7 @@ sub DeleteKey {
     my $self = shift;
     my $key = shift;
 
-    return CallGnuPG(
+    return $self->CallGnuPG(
         Command     => "--delete-secret-and-public-key",
         CommandArgs => [\--', $key],
         Callback    => sub {
@@ -1966,7 +1967,7 @@ sub ImportKey {
     my $self = shift;
     my $key = shift;
 
-    return CallGnuPG(
+    return $self->CallGnuPG(
         Command     => "import_keys",
         Content     => $key,
     );
diff --git a/lib/RT/Test.pm b/lib/RT/Test.pm
index 2415901..41cdf01 100644
--- a/lib/RT/Test.pm
+++ b/lib/RT/Test.pm
@@ -1257,7 +1257,7 @@ sub lsign_gnupg_key {
 
     require RT::Crypt::GnuPG;
 
-    return RT::Crypt::GnuPG::CallGnuPG(
+    return RT::Crypt::GnuPG->CallGnuPG(
         Command     => '--lsign-key',
         CommandArgs => [$key],
         Callback    => sub {
@@ -1277,7 +1277,7 @@ sub trust_gnupg_key {
 
     require RT::Crypt::GnuPG;
 
-    return RT::Crypt::GnuPG::CallGnuPG(
+    return RT::Crypt::GnuPG->CallGnuPG(
         Command     => '--edit-key',
         CommandArgs => [$key],
         Callback    => sub {

commit 209380996eae0fc7727c8a2be061f6c55e37b0a3
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Aug 27 19:59:54 2012 -0400

    Fix a typo, preventing emails from setting internal encryption header
    
    X-RT-Incoming-Encryption, along with a number of other headers, is
    removed from all parts of incoming emails because they are used to
    annotate GPG-related properties of the message.  Fix a typo, which
    allowed the properly-spelled header to pass through under specific
    circumstances.

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 3c9c957..13b6b61 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -114,7 +114,7 @@ sub GetCurrentUser {
     # we clean all possible headers
     my @headers =
         qw(
-            X-RT-Incoming-Encrypton
+            X-RT-Incoming-Encryption
             X-RT-Incoming-Signature
             X-RT-Privacy
         ),

commit afa6ff88f9bf43eff510b6813b7471a5e328c847
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Mon Aug 27 21:24:25 2012 -0400

    Remove internal signing and encryption hints from incoming mail
    
    RT uses the existance of the X-RT-Sign and X-RT-Encrypt headers to
    decide if the outgoing mails it sends as a result of the transaction
    should be signed and/or encrypted.  It standardly inserts these headers
    based on checkboxes visable in the web UI; however, it fails to remove
    them from incoming mails, allowing external users to create nearly
    arbitrary messages signed by the queue's key.

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 13b6b61..625abeb 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -117,6 +117,8 @@ sub GetCurrentUser {
             X-RT-Incoming-Encryption
             X-RT-Incoming-Signature
             X-RT-Privacy
+            X-RT-Sign
+            X-RT-Encrypt
         ),
         map "X-RT-$_-Status", RT::Crypt->Protocols;
     foreach my $p ( $args{'Message'}->parts_DFS ) {

commit 8d70297f42335a0bcc9a250ebcada96168f86fe5
Author: Alex Vandiver <alexmv at bestpractical.com>
Date:   Fri Aug 31 21:12:02 2012 -0400

    Refactor shared code controlling if a message will be encrypted or signed
    (cherry picked from commit 8f242072bb196e155ec9a5fbbf06bc8a2d3c2813)
    
    Conflicts:
    
    	lib/RT/Interface/Email.pm

diff --git a/lib/RT/Interface/Email.pm b/lib/RT/Interface/Email.pm
index 1fd8066..d27886d 100644
--- a/lib/RT/Interface/Email.pm
+++ b/lib/RT/Interface/Email.pm
@@ -326,7 +326,7 @@ sub WillSignEncrypt {
     my $attachment = delete $args{Attachment};
     my $ticket     = delete $args{Ticket};
 
-    if ( not RT->Config->Get('GnuPG')->{'Enable'} ) {
+    if ( not RT->Config->Get('Crypt')->{'Enable'} ) {
         $args{Sign} = $args{Encrypt} = 0;
         return wantarray ? %args : 0;
     }

commit e91abe1dc0d66856c294b13baa084e23129d01a1
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Sat Sep 29 00:04:29 2012 -0400

    The "Always Sign, even redistributed email" option is now called SignAuto

diff --git a/t/crypt/no-signer-address.t b/t/crypt/no-signer-address.t
index 57c2591..16f0ebe 100644
--- a/t/crypt/no-signer-address.t
+++ b/t/crypt/no-signer-address.t
@@ -13,7 +13,7 @@ my $queue;
 {
     $queue = RT::Test->load_or_create_queue(
         Name => 'Regression',
-        Sign => 1,
+        SignAuto => 1,
     );
     ok $queue && $queue->id, 'loaded or created queue';
     ok !$queue->CorrespondAddress, 'address not set';

commit 4bde19295393c982736181bdcbd7f1ef4d9e7b59
Author: Kevin Falcone <falcone at bestpractical.com>
Date:   Tue Oct 2 19:49:50 2012 -0400

    Stop looking for things in the GnuPG configuration
    
    This restores the behavior that if mail comes in which is "invalid" in
    some way that we want to reject, we'll stop gpg/smime processing the
    mail after notifying external users of the failure (basically skip the
    parts that update X-RT-Incoming-(Signature|Encryption) to record
    success).  I'm not sure if that's actually the optimal behavior, but
    it's the back-compat behavior.

diff --git a/lib/RT/Interface/Email/Auth/Crypt.pm b/lib/RT/Interface/Email/Auth/Crypt.pm
index 625abeb..af490f0 100644
--- a/lib/RT/Interface/Email/Auth/Crypt.pm
+++ b/lib/RT/Interface/Email/Auth/Crypt.pm
@@ -216,13 +216,13 @@ sub HandleErrors {
         unless ( $sent_once{'NoPrivateKey'} ) {
             unless ( CheckNoPrivateKey( Message => $args{'Message'}, Status => \@status ) ) {
                 $sent_once{'NoPrivateKey'}++;
-                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnMissingPrivateKey'};
+                $reject = 1 if RT->Config->Get('Crypt')->{'RejectOnMissingPrivateKey'};
             }
         }
         unless ( $sent_once{'BadData'} ) {
             unless ( CheckBadData( Message => $args{'Message'}, Status => \@status ) ) {
                 $sent_once{'BadData'}++;
-                $reject = 1 if RT->Config->Get('GnuPG')->{'RejectOnBadData'};
+                $reject = 1 if RT->Config->Get('Crypt')->{'RejectOnBadData'};
             }
         }
     }

commit 28c6b6c826db2ad9222e005b236dffc79d8b03ad
Author: Thomas Sibley <trs at bestpractical.com>
Date:   Thu Oct 4 21:59:42 2012 -0700

    Let MIME::Head modify the crypt status headers to handle continuations
    
    X-RT-GnuPG-Status and X-RT-SMIME-Status are set to a many-line string
    and previously continuations weren't handled properly.
    
    Temporarily letting the inserted headers be modified fixes the known
    problem.  If instead we marked the entire entity as modifiable early
    after parsing and left it that way, the possibility of unforeseen
    breakage would be too high for a stable release series.  It would also
    lead to even further deviation of RT's stored message from the original
    source message.
    
    Forward ported from commit e6c6ae922252e36b510264472820ea8df72e4b7e.

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index 0dda369..a0d8774 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -293,17 +293,25 @@ sub VerifyDecrypt {
         }
         if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
             my $method = $args{'AddStatus'} ? 'add' : 'set';
+            # Let the header be modified so continuations are handled
+            my $modify = $status_on->head->modify;
+            $status_on->head->modify(1);
             $status_on->head->$method(
                 'X-RT-SMIME-Status' => $res{'status'}
             );
+            $status_on->head->modify($modify);
         }
     } elsif ( $item->{'Type'} eq 'encrypted' ) {
         %res = $self->DecryptRFC3851( %args, %$item );
         if ( $args{'SetStatus'} || $args{'AddStatus'} ) {
             my $method = $args{'AddStatus'} ? 'add' : 'set';
+            # Let the header be modified so continuations are handled
+            my $modify = $item->{'Data'}->head->modify;
+            $item->{'Data'}->head->modify(1);
             $item->{'Data'}->head->$method(
                 'X-RT-SMIME-Status' => $res{'status'}
             );
+            $item->{'Data'}->head->modify($modify);
         }
     } else {
         die "Unknow type '". $item->{'Type'} ."' of protected item";

commit 350ebb3b37df05f2e73a4dcb5cb8997b4127117e
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Dec 29 18:29:29 2012 +0400

    make SMIME more verbose on how it looks for keys
    
    Now, if key is not there then log doesn't indicate problem,
    it only says that decryption failed.

diff --git a/lib/RT/Crypt/SMIME.pm b/lib/RT/Crypt/SMIME.pm
index a0d8774..889f725 100644
--- a/lib/RT/Crypt/SMIME.pm
+++ b/lib/RT/Crypt/SMIME.pm
@@ -460,7 +460,10 @@ sub _Decrypt {
     my $found_key = 0;
     foreach my $address ( @addresses ) {
         my $key_file = File::Spec->catfile( $keyring, $address .'.pem' );
-        next unless -e $key_file && -r _;
+        unless ( -e $key_file && -r _ ) {
+            $RT::Logger->debug("No '$key_file' or it's unreadable");
+            next;
+        }
 
         $found_key = 1;
 
@@ -476,10 +479,14 @@ sub _Decrypt {
         safe_run_child { run3( $cmd, $args{'Content'}, \$buf, \$res{'stderr'} ) };
         unless ( $? ) {
             $encrypted_to = $address;
+            $RT::Logger->debug("Message encrypted for $encrypted_to");
             last;
         }
 
-        next if index($res{'stderr'}, 'no recipient matches key') >= 0;
+        if ( index($res{'stderr'}, 'no recipient matches key') >= 0 ) {
+            $RT::Logger->debug("Message was sent to $address and we have key, but it's not encrypted for this address");
+            next;
+        }
 
         $res{'exit_code'} = $?;
         $res{'message'} = "openssl exitted with error code ". ($? >> 8)
@@ -493,6 +500,7 @@ sub _Decrypt {
         return (undef, %res);
     }
     unless ( $found_key ) {
+        $RT::Logger->error("Couldn't find SMIME key for addresses: ". join ', ', @addresses);
         $res{'exit_code'} = 1;
         $res{'status'} = $self->FormatStatus({
             Operation => 'KeyCheck',

commit c48ab942c6f98e2a75e4584dbd2a62a7a598018a
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 00:56:06 2013 +0400

    test fixup, handle warning in t/crypt/no-signer-address.t

diff --git a/t/crypt/no-signer-address.t b/t/crypt/no-signer-address.t
index 16f0ebe..8f4a0b1 100644
--- a/t/crypt/no-signer-address.t
+++ b/t/crypt/no-signer-address.t
@@ -2,7 +2,7 @@ use strict;
 use warnings;
 
 use RT::Test::GnuPG
-  tests         => 5,
+  tests         => 6,
   gnupg_options => {
     passphrase    => 'rt-test',
     'trust-model' => 'always',
@@ -19,7 +19,8 @@ my $queue;
     ok !$queue->CorrespondAddress, 'address not set';
 }
 
-{
+use Test::Warn;
+warnings_like {
     my $ticket = RT::Ticket->new( RT->SystemUser );
     my ($status, undef, $msg) = $ticket->Create(
         Queue => $queue->id,
@@ -31,5 +32,5 @@ my $queue;
     my $log = RT::Test->file_content([RT::Test->temp_directory, 'rt.debug.log']);
     like $log, qr{secret key not available}, 'error in the log';
     unlike $log, qr{Scrip .*? died}m, "scrip didn't die";
-}
+} [qr{gpg: keyring .*? created}];
 

commit 8f4f6823b89c6a035be4828c95cb192c1b247b34
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 01:03:58 2013 +0400

    test fixup, handle expected warnings

diff --git a/t/crypt/smime/bad-recipients.t b/t/crypt/smime/bad-recipients.t
index 3e61361..74bce6f 100644
--- a/t/crypt/smime/bad-recipients.t
+++ b/t/crypt/smime/bad-recipients.t
@@ -2,7 +2,7 @@
 use strict;
 use warnings;
 
-use RT::Test::SMIME tests => 13;
+use RT::Test::SMIME tests => 14;
 
 use RT::Tickets;
 
@@ -56,10 +56,12 @@ my $bad_user;
 
 RT::Test->clean_caught_mails;
 
-{
+use Test::Warn;
+
+warnings_like {
     my $ticket = RT::Ticket->new(RT->SystemUser);
     my ($status, undef, $msg) = $ticket->Create( Queue => $queue->id, Requestor => [$root->id, $bad_user->id] );
-    ok $status, "created a ticket" or "error: $msg";
+    ok $status, "created a ticket" or diag "error: $msg";
 
     my @mails = RT::Test->fetch_caught_mails;
     is scalar @mails, 3, "autoreply, to bad user, to RT owner";
@@ -68,4 +70,4 @@ RT::Test->clean_caught_mails;
     like $mails[1], qr{To: root}, "notification to RT owner";
     like $mails[1], qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'},
         "notification to owner has error";
-}
+} [qr{Recipient 'baduser\@example\.com' is unusable, the reason is 'Key not found'}];

commit 29283c03682c55ea5dca42f50433e06d997c69d9
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 01:10:13 2013 +0400

    test fixup, handle expected warning

diff --git a/t/mail/crypt-gnupg.t b/t/mail/crypt-gnupg.t
index c353082..2badd01 100644
--- a/t/mail/crypt-gnupg.t
+++ b/t/mail/crypt-gnupg.t
@@ -10,7 +10,7 @@ BEGIN {
         qw/data gnupg keyrings/ );
 }
 
-use RT::Test::GnuPG tests => 99, gnupg_options => { homedir => $homedir };
+use RT::Test::GnuPG tests => 100, gnupg_options => { homedir => $homedir };
 use Test::Warn;
 
 use_ok('RT::Crypt');
@@ -259,7 +259,10 @@ diag 'wrong signed/encrypted parts: no protocol';
     ok( !$res{'exit_code'}, 'success' );
     $entity->head->mime_attr( 'Content-Type.protocol' => undef );
 
-    my @parts = RT::Crypt->FindProtectedParts( Entity => $entity );
+    use Test::Warn;
+    my @parts;
+    warning_like { @parts = RT::Crypt->FindProtectedParts( Entity => $entity ) }
+        qr{Entity is 'multipart/encrypted', but has no protocol defined. Checking for PGP part};
     is( scalar @parts, 1, 'one protected part' );
     is( $parts[0]->{'Type'}, 'encrypted', "have encrypted part" );
     is( $parts[0]->{'Format'}, 'RFC3156', "RFC3156 format" );

commit c9c923a3a4616b6ce4a34b3bdc3d4c4b5acbeade
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 01:13:21 2013 +0400

    fixup, we changed privacy from PGP to GnuPG

diff --git a/t/mail/gnupg-incoming.t b/t/mail/gnupg-incoming.t
index fe4d036..bd3f189 100644
--- a/t/mail/gnupg-incoming.t
+++ b/t/mail/gnupg-incoming.t
@@ -228,7 +228,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/);

commit 0c344fb11475b910e80f62502e32364a51997a8c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 01:27:31 2013 +0400

    test expects SignAuto

diff --git a/t/web/crypt-gnupg.t b/t/web/crypt-gnupg.t
index 202781c..904a639 100644
--- a/t/web/crypt-gnupg.t
+++ b/t/web/crypt-gnupg.t
@@ -40,6 +40,7 @@ my $queue = RT::Test->load_or_create_queue(
     CorrespondAddress => 'general at example.com',
 );
 ok $queue && $queue->id, 'loaded or created queue';
+$queue->SetSignAuto(1);
 my $qid = $queue->id;
 
 my ($baseurl, $m) = RT::Test->started_ok;
@@ -351,12 +352,12 @@ $nokey->PrincipalObj->GrantRight(Right => 'CreateTicket');
 $nokey->PrincipalObj->GrantRight(Right => 'OwnTicket');
 
 my $tick = RT::Ticket->new( RT->SystemUser );
-$tick->Create(Subject => 'owner lacks pubkey', Queue => 'general',
+$tick->Create(Subject => 'owner lacks pubkey', Queue => $qid,
               Owner => $nokey);
 ok(my $id = $tick->id, 'created ticket for owner-without-pubkey');
 
 $tick = RT::Ticket->new( RT->SystemUser );
-$tick->Create(Subject => 'owner has pubkey', Queue => 'general',
+$tick->Create(Subject => 'owner has pubkey', Queue => $qid,
               Owner => 'root');
 ok($id = $tick->id, 'created ticket for owner-with-pubkey');
 
@@ -422,7 +423,7 @@ like($m->content, qr/$key2.*?$key1/s, "second key (now preferred) shows up befor
 $m->no_warnings_ok;
 
 # test that the new fields work
-$m->get("$baseurl/Search/Simple.html?q=General");
+$m->get("$baseurl/Search/Simple.html?q=". $queue->Name);
 my $content = $m->content;
 $content =~ s/(/(/g;
 $content =~ s/)/)/g;

commit 3695530245c1510c58bc35d3ae629e08af31d93c
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 01:51:55 2013 +0400

    some expected warnings in the test

diff --git a/t/web/crypt-gnupg.t b/t/web/crypt-gnupg.t
index 904a639..d459f05 100644
--- a/t/web/crypt-gnupg.t
+++ b/t/web/crypt-gnupg.t
@@ -420,7 +420,7 @@ like($m->content, qr/$key2/, "second key shows up in preferences");
 like($m->content, qr/$key1/, "first key shows up in preferences");
 like($m->content, qr/$key2.*?$key1/s, "second key (now preferred) shows up before the first");
 
-$m->no_warnings_ok;
+ok !grep !m{Couldn't load custom field by 'SMIME Key' identifier}, $m->get_warnings;
 
 # test that the new fields work
 $m->get("$baseurl/Search/Simple.html?q=". $queue->Name);

commit 950e0676d52eb5027ee9fdb6dc6099db9c5fefb0
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 02:13:14 2013 +0400

    fixup, misplaced code

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index 5ef20a1..cd89980 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -799,33 +799,6 @@ sub CheckIfProtected {
     # we check inline PGP block later in another sub
     return () unless $entity->is_multipart;
 
-        # Deal with "partitioned" PGP mail, which (contrary to common
-        # sense) unnecessarily applies a base64 transfer encoding to PGP
-        # mail (whose content is already base64-encoded).
-        if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
-            my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
-            if ($decoder) {
-                local $@;
-                eval {
-                    my $buf = '';
-                    open my $fh, '>', \$buf
-                        or die "Couldn't open scalar for writing: $!";
-                    binmode $fh, ":raw";
-                    $decoder->decode($io, $fh);
-                    close $fh or die "Couldn't close scalar: $!";
-
-                    open $fh, '<', \$buf
-                        or die "Couldn't re-open scalar for reading: $!";
-                    binmode $fh, ":raw";
-                    $io = $fh;
-                    1;
-                } or do {
-                    $RT::Logger->error("Couldn't decode body: $@");
-                }
-            }
-        }
-
-
     # RFC3156, multipart/{signed,encrypted}
     my $type = $entity->effective_type;
     return () unless $type =~ /^multipart\/(?:encrypted|signed)$/;
@@ -999,6 +972,33 @@ sub _CheckIfProtectedInline {
         $RT::Logger->warning( "Entity of type ". $entity->effective_type ." has no body" );
         return '';
     }
+
+    # Deal with "partitioned" PGP mail, which (contrary to common
+    # sense) unnecessarily applies a base64 transfer encoding to PGP
+    # mail (whose content is already base64-encoded).
+    if ( $entity->bodyhandle->is_encoded and $entity->head->mime_encoding ) {
+        my $decoder = MIME::Decoder->new( $entity->head->mime_encoding );
+        if ($decoder) {
+            local $@;
+            eval {
+                my $buf = '';
+                open my $fh, '>', \$buf
+                    or die "Couldn't open scalar for writing: $!";
+                binmode $fh, ":raw";
+                $decoder->decode($io, $fh);
+                close $fh or die "Couldn't close scalar: $!";
+
+                open $fh, '<', \$buf
+                    or die "Couldn't re-open scalar for reading: $!";
+                binmode $fh, ":raw";
+                $io = $fh;
+                1;
+            } or do {
+                $RT::Logger->error("Couldn't decode body: $@");
+            }
+        }
+    }
+
     while ( defined($_ = $io->getline) ) {
         if ( /^-----BEGIN PGP (SIGNED )?MESSAGE-----/ ) {
             return $1? 'signed': 'encrypted';

commit ea5c7c3a679de19a35a7fea3dec7800182dc2ca1
Author: Ruslan Zakirov <ruz at bestpractical.com>
Date:   Sat Jan 26 02:13:44 2013 +0400

    fixup, typo on conflict resolution

diff --git a/lib/RT/Crypt/GnuPG.pm b/lib/RT/Crypt/GnuPG.pm
index cd89980..7f965a8 100644
--- a/lib/RT/Crypt/GnuPG.pm
+++ b/lib/RT/Crypt/GnuPG.pm
@@ -1951,7 +1951,7 @@ sub DeleteKey {
 
     return $self->CallGnuPG(
         Command     => "--delete-secret-and-public-key",
-        CommandArgs => [\--', $key],
+        CommandArgs => ['--', $key],
         Callback    => sub {
             my %handle = @_;
             while ( my $str = readline $handle{'status'} ) {

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


More information about the Rt-commit mailing list