incoming.rb 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. class SecureMailing::SMIME::Incoming < SecureMailing::Backend::Handler
  2. EXPRESSION_MIME = %r{application/(x-pkcs7|pkcs7)-mime}i.freeze
  3. EXPRESSION_SIGNATURE = %r{application/(x-pkcs7|pkcs7)-signature}i.freeze
  4. OPENSSL_PKCS7_VERIFY_FLAGS = OpenSSL::PKCS7::NOVERIFY | OpenSSL::PKCS7::NOINTERN
  5. def initialize(mail)
  6. @mail = mail
  7. @content_type = @mail[:mail_instance].content_type
  8. end
  9. def process
  10. return if !process?
  11. initialize_article_preferences
  12. decrypt
  13. verify_signature
  14. log
  15. end
  16. def initialize_article_preferences
  17. article_preferences[:security] = {
  18. type: 'S/MIME',
  19. sign: {
  20. success: false,
  21. comment: nil,
  22. },
  23. encryption: {
  24. success: false,
  25. comment: nil,
  26. }
  27. }
  28. end
  29. def article_preferences
  30. @article_preferences ||= begin
  31. key = 'x-zammad-article-preferences'.to_sym
  32. @mail[ key ] ||= {}
  33. @mail[ key ]
  34. end
  35. end
  36. def process?
  37. signed? || smime?
  38. end
  39. def signed?(content_type = @content_type)
  40. EXPRESSION_SIGNATURE.match?(content_type)
  41. end
  42. def smime?(content_type = @content_type)
  43. EXPRESSION_MIME.match?(content_type)
  44. end
  45. def decrypt
  46. return if !smime?
  47. success = false
  48. comment = 'Unable to find private key to decrypt'
  49. ::SMIMECertificate.where.not(private_key: [nil, '']).find_each do |cert|
  50. key = OpenSSL::PKey::RSA.new(cert.private_key, cert.private_key_secret)
  51. begin
  52. decrypted_data = p7enc.decrypt(key, cert.parsed)
  53. rescue
  54. next
  55. end
  56. @mail[:mail_instance].header['Content-Type'] = nil
  57. @mail[:mail_instance].header['Content-Disposition'] = nil
  58. @mail[:mail_instance].header['Content-Transfer-Encoding'] = nil
  59. @mail[:mail_instance].header['Content-Description'] = nil
  60. new_raw_mail = "#{@mail[:mail_instance].header}#{decrypted_data}"
  61. mail_new = Channel::EmailParser.new.parse(new_raw_mail)
  62. mail_new.each do |local_key, local_value|
  63. @mail[local_key] = local_value
  64. end
  65. success = true
  66. comment = cert.subject
  67. if cert.expired?
  68. comment += " (Certificate #{cert.fingerprint} with start date #{cert.not_before_at} and end date #{cert.not_after_at} expired!)"
  69. end
  70. # overwrite content_type for signature checking
  71. @content_type = @mail[:mail_instance].content_type
  72. break
  73. end
  74. article_preferences[:security][:encryption] = {
  75. success: success,
  76. comment: comment,
  77. }
  78. end
  79. def verify_signature
  80. return if !signed?
  81. success = false
  82. comment = 'Unable to find certificate for verification'
  83. ::SMIMECertificate.find_each do |cert|
  84. verify_certs = []
  85. verify_ca = OpenSSL::X509::Store.new
  86. if cert.parsed.issuer.to_s == cert.parsed.subject.to_s
  87. verify_ca.add_cert(cert.parsed)
  88. # CA
  89. verify_certs = p7enc.certificates.select do |message_cert|
  90. message_cert.issuer.to_s == cert.parsed.subject.to_s && verify_ca.verify(message_cert)
  91. end
  92. else
  93. # normal
  94. verify_certs.push(cert.parsed)
  95. end
  96. success = p7enc.verify(verify_certs, verify_ca, nil, OPENSSL_PKCS7_VERIFY_FLAGS)
  97. next if !success
  98. comment = cert.subject
  99. if cert.expired?
  100. comment += " (Certificate #{cert.fingerprint} with start date #{cert.not_before_at} and end date #{cert.not_after_at} expired!)"
  101. end
  102. break
  103. rescue => e
  104. success = false
  105. comment = e.message
  106. end
  107. if success
  108. @mail[:attachments].delete_if do |attachment|
  109. signed?(attachment.dig(:preferences, 'Content-Type'))
  110. end
  111. end
  112. article_preferences[:security][:sign] = {
  113. success: success,
  114. comment: comment,
  115. }
  116. end
  117. def p7enc
  118. OpenSSL::PKCS7.read_smime(@mail[:raw])
  119. end
  120. def log
  121. %i[sign encryption].each do |action|
  122. result = article_preferences[:security][action]
  123. next if result.blank?
  124. if result[:success]
  125. status = 'success'
  126. elsif result[:comment].blank?
  127. # means not performed
  128. next
  129. else
  130. status = 'failed'
  131. end
  132. HttpLog.create(
  133. direction: 'in',
  134. facility: 'S/MIME',
  135. url: "#{@mail[:from]} -> #{@mail[:to]}",
  136. status: status,
  137. ip: nil,
  138. request: {
  139. message_id: @mail[:message_id],
  140. },
  141. response: article_preferences[:security],
  142. method: action,
  143. created_by_id: 1,
  144. updated_by_id: 1,
  145. )
  146. end
  147. end
  148. end