181 lines
4.5 KiB
Ruby
181 lines
4.5 KiB
Ruby
|
class SecureMailing::SMIME::Incoming < SecureMailing::Backend::Handler
|
||
|
|
||
|
EXPRESSION_MIME = %r{application/(x-pkcs7|pkcs7)-mime}i.freeze
|
||
|
EXPRESSION_SIGNATURE = %r{application/(x-pkcs7|pkcs7)-signature}i.freeze
|
||
|
|
||
|
OPENSSL_PKCS7_VERIFY_FLAGS = OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN
|
||
|
|
||
|
def initialize(mail)
|
||
|
@mail = mail
|
||
|
@content_type = @mail[:mail_instance].content_type
|
||
|
end
|
||
|
|
||
|
def process
|
||
|
return if !process?
|
||
|
|
||
|
initialize_article_preferences
|
||
|
decrypt
|
||
|
verify_signature
|
||
|
log
|
||
|
end
|
||
|
|
||
|
def initialize_article_preferences
|
||
|
article_preferences[:security] = {
|
||
|
type: 'S/MIME',
|
||
|
sign: {
|
||
|
success: false,
|
||
|
comment: nil,
|
||
|
},
|
||
|
encryption: {
|
||
|
success: false,
|
||
|
comment: nil,
|
||
|
}
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def article_preferences
|
||
|
@article_preferences ||= begin
|
||
|
key = 'x-zammad-article-preferences'.to_sym
|
||
|
@mail[ key ] ||= {}
|
||
|
@mail[ key ]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def process?
|
||
|
signed? || smime?
|
||
|
end
|
||
|
|
||
|
def signed?(content_type = @content_type)
|
||
|
EXPRESSION_SIGNATURE.match?(content_type)
|
||
|
end
|
||
|
|
||
|
def smime?(content_type = @content_type)
|
||
|
EXPRESSION_MIME.match?(content_type)
|
||
|
end
|
||
|
|
||
|
def decrypt
|
||
|
return if !smime?
|
||
|
|
||
|
success = false
|
||
|
comment = 'Unable to find private key to decrypt'
|
||
|
::SMIMECertificate.where.not(private_key: [nil, '']).find_each do |cert|
|
||
|
key = OpenSSL::PKey::RSA.new(cert.private_key, cert.private_key_secret)
|
||
|
|
||
|
begin
|
||
|
decrypted_data = p7enc.decrypt(key, cert.parsed)
|
||
|
rescue
|
||
|
next
|
||
|
end
|
||
|
|
||
|
@mail[:mail_instance].header['Content-Type'] = nil
|
||
|
@mail[:mail_instance].header['Content-Disposition'] = nil
|
||
|
@mail[:mail_instance].header['Content-Transfer-Encoding'] = nil
|
||
|
@mail[:mail_instance].header['Content-Description'] = nil
|
||
|
|
||
|
new_raw_mail = "#{@mail[:mail_instance].header}#{decrypted_data}"
|
||
|
|
||
|
mail_new = Channel::EmailParser.new.parse(new_raw_mail)
|
||
|
mail_new.each do |local_key, local_value|
|
||
|
@mail[local_key] = local_value
|
||
|
end
|
||
|
|
||
|
success = true
|
||
|
comment = cert.subject
|
||
|
if cert.expired?
|
||
|
comment += " (Certificate #{cert.fingerprint} with start date #{cert.not_before_at} and end date #{cert.not_after_at} expired!)"
|
||
|
end
|
||
|
|
||
|
# overwrite content_type for signature checking
|
||
|
@content_type = @mail[:mail_instance].content_type
|
||
|
break
|
||
|
end
|
||
|
|
||
|
article_preferences[:security][:encryption] = {
|
||
|
success: success,
|
||
|
comment: comment,
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def verify_signature
|
||
|
return if !signed?
|
||
|
|
||
|
success = false
|
||
|
comment = 'Unable to find certificate for verification'
|
||
|
::SMIMECertificate.find_each do |cert|
|
||
|
verify_certs = []
|
||
|
verify_ca = OpenSSL::X509::Store.new
|
||
|
|
||
|
if cert.parsed.issuer.to_s == cert.parsed.subject.to_s
|
||
|
verify_ca.add_cert(cert.parsed)
|
||
|
|
||
|
# CA
|
||
|
verify_certs = p7enc.certificates.select do |message_cert|
|
||
|
message_cert.issuer.to_s == cert.parsed.subject.to_s && verify_ca.verify(message_cert)
|
||
|
end
|
||
|
else
|
||
|
|
||
|
# normal
|
||
|
verify_certs.push(cert.parsed)
|
||
|
end
|
||
|
|
||
|
success = p7enc.verify(verify_certs, verify_ca, nil, OPENSSL_PKCS7_VERIFY_FLAGS)
|
||
|
next if !success
|
||
|
|
||
|
comment = cert.subject
|
||
|
if cert.expired?
|
||
|
comment += " (Certificate #{cert.fingerprint} with start date #{cert.not_before_at} and end date #{cert.not_after_at} expired!)"
|
||
|
end
|
||
|
break
|
||
|
rescue => e
|
||
|
success = false
|
||
|
comment = e.message
|
||
|
end
|
||
|
|
||
|
if success
|
||
|
@mail[:attachments].delete_if do |attachment|
|
||
|
signed?(attachment.dig(:preferences, 'Content-Type'))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
article_preferences[:security][:sign] = {
|
||
|
success: success,
|
||
|
comment: comment,
|
||
|
}
|
||
|
end
|
||
|
|
||
|
def p7enc
|
||
|
OpenSSL::PKCS7.read_smime(@mail[:raw])
|
||
|
end
|
||
|
|
||
|
def log
|
||
|
%i[sign encryption].each do |action|
|
||
|
result = article_preferences[:security][action]
|
||
|
next if result.blank?
|
||
|
|
||
|
if result[:success]
|
||
|
status = 'success'
|
||
|
elsif result[:comment].blank?
|
||
|
# means not performed
|
||
|
next
|
||
|
else
|
||
|
status = 'failed'
|
||
|
end
|
||
|
|
||
|
HttpLog.create(
|
||
|
direction: 'in',
|
||
|
facility: 'S/MIME',
|
||
|
url: "#{@mail[:from]} -> #{@mail[:to]}",
|
||
|
status: status,
|
||
|
ip: nil,
|
||
|
request: {
|
||
|
message_id: @mail[:message_id],
|
||
|
},
|
||
|
response: article_preferences[:security],
|
||
|
method: action,
|
||
|
created_by_id: 1,
|
||
|
updated_by_id: 1,
|
||
|
)
|
||
|
end
|
||
|
end
|
||
|
end
|