123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- 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
|