outgoing.rb 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. class SecureMailing::SMIME::Outgoing < SecureMailing::Backend::Handler
  3. def initialize(mail, security)
  4. super()
  5. @mail = mail
  6. @security = security
  7. end
  8. def process
  9. return if !process?
  10. @mail = workaround_mail_bit_encoding_issue(@mail)
  11. if @security[:sign][:success] && @security[:encryption][:success]
  12. processed = encrypt(signed)
  13. log('sign', 'success')
  14. log('encryption', 'success')
  15. elsif @security[:sign][:success]
  16. processed = Mail.new(signed)
  17. log('sign', 'success')
  18. elsif @security[:encryption][:success]
  19. processed = encrypt(@mail.encoded)
  20. log('encryption', 'success')
  21. end
  22. overwrite_mail(processed)
  23. end
  24. def process?
  25. return false if @security.blank?
  26. return false if @security[:type] != 'S/MIME'
  27. @security[:sign][:success] || @security[:encryption][:success]
  28. end
  29. # S/MIME signing fails because of message encoding #3147
  30. # workaround for https://github.com/mikel/mail/issues/1190
  31. def workaround_mail_bit_encoding_issue(mail)
  32. # change 7bit/8bit encoding to binary so that
  33. # base64 will be used to encode the content
  34. if mail.body.encoding.include?('bit')
  35. mail.body.encoding = :binary
  36. end
  37. # go into recursion for nested parts
  38. mail.parts&.each do |part|
  39. workaround_mail_bit_encoding_issue(part)
  40. end
  41. mail
  42. end
  43. def overwrite_mail(processed)
  44. @mail.body = nil
  45. @mail.body = processed.body.encoded
  46. @mail.content_disposition = processed.content_disposition
  47. @mail.content_transfer_encoding = processed.content_transfer_encoding
  48. @mail.content_type = processed.content_type
  49. end
  50. def signed
  51. from = @mail.from.first
  52. cert_model = SMIMECertificate.for_sender_email_address(from)
  53. raise "Unable to find ssl private key for '#{from}'" if !cert_model
  54. raise "Expired certificate for #{from} (fingerprint #{cert_model.fingerprint}) with #{cert_model.not_before_at} to #{cert_model.not_after_at}" if !@security[:sign][:allow_expired] && cert_model.expired?
  55. private_key = OpenSSL::PKey::RSA.new(cert_model.private_key, cert_model.private_key_secret)
  56. OpenSSL::PKCS7.write_smime(OpenSSL::PKCS7.sign(cert_model.parsed, private_key, @mail.encoded, chain(cert_model), OpenSSL::PKCS7::DETACHED))
  57. rescue => e
  58. log('sign', 'failed', e.message)
  59. raise
  60. end
  61. def chain(cert)
  62. lookup_issuer = cert.parsed.issuer.to_s
  63. result = []
  64. loop do
  65. found_cert = SMIMECertificate.find_by(subject: lookup_issuer)
  66. break if found_cert.blank?
  67. subject = found_cert.parsed.subject.to_s
  68. lookup_issuer = found_cert.parsed.issuer.to_s
  69. result.push(found_cert.parsed)
  70. # we've reached the root CA
  71. break if subject == lookup_issuer
  72. end
  73. result
  74. end
  75. def encrypt(data)
  76. certificates = SMIMECertificate.for_recipipent_email_addresses!(@mail.to)
  77. expired_cert = certificates.detect(&:expired?)
  78. raise "Expired certificates for cert with #{expired_cert.not_before_at} to #{expired_cert.not_after_at}" if !@security[:encryption][:allow_expired] && expired_cert.present?
  79. Mail.new(OpenSSL::PKCS7.write_smime(OpenSSL::PKCS7.encrypt(certificates.map(&:parsed), data, cipher)))
  80. rescue => e
  81. log('encryption', 'failed', e.message)
  82. raise
  83. end
  84. def cipher
  85. @cipher ||= OpenSSL::Cipher.new('AES-128-CBC')
  86. end
  87. def log(action, status, error = nil)
  88. HttpLog.create(
  89. direction: 'out',
  90. facility: 'S/MIME',
  91. url: "#{@mail[:from]} -> #{@mail[:to]}",
  92. status: status,
  93. ip: nil,
  94. request: @security,
  95. response: { error: error },
  96. method: action,
  97. created_by_id: 1,
  98. updated_by_id: 1,
  99. )
  100. end
  101. end