Browse Source

Fixes #5357 - S/MIME verification does not work when the mail does not contain the chain of certificates.

Co-authored-by: Tobias Schäfer <ts@zammad.com>
Rolf Schmidt 5 months ago
parent
commit
d589121815
2 changed files with 62 additions and 3 deletions
  1. 14 3
      lib/secure_mailing/smime/incoming.rb
  2. 48 0
      spec/lib/secure_mailing/smime_spec.rb

+ 14 - 3
lib/secure_mailing/smime/incoming.rb

@@ -101,10 +101,21 @@ class SecureMailing::SMIME::Incoming < SecureMailing::Backend::HandlerIncoming
     subject_hashes = subjects.map { |subject| subject.hash.to_s(16) }
     return if subject_hashes.blank?
 
-    existing_certs = ::SMIMECertificate.where(subject_hash: subject_hashes).sort_by do |certificate|
-      # ensure that we have the same order as the certificates in the mail
+    # Try to find CA/Public key for the sender certificate
+    # 1. In the SMIME store with the mail chain certifiates (reordered)
+    # 2. In the SMIME store with the issuer of the sender certificate
+    # 3. In the SSL store with the issuer of the sender certificate
+    certificates_by_mail_chain = ::SMIMECertificate.where(subject_hash: subject_hashes).sort_by do |certificate|
       subject_hashes.index(certificate.parsed.subject.hash.to_s(16))
-    end
+    end.presence
+    certificate_by_issuer_smime_store = ::SMIMECertificate.where(subject_hash: certificates.first.issuer.hash.to_s(16)).presence
+    certificate_by_issuer_ssl_store   = ::SSLCertificate.where(subject: certificates.first.issuer.to_s, ca: true).filter_map do |cert|
+      ::SMIMECertificate.new(public_key: cert.certificate)
+    rescue
+      next
+    end.presence
+    existing_certs = certificates_by_mail_chain || certificate_by_issuer_smime_store || certificate_by_issuer_ssl_store
+
     return if existing_certs.blank?
 
     if subject_hashes.size > existing_certs.size

+ 48 - 0
spec/lib/secure_mailing/smime_spec.rb

@@ -417,6 +417,54 @@ RSpec.describe SecureMailing::SMIME do
           expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be_nil
         end
 
+        context 'when the mail is not containing the certificate chain but Intermediate CA is present on incoming mails (#5357)' do
+          let(:ca_fixture) { 'IntermediateCA' }
+
+          context 'when present in the SMIME store' do
+            let(:mail) do
+              smime_mail = build_mail
+              mail       = Channel::EmailParser.new.parse(smime_mail.to_s)
+
+              sender_certificate.destroy!
+              create(:smime_certificate, fixture: ca_fixture)
+
+              SecureMailing.incoming(mail)
+
+              mail
+            end
+
+            it 'does verify' do
+              expect(mail[:body]).to include(raw_body)
+              expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true
+              expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(intermediate_ca_certificate_subject)
+              expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
+              expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be_nil
+            end
+          end
+
+          context 'when present in the SSL store' do
+            let(:mail) do
+              smime_mail = build_mail
+              mail       = Channel::EmailParser.new.parse(smime_mail.to_s)
+
+              sender_certificate.destroy!
+              create(:ssl_certificate, fixture: ca_fixture)
+
+              SecureMailing.incoming(mail)
+
+              mail
+            end
+
+            it 'does verify' do
+              expect(mail[:body]).to include(raw_body)
+              expect(mail['x-zammad-article-preferences'][:security][:sign][:success]).to be true
+              expect(mail['x-zammad-article-preferences'][:security][:sign][:comment]).to eq(intermediate_ca_certificate_subject)
+              expect(mail['x-zammad-article-preferences'][:security][:encryption][:success]).to be false
+              expect(mail['x-zammad-article-preferences'][:security][:encryption][:comment]).to be_nil
+            end
+          end
+        end
+
         context 'public key present in signature' do
 
           let(:not_related_fixture)      { 'smime3@example.com' }