lib/secure_mailing/smime/outgoing.rb
# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
class SecureMailing::SMIME::Outgoing < SecureMailing::Backend::HandlerOutgoing
def type
'S/MIME'
end
def signed
from = mail.from.first
cert_model = SMIMECertificate.find_by_email_address(from, filter: { key: 'private', usage: :signature, ignore_usable: true }).first
raise "Unable to find ssl private key for '#{from}'" if !cert_model
raise "Expired certificate for #{from} (fingerprint #{cert_model.fingerprint}) with #{cert_model.parsed.not_before} to #{cert_model.parsed.not_after}" if !security[:sign][:allow_expired] && !cert_model.parsed.usable?
private_key = OpenSSL::PKey::RSA.new(cert_model.private_key, cert_model.private_key_secret)
Mail.new(OpenSSL::PKCS7.write_smime(OpenSSL::PKCS7.sign(cert_model.parsed, private_key, mail.encoded, chain(cert_model), OpenSSL::PKCS7::DETACHED)))
rescue => e
log('sign', 'failed', e.message)
raise
end
def chain(cert)
lookup_issuer_hash = cert.parsed.issuer_hash
result = []
loop do
found_cert = SMIMECertificate.find_by(subject_hash: lookup_issuer_hash)
break if found_cert.blank?
subject_hash = found_cert.parsed.subject_hash
lookup_issuer_hash = found_cert.parsed.issuer_hash
result.push(found_cert.parsed)
# we've reached the root CA
break if subject_hash == lookup_issuer_hash
end
result
end
def encrypt(data)
unusable_cert = certificates.detect { |cert| !cert.parsed.usable? }
raise "Unusable certificates for cert with #{unusable_cert.parsed.not_before} to #{unusable_cert.parsed.not_after}" if !security[:encryption][:allow_expired] && unusable_cert.present?
Mail.new(OpenSSL::PKCS7.write_smime(OpenSSL::PKCS7.encrypt(certificates.map(&:parsed), data, cipher)))
rescue => e
log('encryption', 'failed', e.message)
raise
end
def cipher
@cipher ||= OpenSSL::Cipher.new('AES-128-CBC')
end
private
def certificates
certificates = []
%w[to cc].each do |recipient|
addresses = mail.send(recipient)
next if !addresses
certificates += SMIMECertificate.find_for_multiple_email_addresses!(addresses, filter: { key: 'public', ignore_usable: true, usage: :encryption }, blame: true)
end
certificates
end
end