trabajo-afectivo/lib/secure_mailing/smime/incoming.rb

181 lines
4.5 KiB
Ruby
Raw Normal View History

2020-06-02 11:01:16 +00:00
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