2022-01-01 13:38:12 +00:00
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
2021-06-01 12:20:20 +00:00
2020-06-02 11:01:16 +00:00
class SecureMailing :: SMIME :: Outgoing < SecureMailing :: Backend :: Handler
def initialize ( mail , security )
2020-09-30 09:07:01 +00:00
super ( )
2020-06-02 11:01:16 +00:00
@mail = mail
@security = security
end
def process
return if ! process?
2020-08-13 11:32:52 +00:00
@mail = workaround_mail_bit_encoding_issue ( @mail )
2020-06-02 11:01:16 +00:00
if @security [ :sign ] [ :success ] && @security [ :encryption ] [ :success ]
processed = encrypt ( signed )
log ( 'sign' , 'success' )
log ( 'encryption' , 'success' )
elsif @security [ :sign ] [ :success ]
processed = Mail . new ( signed )
log ( 'sign' , 'success' )
elsif @security [ :encryption ] [ :success ]
processed = encrypt ( @mail . encoded )
log ( 'encryption' , 'success' )
end
overwrite_mail ( processed )
end
def process?
return false if @security . blank?
return false if @security [ :type ] != 'S/MIME'
@security [ :sign ] [ :success ] || @security [ :encryption ] [ :success ]
end
2020-08-13 11:32:52 +00:00
# S/MIME signing fails because of message encoding #3147
# workaround for https://github.com/mikel/mail/issues/1190
def workaround_mail_bit_encoding_issue ( mail )
# change 7bit/8bit encoding to binary so that
# base64 will be used to encode the content
if mail . body . encoding . include? ( 'bit' )
mail . body . encoding = :binary
end
# go into recursion for nested parts
mail . parts & . each do | part |
workaround_mail_bit_encoding_issue ( part )
end
mail
end
2020-06-02 11:01:16 +00:00
def overwrite_mail ( processed )
@mail . body = nil
@mail . body = processed . body . encoded
@mail . content_disposition = processed . content_disposition
@mail . content_transfer_encoding = processed . content_transfer_encoding
@mail . content_type = processed . content_type
end
def signed
from = @mail . from . first
cert_model = SMIMECertificate . for_sender_email_address ( from )
raise " Unable to find ssl private key for ' #{ from } ' " if ! cert_model
raise " Expired certificate for #{ from } (fingerprint #{ cert_model . fingerprint } ) with #{ cert_model . not_before_at } to #{ cert_model . not_after_at } " if ! @security [ :sign ] [ :allow_expired ] && cert_model . expired?
private_key = OpenSSL :: PKey :: RSA . new ( cert_model . private_key , cert_model . private_key_secret )
2021-04-27 06:53:36 +00:00
OpenSSL :: PKCS7 . write_smime ( OpenSSL :: PKCS7 . sign ( cert_model . parsed , private_key , @mail . encoded , chain ( cert_model ) , OpenSSL :: PKCS7 :: DETACHED ) )
2020-06-02 11:01:16 +00:00
rescue = > e
log ( 'sign' , 'failed' , e . message )
raise
end
2021-04-27 06:53:36 +00:00
def chain ( cert )
lookup_issuer = cert . parsed . issuer . to_s
result = [ ]
loop do
found_cert = SMIMECertificate . find_by ( subject : lookup_issuer )
break if found_cert . blank?
subject = found_cert . parsed . subject . to_s
lookup_issuer = found_cert . parsed . issuer . to_s
result . push ( found_cert . parsed )
# we've reached the root CA
break if subject == lookup_issuer
end
result
end
2020-06-02 11:01:16 +00:00
def encrypt ( data )
certificates = SMIMECertificate . for_recipipent_email_addresses! ( @mail . to )
expired_cert = certificates . detect ( & :expired? )
raise " Expired certificates for cert with #{ expired_cert . not_before_at } to #{ expired_cert . not_after_at } " if ! @security [ :encryption ] [ :allow_expired ] && expired_cert . present?
Mail . new ( OpenSSL :: PKCS7 . write_smime ( OpenSSL :: PKCS7 . encrypt ( certificates . map ( & :parsed ) , data , cipher ) ) )
rescue = > e
log ( 'encryption' , 'failed' , e . message )
raise
end
def cipher
@cipher || = OpenSSL :: Cipher . new ( 'AES-128-CBC' )
end
def log ( action , status , error = nil )
HttpLog . create (
direction : 'out' ,
facility : 'S/MIME' ,
url : " #{ @mail [ :from ] } -> #{ @mail [ :to ] } " ,
status : status ,
ip : nil ,
request : @security ,
response : { error : error } ,
method : action ,
created_by_id : 1 ,
updated_by_id : 1 ,
)
end
end