smime_certificate.rb 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
  2. class SMIMECertificate < ApplicationModel
  3. validates :fingerprint, uniqueness: true
  4. def self.parse(raw)
  5. OpenSSL::X509::Certificate.new(raw.gsub!(/(?:TRUSTED\s)?(CERTIFICATE---)/, '\1'))
  6. end
  7. # Search for the certificate of the given sender email address
  8. #
  9. # @example
  10. # certificate = SMIMECertificates.for_sender_email_address('some1@example.com')
  11. # # => #<SMIMECertificate:0x00007fdd4e27eec0...
  12. #
  13. # @return [SMIMECertificate, nil] The found certificate record or nil
  14. def self.for_sender_email_address(address)
  15. downcased_address = address.downcase
  16. where.not(private_key: nil).find_each.detect do |certificate|
  17. certificate.email_addresses.include?(downcased_address)
  18. end
  19. end
  20. # Search for certificates of the given recipients email addresses
  21. #
  22. # @example
  23. # certificates = SMIMECertificates.for_recipipent_email_addresses!(['some1@example.com', 'some2@example.com'])
  24. # # => [#<SMIMECertificate:0x00007fdd4e27eec0...
  25. #
  26. # @raise [ActiveRecord::RecordNotFound] if there are recipients for which no certificate could be found
  27. #
  28. # @return [Array<SMIMECertificate>] The found certificate records
  29. def self.for_recipipent_email_addresses!(addresses)
  30. certificates = []
  31. remaining_addresses = addresses.map(&:downcase)
  32. find_each do |certificate|
  33. # intersection of both lists
  34. cerfiticate_for = certificate.email_addresses & remaining_addresses
  35. next if cerfiticate_for.blank?
  36. certificates.push(certificate)
  37. # subtract found recipient(s)
  38. remaining_addresses -= cerfiticate_for
  39. # end loop if no addresses are remaining
  40. break if remaining_addresses.blank?
  41. end
  42. return certificates if remaining_addresses.blank?
  43. raise ActiveRecord::RecordNotFound, "Can't find S/MIME encryption certificates for: #{remaining_addresses.join(', ')}"
  44. end
  45. def public_key=(string)
  46. cert = self.class.parse(string)
  47. self.subject = cert.subject
  48. self.doc_hash = cert.subject.hash.to_s(16)
  49. self.fingerprint = OpenSSL::Digest.new('SHA1', cert.to_der).to_s
  50. self.modulus = cert.public_key.n.to_s(16)
  51. self.not_before_at = cert.not_before
  52. self.not_after_at = cert.not_after
  53. self.raw = cert.to_s
  54. end
  55. def parsed
  56. @parsed ||= self.class.parse(raw)
  57. end
  58. def email_addresses
  59. @email_addresses ||= begin
  60. subject_alt_name = parsed.extensions.detect { |extension| extension.oid == 'subjectAltName' }
  61. if subject_alt_name.blank?
  62. warning = <<~TEXT.squish
  63. SMIMECertificate with ID #{id} has no subjectAltName
  64. extension and therefore no email addresses assigned.
  65. This makes it useless in terms of S/MIME. Please check.
  66. TEXT
  67. Rails.logger.warn warning
  68. return []
  69. end
  70. # ["IP Address:192.168.7.23", "IP Address:192.168.7.42", "email:jd@example.com", "email:John.Doe@example.com", "dirName:dir_sect"]
  71. entries = subject_alt_name.value.split(/,\s?/)
  72. # ["email:jd@example.com", "email:John.Doe@example.com"]
  73. email_address_entries = entries.select { |entry| entry.start_with?('email') }
  74. # ["jd@example.com", "John.Doe@example.com"]
  75. email_address_entries.map! { |entry| entry.split(':')[1] }
  76. # ["jd@example.com", "john.doe@example.com"]
  77. email_address_entries.map!(&:downcase)
  78. end
  79. end
  80. def expired?
  81. !Time.zone.now.between?(not_before_at, not_after_at)
  82. end
  83. end