init III
This commit is contained in:
386
Perl OTRS/Kernel/Output/HTML/ArticleCheck/PGP.pm
Normal file
386
Perl OTRS/Kernel/Output/HTML/ArticleCheck/PGP.pm
Normal file
@@ -0,0 +1,386 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::Output::HTML::ArticleCheck::PGP;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use MIME::Parser;
|
||||
use Kernel::System::EmailParser;
|
||||
use Kernel::System::VariableCheck qw(:all);
|
||||
use Kernel::Language qw(Translatable);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Crypt::PGP',
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::Ticket::Article',
|
||||
);
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
# allocate new hash for object
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
# get needed params
|
||||
for my $Needed (qw(UserID ArticleID)) {
|
||||
if ( $Param{$Needed} ) {
|
||||
$Self->{$Needed} = $Param{$Needed};
|
||||
}
|
||||
else {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
sub Check {
|
||||
my ( $Self, %Param ) = @_;
|
||||
my %SignCheck;
|
||||
my @Return;
|
||||
|
||||
# get config object
|
||||
my $ConfigObject = $Param{ConfigObject} || $Kernel::OM->Get('Kernel::Config');
|
||||
|
||||
# check if pgp is enabled
|
||||
return if !$ConfigObject->Get('PGP');
|
||||
|
||||
my $ArticleObject = $Param{ArticleObject} || $Kernel::OM->Get('Kernel::System::Ticket::Article');
|
||||
|
||||
my $ArticleBackendObject = $ArticleObject->BackendForArticle(
|
||||
TicketID => $Param{Article}->{TicketID},
|
||||
ArticleID => $Param{Article}->{ArticleID},
|
||||
);
|
||||
|
||||
# check if article is an email
|
||||
return if $ArticleBackendObject->ChannelNameGet() ne 'Email';
|
||||
|
||||
# get needed objects
|
||||
my $PGPObject = $Kernel::OM->Get('Kernel::System::Crypt::PGP');
|
||||
|
||||
# check inline pgp crypt
|
||||
if ( $Param{Article}->{Body} && $Param{Article}->{Body} =~ /\A[\s\n]*^-----BEGIN PGP MESSAGE-----/m ) {
|
||||
|
||||
# check sender (don't decrypt sent emails)
|
||||
if ( $Param{Article}->{SenderType} =~ /(agent|system)/i ) {
|
||||
|
||||
# return info
|
||||
return (
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => Translatable('Sent message encrypted to recipient!'),
|
||||
}
|
||||
);
|
||||
}
|
||||
my %Decrypt = $PGPObject->Decrypt( Message => $Param{Article}->{Body} );
|
||||
if ( $Decrypt{Successful} ) {
|
||||
|
||||
# remember to result
|
||||
$Self->{Result} = \%Decrypt;
|
||||
$Param{Article}->{Body} = $Decrypt{Data};
|
||||
|
||||
# updated article body
|
||||
$ArticleBackendObject->ArticleUpdate(
|
||||
TicketID => $Param{Article}->{TicketID},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
Key => 'Body',
|
||||
Value => $Decrypt{Data},
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
|
||||
# get a list of all article attachments
|
||||
my %Index = $ArticleBackendObject->ArticleAttachmentIndex(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
);
|
||||
|
||||
my @Attachments;
|
||||
if ( IsHashRefWithData( \%Index ) ) {
|
||||
for my $FileID ( sort keys %Index ) {
|
||||
|
||||
# get attachment details
|
||||
my %Attachment = $ArticleBackendObject->ArticleAttachment(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
FileID => $FileID,
|
||||
);
|
||||
|
||||
# store attachemnts attributes that might change after decryption
|
||||
my $AttachmentContent = $Attachment{Content};
|
||||
my $AttachmentFilename = $Attachment{Filename};
|
||||
|
||||
# try to decrypt the attachment, non ecrypted attachments will succeed too.
|
||||
%Decrypt = $PGPObject->Decrypt( Message => $Attachment{Content} );
|
||||
|
||||
if ( $Decrypt{Successful} ) {
|
||||
|
||||
# set decrypted content
|
||||
$AttachmentContent = $Decrypt{Data};
|
||||
|
||||
# remove .pgp .gpg or asc extensions (if any)
|
||||
$AttachmentFilename =~ s{ (\. [^\.]+) \. (?: pgp|gpg|asc) \z}{$1}msx;
|
||||
}
|
||||
|
||||
# remember decrypted attachement, to add it later
|
||||
push @Attachments, {
|
||||
%Attachment,
|
||||
Content => $AttachmentContent,
|
||||
Filename => $AttachmentFilename,
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $Self->{UserID},
|
||||
};
|
||||
}
|
||||
|
||||
# delete crypted attachments
|
||||
$ArticleBackendObject->ArticleDeleteAttachment(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
|
||||
# write decrypted attachments to the storage
|
||||
for my $Attachment (@Attachments) {
|
||||
$ArticleBackendObject->ArticleWriteAttachment( %{$Attachment} );
|
||||
}
|
||||
}
|
||||
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => $Decrypt{Message},
|
||||
%Decrypt,
|
||||
},
|
||||
);
|
||||
}
|
||||
else {
|
||||
|
||||
# return with error
|
||||
return (
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => $Decrypt{Message},
|
||||
%Decrypt,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# Get plain article/email from filesystem storage.
|
||||
my $Message = $ArticleBackendObject->ArticlePlain(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
return if !$Message;
|
||||
|
||||
# check inline pgp signature (but ignore if is in quoted text)
|
||||
if (
|
||||
$Param{Article}->{Body}
|
||||
&& $Param{Article}->{Body} =~ m{ ^\s* -----BEGIN [ ] PGP [ ] SIGNED [ ] MESSAGE----- }xms
|
||||
)
|
||||
{
|
||||
# create local email parser object
|
||||
my $ParserObject = Kernel::System::EmailParser->new(
|
||||
Email => $Message,
|
||||
);
|
||||
|
||||
# get the charset of the original message
|
||||
my $Charset = $ParserObject->GetCharset();
|
||||
|
||||
# verify message PGP signature
|
||||
%SignCheck = $PGPObject->Verify(
|
||||
Message => $Param{Article}->{Body},
|
||||
Charset => $Charset
|
||||
);
|
||||
|
||||
if (%SignCheck) {
|
||||
|
||||
# remember to result
|
||||
$Self->{Result} = \%SignCheck;
|
||||
}
|
||||
else {
|
||||
|
||||
# return with error
|
||||
return (
|
||||
{
|
||||
Key => Translatable('Signed'),
|
||||
Value => Translatable('"PGP SIGNED MESSAGE" header found, but invalid!'),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# check mime pgp
|
||||
else {
|
||||
|
||||
# check body
|
||||
# if body =~ application/pgp-encrypted
|
||||
# if crypted, decrypt it
|
||||
# remember that it was crypted!
|
||||
|
||||
my $Parser = MIME::Parser->new();
|
||||
$Parser->decode_headers(0);
|
||||
$Parser->extract_nested_messages(0);
|
||||
$Parser->output_to_core('ALL');
|
||||
|
||||
# prevent modification of body by parser - required for bug #11755
|
||||
$Parser->decode_bodies(0);
|
||||
my $Entity = $Parser->parse_data($Message);
|
||||
$Parser->decode_bodies(1);
|
||||
my $Head = $Entity->head();
|
||||
$Head->unfold();
|
||||
$Head->combine('Content-Type');
|
||||
my $ContentType = $Head->get('Content-Type');
|
||||
|
||||
# check if we need to decrypt it
|
||||
if (
|
||||
$ContentType
|
||||
&& $ContentType =~ /multipart\/encrypted/i
|
||||
&& $ContentType =~ /application\/pgp/i
|
||||
)
|
||||
{
|
||||
|
||||
# check sender (don't decrypt sent emails)
|
||||
if ( $Param{Article}->{SenderType} && $Param{Article}->{SenderType} =~ /(agent|system)/i ) {
|
||||
|
||||
# return info
|
||||
return (
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => Translatable('Sent message encrypted to recipient!'),
|
||||
Successful => 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
# get crypted part of the mail
|
||||
my $Crypted = $Entity->parts(1)->as_string();
|
||||
|
||||
# decrypt it
|
||||
my %Decrypt = $PGPObject->Decrypt(
|
||||
Message => $Crypted,
|
||||
);
|
||||
if ( $Decrypt{Successful} ) {
|
||||
$Entity = $Parser->parse_data( $Decrypt{Data} );
|
||||
my $Head = $Entity->head();
|
||||
$Head->unfold();
|
||||
$Head->combine('Content-Type');
|
||||
$ContentType = $Head->get('Content-Type');
|
||||
|
||||
# use a copy of the Entity to get the body, otherwise the original mail content
|
||||
# could be altered and a signature verify could fail. See Bug#9954
|
||||
my $EntityCopy = $Entity->dup();
|
||||
|
||||
my $ParserObject = Kernel::System::EmailParser->new(
|
||||
Entity => $EntityCopy,
|
||||
);
|
||||
|
||||
my $Body = $ParserObject->GetMessageBody();
|
||||
|
||||
# updated article body
|
||||
$ArticleBackendObject->ArticleUpdate(
|
||||
TicketID => $Param{Article}->{TicketID},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
Key => 'Body',
|
||||
Value => $Body,
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
|
||||
# delete crypted attachments
|
||||
$ArticleBackendObject->ArticleDeleteAttachment(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
|
||||
# write attachments to the storage
|
||||
for my $Attachment ( $ParserObject->GetAttachments() ) {
|
||||
$ArticleBackendObject->ArticleWriteAttachment(
|
||||
%{$Attachment},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
}
|
||||
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => $Decrypt{Message},
|
||||
%Decrypt,
|
||||
},
|
||||
);
|
||||
}
|
||||
else {
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => $Decrypt{Message},
|
||||
%Decrypt,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
$ContentType
|
||||
&& $ContentType =~ /multipart\/signed/i
|
||||
&& $ContentType =~ /application\/pgp/i
|
||||
&& $Entity->parts(0)
|
||||
&& $Entity->parts(1)
|
||||
)
|
||||
{
|
||||
|
||||
my $SignedText = $Entity->parts(0)->as_string();
|
||||
my $SignatureText = $Entity->parts(1)->body_as_string();
|
||||
|
||||
# according to RFC3156 all line endings MUST be CR/LF
|
||||
$SignedText =~ s/\x0A/\x0D\x0A/g;
|
||||
$SignedText =~ s/\x0D+/\x0D/g;
|
||||
|
||||
%SignCheck = $PGPObject->Verify(
|
||||
Message => $SignedText,
|
||||
Sign => $SignatureText,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (%SignCheck) {
|
||||
|
||||
# return result
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Signed'),
|
||||
Value => $SignCheck{Message},
|
||||
%SignCheck,
|
||||
},
|
||||
);
|
||||
}
|
||||
return @Return;
|
||||
}
|
||||
|
||||
sub Filter {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# remove signature if one is found
|
||||
if ( $Self->{Result}->{SignatureFound} ) {
|
||||
|
||||
# remove pgp begin signed message
|
||||
$Param{Article}->{Body} =~ s/^-----BEGIN\sPGP\sSIGNED\sMESSAGE-----.+?Hash:\s.+?$//sm;
|
||||
|
||||
# remove pgp inline sign
|
||||
$Param{Article}->{Body}
|
||||
=~ s/^-----BEGIN\sPGP\sSIGNATURE-----.+?-----END\sPGP\sSIGNATURE-----//sm;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
1;
|
||||
486
Perl OTRS/Kernel/Output/HTML/ArticleCheck/SMIME.pm
Normal file
486
Perl OTRS/Kernel/Output/HTML/ArticleCheck/SMIME.pm
Normal file
@@ -0,0 +1,486 @@
|
||||
# --
|
||||
# Copyright (C) 2001-2019 OTRS AG, https://otrs.com/
|
||||
# --
|
||||
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
|
||||
# the enclosed file COPYING for license information (GPL). If you
|
||||
# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt.
|
||||
# --
|
||||
|
||||
package Kernel::Output::HTML::ArticleCheck::SMIME;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Kernel::System::EmailParser;
|
||||
use Kernel::Language qw(Translatable);
|
||||
|
||||
our @ObjectDependencies = (
|
||||
'Kernel::Config',
|
||||
'Kernel::System::Crypt::SMIME',
|
||||
'Kernel::System::Log',
|
||||
'Kernel::System::Ticket::Article',
|
||||
'Kernel::Output::HTML::Layout',
|
||||
);
|
||||
|
||||
sub new {
|
||||
my ( $Type, %Param ) = @_;
|
||||
|
||||
my $Self = {};
|
||||
bless( $Self, $Type );
|
||||
|
||||
for my $Needed (qw(UserID ArticleID)) {
|
||||
if ( $Param{$Needed} ) {
|
||||
$Self->{$Needed} = $Param{$Needed};
|
||||
}
|
||||
else {
|
||||
$Kernel::OM->Get('Kernel::System::Log')->Log(
|
||||
Priority => 'error',
|
||||
Message => "Need $Needed!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $Self;
|
||||
}
|
||||
|
||||
sub Check {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
my %SignCheck;
|
||||
my @Return;
|
||||
|
||||
my $ConfigObject = $Param{ConfigObject} || $Kernel::OM->Get('Kernel::Config');
|
||||
my $LayoutObject = $Param{LayoutObject} || $Kernel::OM->Get('Kernel::Output::HTML::Layout');
|
||||
|
||||
my $UserType = $LayoutObject->{UserType} // '';
|
||||
my $ChangeUserID = $UserType eq 'Customer' ? $ConfigObject->Get('CustomerPanelUserID') : $Self->{UserID};
|
||||
|
||||
# check if smime is enabled
|
||||
return if !$ConfigObject->Get('SMIME');
|
||||
|
||||
# check if article is an email
|
||||
my $ArticleBackendObject
|
||||
= $Kernel::OM->Get('Kernel::System::Ticket::Article')->BackendForArticle( %{ $Param{Article} // {} } );
|
||||
return if $ArticleBackendObject->ChannelNameGet() ne 'Email';
|
||||
|
||||
my $SMIMEObject = $Kernel::OM->Get('Kernel::System::Crypt::SMIME');
|
||||
|
||||
# check inline smime
|
||||
if ( $Param{Article}->{Body} && $Param{Article}->{Body} =~ /^-----BEGIN PKCS7-----/ ) {
|
||||
%SignCheck = $SMIMEObject->Verify( Message => $Param{Article}->{Body} );
|
||||
if (%SignCheck) {
|
||||
|
||||
# remember to result
|
||||
$Self->{Result} = \%SignCheck;
|
||||
}
|
||||
else {
|
||||
|
||||
# return with error
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Signed'),
|
||||
Value => Translatable('"S/MIME SIGNED MESSAGE" header found, but invalid!'),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# check smime
|
||||
else {
|
||||
|
||||
# get email from fs
|
||||
my $Message = $ArticleBackendObject->ArticlePlain(
|
||||
TicketID => $Param{Article}->{TicketID},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $Self->{UserID},
|
||||
);
|
||||
return if !$Message;
|
||||
|
||||
my @Email = ();
|
||||
my @Lines = split( /\n/, $Message );
|
||||
for my $Line (@Lines) {
|
||||
push( @Email, $Line . "\n" );
|
||||
}
|
||||
|
||||
my $ParserObject = Kernel::System::EmailParser->new(
|
||||
Email => \@Email,
|
||||
);
|
||||
|
||||
use MIME::Parser;
|
||||
my $Parser = MIME::Parser->new();
|
||||
$Parser->decode_headers(0);
|
||||
$Parser->extract_nested_messages(0);
|
||||
$Parser->output_to_core("ALL");
|
||||
my $Entity = $Parser->parse_data($Message);
|
||||
my $Head = $Entity->head();
|
||||
$Head->unfold();
|
||||
$Head->combine('Content-Type');
|
||||
my $ContentType = $Head->get('Content-Type');
|
||||
|
||||
if (
|
||||
$ContentType
|
||||
&& $ContentType =~ /application\/(x-pkcs7|pkcs7)-mime/i
|
||||
&& $ContentType !~ /signed/i
|
||||
)
|
||||
{
|
||||
|
||||
# check if article is already decrypted
|
||||
if ( $Param{Article}->{Body} && $Param{Article}->{Body} ne '- no text message => see attachment -' ) {
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => Translatable('Ticket decrypted before'),
|
||||
Successful => 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
# check sender (don't decrypt sent emails)
|
||||
if ( $Param{Article}->{SenderType} && $Param{Article}->{SenderType} =~ /(agent|system)/i ) {
|
||||
|
||||
# return info
|
||||
return (
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => Translatable('Sent message encrypted to recipient!'),
|
||||
Successful => 1,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
# get all email addresses on article
|
||||
my %EmailsToSearch;
|
||||
for my $Email (qw(Resent-To Envelope-To To Cc Delivered-To X-Original-To)) {
|
||||
|
||||
my @EmailAddressOnField = $ParserObject->SplitAddressLine(
|
||||
Line => $ParserObject->GetParam( WHAT => $Email ),
|
||||
);
|
||||
|
||||
# filter email addresses avoiding repeated and save on hash to search
|
||||
for my $EmailAddress (@EmailAddressOnField) {
|
||||
my $CleanEmailAddress = $ParserObject->GetEmailAddress(
|
||||
Email => $EmailAddress,
|
||||
);
|
||||
$EmailsToSearch{$CleanEmailAddress} = '1';
|
||||
}
|
||||
}
|
||||
|
||||
# look for private keys for every email address
|
||||
# extract every resulting cert and put it into an hash of hashes avoiding repeated
|
||||
my %PrivateKeys;
|
||||
for my $EmailAddress ( sort keys %EmailsToSearch ) {
|
||||
my @PrivateKeysResult = $SMIMEObject->PrivateSearch(
|
||||
Search => $EmailAddress,
|
||||
);
|
||||
for my $Cert (@PrivateKeysResult) {
|
||||
$PrivateKeys{ $Cert->{Filename} } = $Cert;
|
||||
}
|
||||
}
|
||||
|
||||
# search private cert to decrypt email
|
||||
if ( !%PrivateKeys ) {
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => Translatable('Impossible to decrypt: private key for email was not found!'),
|
||||
}
|
||||
);
|
||||
return @Return;
|
||||
}
|
||||
|
||||
my %Decrypt;
|
||||
PRIVATESEARCH:
|
||||
for my $CertResult ( values %PrivateKeys ) {
|
||||
|
||||
# decrypt
|
||||
%Decrypt = $SMIMEObject->Decrypt(
|
||||
Message => $Message,
|
||||
SearchingNeededKey => 1,
|
||||
%{$CertResult},
|
||||
);
|
||||
last PRIVATESEARCH if ( $Decrypt{Successful} );
|
||||
}
|
||||
|
||||
# ok, decryption went fine
|
||||
if ( $Decrypt{Successful} ) {
|
||||
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => $Decrypt{Message} || Translatable('Successful decryption'),
|
||||
%Decrypt,
|
||||
}
|
||||
);
|
||||
|
||||
# store decrypted data
|
||||
my $EmailContent = $Decrypt{Data};
|
||||
|
||||
# now check if the data contains a signature too
|
||||
%SignCheck = $SMIMEObject->Verify(
|
||||
Message => $Decrypt{Data},
|
||||
);
|
||||
|
||||
if ( $SignCheck{SignatureFound} ) {
|
||||
|
||||
# If the signature was verified well, use the stripped content to store the email.
|
||||
# Now it contains only the email without other SMIME generated data.
|
||||
$EmailContent = $SignCheck{Content} if $SignCheck{Successful};
|
||||
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Signed'),
|
||||
Value => $SignCheck{Message},
|
||||
%SignCheck,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
# parse the decrypted email body
|
||||
my $ParserObject = Kernel::System::EmailParser->new(
|
||||
Email => $EmailContent
|
||||
);
|
||||
my $Body = $ParserObject->GetMessageBody();
|
||||
|
||||
# from RFC 3850
|
||||
# 3. Using Distinguished Names for Internet Mail
|
||||
#
|
||||
# End-entity certificates MAY contain ...
|
||||
#
|
||||
# ...
|
||||
#
|
||||
# Sending agents SHOULD make the address in the From or Sender header
|
||||
# in a mail message match an Internet mail address in the signer's
|
||||
# certificate. Receiving agents MUST check that the address in the
|
||||
# From or Sender header of a mail message matches an Internet mail
|
||||
# address, if present, in the signer's certificate, if mail addresses
|
||||
# are present in the certificate. A receiving agent SHOULD provide
|
||||
# some explicit alternate processing of the message if this comparison
|
||||
# fails, which may be to display a message that shows the recipient the
|
||||
# addresses in the certificate or other certificate details.
|
||||
|
||||
# as described in bug#5098 and RFC 3850 an alternate mail handling should be
|
||||
# made if sender and signer addresses does not match
|
||||
|
||||
# get original sender from email
|
||||
my @OrigEmail = map {"$_\n"} split( /\n/, $Message );
|
||||
my $ParserObjectOrig = Kernel::System::EmailParser->new(
|
||||
Email => \@OrigEmail,
|
||||
);
|
||||
|
||||
my $OrigFrom = $ParserObjectOrig->GetParam( WHAT => 'From' );
|
||||
my $OrigSender = $ParserObjectOrig->GetEmailAddress( Email => $OrigFrom );
|
||||
|
||||
# compare sender email to signer email
|
||||
my $SignerSenderMatch = 0;
|
||||
SIGNER:
|
||||
for my $Signer ( @{ $SignCheck{Signers} } ) {
|
||||
if ( $OrigSender =~ m{\A \Q$Signer\E \z}xmsi ) {
|
||||
$SignerSenderMatch = 1;
|
||||
last SIGNER;
|
||||
}
|
||||
}
|
||||
|
||||
# sender email does not match signing certificate!
|
||||
if ( !$SignerSenderMatch ) {
|
||||
$SignCheck{Successful} = 0;
|
||||
$SignCheck{Message} =~ s/successful/failed!/;
|
||||
$SignCheck{Message} .= " (signed by "
|
||||
. join( ' | ', @{ $SignCheck{Signers} } )
|
||||
. ")"
|
||||
. ", but sender address $OrigSender: does not match certificate address!";
|
||||
}
|
||||
|
||||
# updated article body
|
||||
$ArticleBackendObject->ArticleUpdate(
|
||||
TicketID => $Param{Article}->{TicketID},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
Key => 'Body',
|
||||
Value => $Body,
|
||||
UserID => $ChangeUserID,
|
||||
);
|
||||
|
||||
# delete crypted attachments
|
||||
$ArticleBackendObject->ArticleDeleteAttachment(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $ChangeUserID,
|
||||
);
|
||||
|
||||
# write attachments to the storage
|
||||
for my $Attachment ( $ParserObject->GetAttachments() ) {
|
||||
$ArticleBackendObject->ArticleWriteAttachment(
|
||||
%{$Attachment},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $ChangeUserID,
|
||||
);
|
||||
}
|
||||
|
||||
return @Return;
|
||||
}
|
||||
else {
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Crypted'),
|
||||
Value => "$Decrypt{Message}",
|
||||
%Decrypt,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$ContentType
|
||||
&& $ContentType =~ /application\/(x-pkcs7|pkcs7)/i
|
||||
&& $ContentType =~ /signed/i
|
||||
)
|
||||
{
|
||||
|
||||
# check sign and get clear content
|
||||
%SignCheck = $SMIMEObject->Verify(
|
||||
Message => $Message,
|
||||
);
|
||||
|
||||
# parse and update clear content
|
||||
if ( %SignCheck && $SignCheck{Successful} && $SignCheck{Content} ) {
|
||||
|
||||
my @Email = ();
|
||||
my @Lines = split( /\n/, $SignCheck{Content} );
|
||||
for (@Lines) {
|
||||
push( @Email, $_ . "\n" );
|
||||
}
|
||||
my $ParserObject = Kernel::System::EmailParser->new(
|
||||
Email => \@Email,
|
||||
);
|
||||
my $Body = $ParserObject->GetMessageBody();
|
||||
|
||||
# from RFC 3850
|
||||
# 3. Using Distinguished Names for Internet Mail
|
||||
#
|
||||
# End-entity certificates MAY contain ...
|
||||
#
|
||||
# ...
|
||||
#
|
||||
# Sending agents SHOULD make the address in the From or Sender header
|
||||
# in a mail message match an Internet mail address in the signer's
|
||||
# certificate. Receiving agents MUST check that the address in the
|
||||
# From or Sender header of a mail message matches an Internet mail
|
||||
# address, if present, in the signer's certificate, if mail addresses
|
||||
# are present in the certificate. A receiving agent SHOULD provide
|
||||
# some explicit alternate processing of the message if this comparison
|
||||
# fails, which may be to display a message that shows the recipient the
|
||||
# addresses in the certificate or other certificate details.
|
||||
|
||||
# as described in bug#5098 and RFC 3850 an alternate mail handling should be
|
||||
# made if sender and signer addresses does not match
|
||||
|
||||
# get original sender from email
|
||||
my @OrigEmail = map {"$_\n"} split( /\n/, $Message );
|
||||
my $ParserObjectOrig = Kernel::System::EmailParser->new(
|
||||
Email => \@OrigEmail,
|
||||
);
|
||||
|
||||
my $OrigFrom = $ParserObjectOrig->GetParam( WHAT => 'From' );
|
||||
my $OrigSender = $ParserObjectOrig->GetEmailAddress( Email => $OrigFrom );
|
||||
|
||||
# compare sender email to signer email
|
||||
my $SignerSenderMatch = 0;
|
||||
SIGNER:
|
||||
for my $Signer ( @{ $SignCheck{Signers} } ) {
|
||||
if ( $OrigSender =~ m{\A \Q$Signer\E \z}xmsi ) {
|
||||
$SignerSenderMatch = 1;
|
||||
last SIGNER;
|
||||
}
|
||||
}
|
||||
|
||||
# sender email does not match signing certificate!
|
||||
if ( !$SignerSenderMatch ) {
|
||||
$SignCheck{Successful} = 0;
|
||||
$SignCheck{Message} =~ s/successful/failed!/;
|
||||
$SignCheck{Message} .= " (signed by "
|
||||
. join( ' | ', @{ $SignCheck{Signers} } )
|
||||
. ")"
|
||||
. ", but sender address $OrigSender: does not match certificate address!";
|
||||
}
|
||||
|
||||
# updated article body
|
||||
$ArticleBackendObject->ArticleUpdate(
|
||||
TicketID => $Param{Article}->{TicketID},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
Key => 'Body',
|
||||
Value => $Body,
|
||||
UserID => $ChangeUserID,
|
||||
);
|
||||
|
||||
# delete crypted attachments
|
||||
$ArticleBackendObject->ArticleDeleteAttachment(
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $ChangeUserID,
|
||||
);
|
||||
|
||||
# write attachments to the storage
|
||||
for my $Attachment ( $ParserObject->GetAttachments() ) {
|
||||
$ArticleBackendObject->ArticleWriteAttachment(
|
||||
%{$Attachment},
|
||||
ArticleID => $Self->{ArticleID},
|
||||
UserID => $ChangeUserID,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
# output signature verification errors
|
||||
elsif (
|
||||
%SignCheck
|
||||
&& !$SignCheck{SignatureFound}
|
||||
&& !$SignCheck{Successful}
|
||||
&& !$SignCheck{Content}
|
||||
)
|
||||
{
|
||||
|
||||
# return result
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Signed'),
|
||||
Value => $SignCheck{Message},
|
||||
%SignCheck,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $SignCheck{SignatureFound} ) {
|
||||
|
||||
# return result
|
||||
push(
|
||||
@Return,
|
||||
{
|
||||
Key => Translatable('Signed'),
|
||||
Value => $SignCheck{Message},
|
||||
%SignCheck,
|
||||
}
|
||||
);
|
||||
}
|
||||
return @Return;
|
||||
}
|
||||
|
||||
sub Filter {
|
||||
my ( $Self, %Param ) = @_;
|
||||
|
||||
# remove signature if one is found
|
||||
if ( $Self->{Result}->{SignatureFound} ) {
|
||||
|
||||
# remove SMIME begin signed message
|
||||
$Param{Article}->{Body} =~ s/^-----BEGIN\sPKCS7-----.+?Hash:\s.+?$//sm;
|
||||
|
||||
# remove SMIME inline sign
|
||||
$Param{Article}->{Body} =~ s/^-----END\sPKCS7-----//sm;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
1;
|
||||
Reference in New Issue
Block a user