diff options
author | Diego Louzán <diego.louzan.ext@siemens.com> | 2019-07-10 22:40:28 +0300 |
---|---|---|
committer | Diego Louzán <diego.louzan.ext@siemens.com> | 2019-08-20 17:13:32 +0300 |
commit | 0dcb9d21efc1db97765d82ee39a0f0905ba945ba (patch) | |
tree | 48b0fa42bbe0186e28758ba496f45ef11972aed6 /spec/lib/gitlab/email | |
parent | d8966abd20c860d2f30141f3647f2b81f70b683d (diff) |
feat: SMIME signed notification emails
- Add mail interceptor the signs outgoing email with SMIME
- Add lib and helpers to work with SMIME data
- New configuration params for setting up SMIME key and cert files
Diffstat (limited to 'spec/lib/gitlab/email')
4 files changed, 155 insertions, 3 deletions
diff --git a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb index 0c58cf088cc..c8ed12523d0 100644 --- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb +++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb @@ -13,9 +13,6 @@ describe Gitlab::Email::Hook::DisableEmailInterceptor do end after do - # Removing interceptor from the list because unregister_interceptor is - # implemented in later version of mail gem - # See: https://github.com/mikel/mail/pull/705 Mail.unregister_interceptor(described_class) end diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb new file mode 100644 index 00000000000..35aa663b0a5 --- /dev/null +++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe Gitlab::Email::Hook::SmimeSignatureInterceptor do + include SmimeHelper + + # cert generation is an expensive operation and they are used read-only, + # so we share them as instance variables in all tests + before :context do + @root_ca = generate_root + @cert = generate_cert(root_ca: @root_ca) + end + + let(:root_certificate) do + Gitlab::Email::Smime::Certificate.new(@root_ca[:key], @root_ca[:cert]) + end + + let(:certificate) do + Gitlab::Email::Smime::Certificate.new(@cert[:key], @cert[:cert]) + end + + let(:mail) do + ActionMailer::Base.mail(to: 'test@example.com', from: 'info@example.com', body: 'signed hello') + end + + before do + allow(Gitlab::Email::Smime::Certificate).to receive_messages(from_files: certificate) + + Mail.register_interceptor(described_class) + mail.deliver_now + end + + after do + Mail.unregister_interceptor(described_class) + end + + it 'signs the email appropriately with SMIME' do + expect(mail.header['To'].value).to eq('test@example.com') + expect(mail.header['From'].value).to eq('info@example.com') + expect(mail.header['Content-Type'].value).to match('multipart/signed').and match('protocol="application/x-pkcs7-signature"') + + # verify signature and obtain pkcs7 encoded content + p7enc = Gitlab::Email::Smime::Signer.verify_signature( + cert: certificate.cert, + ca_cert: root_certificate.cert, + signed_data: mail.encoded) + + # envelope in a Mail object and obtain the body + decoded_mail = Mail.new(p7enc.data) + + expect(decoded_mail.body.encoded).to eq('signed hello') + end +end diff --git a/spec/lib/gitlab/email/smime/certificate_spec.rb b/spec/lib/gitlab/email/smime/certificate_spec.rb new file mode 100644 index 00000000000..90b27602413 --- /dev/null +++ b/spec/lib/gitlab/email/smime/certificate_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Email::Smime::Certificate do + include SmimeHelper + + # cert generation is an expensive operation and they are used read-only, + # so we share them as instance variables in all tests + before :context do + @root_ca = generate_root + @cert = generate_cert(root_ca: @root_ca) + end + + describe 'testing environment setup' do + describe 'generate_root' do + subject { @root_ca } + + it 'generates a root CA that expires a long way in the future' do + expect(subject[:cert].not_after).to be > 999.years.from_now + end + end + + describe 'generate_cert' do + subject { @cert } + + it 'generates a cert properly signed by the root CA' do + expect(subject[:cert].issuer).to eq(@root_ca[:cert].subject) + end + + it 'generates a cert that expires soon' do + expect(subject[:cert].not_after).to be < 60.minutes.from_now + end + + it 'generates a cert intended for email signing' do + expect(subject[:cert].extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection'))) + end + + context 'passing in INFINITE_EXPIRY' do + subject { generate_cert(root_ca: @root_ca, expires_in: SmimeHelper::INFINITE_EXPIRY) } + + it 'generates a cert that expires a long way in the future' do + expect(subject[:cert].not_after).to be > 999.years.from_now + end + end + end + end + + describe '.from_strings' do + it 'parses correctly a certificate and key' do + parsed_cert = described_class.from_strings(@cert[:key].to_s, @cert[:cert].to_pem) + + common_cert_tests(parsed_cert, @cert, @root_ca) + end + end + + describe '.from_files' do + it 'parses correctly a certificate and key' do + allow(File).to receive(:read).with('a_key').and_return(@cert[:key].to_s) + allow(File).to receive(:read).with('a_cert').and_return(@cert[:cert].to_pem) + + parsed_cert = described_class.from_files('a_key', 'a_cert') + + common_cert_tests(parsed_cert, @cert, @root_ca) + end + end + + def common_cert_tests(parsed_cert, cert, root_ca) + expect(parsed_cert.cert).to be_a(OpenSSL::X509::Certificate) + expect(parsed_cert.cert.subject).to eq(cert[:cert].subject) + expect(parsed_cert.cert.issuer).to eq(root_ca[:cert].subject) + expect(parsed_cert.cert.not_before).to eq(cert[:cert].not_before) + expect(parsed_cert.cert.not_after).to eq(cert[:cert].not_after) + expect(parsed_cert.cert.extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection'))) + expect(parsed_cert.key).to be_a(OpenSSL::PKey::RSA) + end +end diff --git a/spec/lib/gitlab/email/smime/signer_spec.rb b/spec/lib/gitlab/email/smime/signer_spec.rb new file mode 100644 index 00000000000..56048b7148c --- /dev/null +++ b/spec/lib/gitlab/email/smime/signer_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Email::Smime::Signer do + include SmimeHelper + + it 'signs data appropriately with SMIME' do + root_certificate = generate_root + certificate = generate_cert(root_ca: root_certificate) + + signed_content = described_class.sign( + cert: certificate[:cert], + key: certificate[:key], + data: 'signed content') + expect(signed_content).not_to be_nil + + p7enc = described_class.verify_signature( + cert: certificate[:cert], + ca_cert: root_certificate[:cert], + signed_data: signed_content) + + expect(p7enc).not_to be_nil + expect(p7enc.data).to eq('signed content') + end +end |