incoming.rb 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class SecureMailing::PGP::Incoming < SecureMailing::Backend::HandlerIncoming
  3. attr_accessor :mime_type, :content_type_parameters
  4. ENCRYPTION_CONTENT_TYPE = 'application/pgp-encrypted'.freeze
  5. ENCRYPTED_PART_CONTENT_TYPE = 'application/octet-stream'.freeze
  6. SIGNATURE_CONTENT_TYPE = 'application/pgp-signature'.freeze
  7. def initialize(mail)
  8. super
  9. @mime_type = mail[:mail_instance].mime_type
  10. @content_type_parameters = mail[:mail_instance].content_type_parameters
  11. end
  12. def type
  13. 'PGP'
  14. end
  15. def encrypted?
  16. content_type.present? && mime_type.eql?('multipart/encrypted') && content_type_parameters[:protocol].eql?(ENCRYPTION_CONTENT_TYPE)
  17. end
  18. def signed?
  19. content_type.present? && mime_type.eql?('multipart/signed') && content_type_parameters[:protocol].eql?(SIGNATURE_CONTENT_TYPE)
  20. end
  21. def decrypt
  22. return if !decryptable?
  23. cipher_part = cipher_part_meta_check
  24. return if cipher_part.nil?
  25. return if !decrypt_body(cipher_part.body.decoded)
  26. set_article_preferences(
  27. operation: :encryption,
  28. success: true,
  29. comment: '',
  30. )
  31. end
  32. def verify_signature
  33. return if !verifiable?
  34. signature_part = signature_part_meta_check
  35. return if signature_part.nil?
  36. verified_result(signature_part.body.decoded)
  37. set_article_preferences(
  38. operation: :sign,
  39. success: true,
  40. comment: __('Good signature'),
  41. )
  42. end
  43. private
  44. def update_instance_meta_information
  45. # Overwrite mime type and content type parameters for decrypted mail.
  46. @mime_type = mail[:mail_instance].mime_type
  47. @content_type_parameters = mail[:mail_instance].content_type_parameters
  48. end
  49. def result_success?(result)
  50. result[:status].success?
  51. end
  52. def result_comment(result)
  53. result[:stdout] || result[:stderr] || ''
  54. end
  55. def mail_part_check(operation)
  56. return true if mail[:mail_instance].parts.length.eql?(2)
  57. set_article_preferences(
  58. operation: operation,
  59. comment: __('This PGP email does not have exactly two body parts for PGP mails as mandated by RFC 3156.'),
  60. )
  61. false
  62. end
  63. def signature_part_meta_check
  64. signature_part = mail[:mail_instance].parts[1]
  65. return signature_part if signature_part.has_content_type? && signature_part.mime_type.eql?(SIGNATURE_CONTENT_TYPE)
  66. set_article_preferences(
  67. operation: :sign,
  68. comment: __('The signature part of this PGP email is missing or has a wrong content type according to RFC 3156.'),
  69. )
  70. nil
  71. end
  72. def version_part_check
  73. version_part = mail[:mail_instance].parts[0]
  74. return true if version_part.mime_type.eql?(ENCRYPTION_CONTENT_TYPE) && version_part.body.include?('Version: 1')
  75. set_article_preferences(
  76. operation: :encryption,
  77. comment: __('The first part of this PGP email is not a valid version part as mandated by RFC 3156.'),
  78. )
  79. false
  80. end
  81. def cipher_part_meta_check
  82. cipher_part = mail[:mail_instance].parts[1]
  83. return cipher_part if cipher_part.has_content_type? && cipher_part.mime_type.eql?(ENCRYPTED_PART_CONTENT_TYPE)
  84. set_article_preferences(
  85. operation: :encryption,
  86. comment: __('The encrypted part of this PGP email has an incorrect MIME type according to RFC 3156.'),
  87. )
  88. nil
  89. end
  90. def verifiable?
  91. return false if !signed?
  92. return false if !mail_part_check(:sign)
  93. return false if sign_keys.blank?
  94. true
  95. end
  96. def verified_result(signature)
  97. SecureMailing::PGP::Tool.new.with_private_keyring do |pgp_tool|
  98. sign_keys.each { |key| pgp_tool.import(key.key) }
  99. begin
  100. pgp_tool.verify(verify_data, signature: signature)
  101. rescue => e
  102. set_article_preferences(
  103. operation: :sign,
  104. comment: e.message,
  105. )
  106. end
  107. end
  108. end
  109. def verify_data
  110. raw_source = mail['raw']
  111. parts = raw_source.split(%r{^--#{mail[:mail_instance].boundary}\s$})[1..-2]
  112. "#{parts[0].strip}\r\n"
  113. end
  114. def decryptable?
  115. return false if !encrypted?
  116. return false if !mail_part_check(:encryption)
  117. return false if !version_part_check
  118. return false if decrypt_keys.blank?
  119. true
  120. end
  121. def decrypt_sign_verify_suppressed?(stderr)
  122. stderr.include?('gpg: signature verification suppressed')
  123. end
  124. def decrypt_body(data)
  125. result = decrypted_result(data)
  126. return false if result.nil?
  127. if !result[:status].success?
  128. set_article_preferences(
  129. operation: :encryption,
  130. comment: result_comment(result)
  131. )
  132. return false
  133. end
  134. decrypted_body = result[:stdout]
  135. # If we're not getting a content header, we need to add a newline, otherwise it's fucked up.
  136. if !decrypted_body.starts_with?(%r{Content-\w+:})
  137. decrypted_body = "\n#{decrypted_body}"
  138. end
  139. parse_decrypted_mail(decrypted_body)
  140. update_instance_meta_information
  141. check_signature(result[:stderr])
  142. true
  143. end
  144. def decrypted_result(data)
  145. SecureMailing::PGP::Tool.new.with_private_keyring do |pgp_tool| # rubocop:disable Metrics/BlockLength
  146. result = nil
  147. decrypt_keys.each do |key|
  148. pgp_tool.import(key.key)
  149. begin
  150. result = pgp_tool.decrypt(data, key.passphrase, skip_verify: true)
  151. check_signature_embedded(data, key, result[:stderr])
  152. break
  153. rescue SecureMailing::PGP::Tool::Error::NoData,
  154. SecureMailing::PGP::Tool::Error::BadPassphrase,
  155. SecureMailing::PGP::Tool::Error::NoPassphrase,
  156. SecureMailing::PGP::Tool::Error::UnknownError => e
  157. # General decryption errors, no further checks needed.
  158. set_article_preferences(
  159. operation: :encryption,
  160. comment: e.message,
  161. )
  162. break
  163. rescue SecureMailing::PGP::Tool::Error::NoSecretKey
  164. next
  165. rescue => e
  166. set_article_preferences(
  167. operation: :sign,
  168. comment: e.message,
  169. )
  170. break
  171. end
  172. end
  173. result
  174. end
  175. end
  176. def check_signature_embedded(data, private_key, stderr)
  177. return if !decrypt_sign_verify_suppressed?(stderr)
  178. sign_comment = __('Good signature')
  179. sign_success = true
  180. SecureMailing::PGP::Tool.new.with_private_keyring do |pgp_tool|
  181. sign_keys.each { |key| pgp_tool.import(key.key) }
  182. pgp_tool.import(private_key.key)
  183. begin
  184. pgp_tool.decrypt(data, private_key.passphrase)
  185. rescue SecureMailing::PGP::Tool::Error::NoPublicKey,
  186. SecureMailing::PGP::Tool::Error::ExpiredKey,
  187. SecureMailing::PGP::Tool::Error::RevokedKey,
  188. SecureMailing::PGP::Tool::Error::ExpiredSignature,
  189. SecureMailing::PGP::Tool::Error::BadSignature,
  190. SecureMailing::PGP::Tool::Error::ExpiredKeySignature,
  191. SecureMailing::PGP::Tool::Error::RevokedKeySignature => e
  192. sign_comment = e.message
  193. sign_success = false
  194. end
  195. end
  196. set_article_preferences(
  197. operation: :sign,
  198. comment: sign_comment,
  199. success: sign_success,
  200. )
  201. end
  202. def check_signature(result_output)
  203. return if !signed?
  204. return if result_output.empty?
  205. sign_success = false
  206. sign_comment = ''
  207. if result_output.include?('gpg: Good signature')
  208. sign_success = true
  209. sign_comment = __('Good signature')
  210. end
  211. set_article_preferences(
  212. operation: :sign,
  213. comment: sign_comment,
  214. success: sign_success,
  215. )
  216. end
  217. def sign_keys
  218. @sign_keys ||= pgp_keys(mail[:mail_instance].from.first, :sign, false)
  219. end
  220. def decrypt_keys
  221. @decrypt_keys ||= begin
  222. keys = []
  223. mail[:mail_instance].to.each { |to| keys += pgp_keys(to, :encryption, true) }
  224. if mail[:mail_instance].cc.present?
  225. mail[:mail_instance].cc.each { |cc| keys += pgp_keys(cc, :encryption, true) }
  226. end
  227. keys
  228. end
  229. end
  230. def pgp_keys(uid, operation, secret)
  231. records = PGPKey.find_all_by_uid(uid, only_valid: false, secret: secret)
  232. if records.empty?
  233. set_article_preferences(
  234. operation: operation,
  235. comment: secret ? __('The private PGP key could not be found.') : __('The public PGP key could not be found.'),
  236. )
  237. return []
  238. end
  239. records
  240. end
  241. end