From af9bcf96e4ee952a15027ab3c0ee3ee194c1792f Mon Sep 17 00:00:00 2001 From: Rolf Schmidt Date: Wed, 20 Oct 2021 13:04:31 +0200 Subject: [PATCH] Fixes #3727 - Adding private keys allows adding certificates. --- .../integration/smime_controller.rb | 14 +-- app/models/smime_certificate.rb | 22 +++++ spec/fixtures/smime/issue_3727.key | 87 +++++++++++++++++++ spec/system/system/integration/smime_spec.rb | 83 +++++++++++------- 4 files changed, 164 insertions(+), 42 deletions(-) create mode 100644 spec/fixtures/smime/issue_3727.key diff --git a/app/controllers/integration/smime_controller.rb b/app/controllers/integration/smime_controller.rb index ae7a6c621..7204cc842 100644 --- a/app/controllers/integration/smime_controller.rb +++ b/app/controllers/integration/smime_controller.rb @@ -42,9 +42,7 @@ class Integration::SMIMEController < ApplicationController string = params[:file].read.force_encoding('utf-8') end - items = string.scan(%r{.+?-+END(?: TRUSTED)? CERTIFICATE-+}mi).each_with_object([]) do |cert, result| - result << SMIMECertificate.create!(public_key: cert) - end + items = SMIMECertificate.create_certificates(string) render json: { result: 'ok', @@ -73,14 +71,8 @@ class Integration::SMIMEController < ApplicationController raise "Parameter 'data' or 'file' required." if string.blank? - private_key = OpenSSL::PKey.read(string, params[:secret]) - modulus = private_key.public_key.n.to_s(16) - - certificate = SMIMECertificate.find_by(modulus: modulus) - - raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate - - certificate.update!(private_key: string, private_key_secret: params[:secret]) + SMIMECertificate.create_certificates(string) + SMIMECertificate.create_private_keys(string, params[:secret]) render json: { result: 'ok', diff --git a/app/models/smime_certificate.rb b/app/models/smime_certificate.rb index 932e79632..6d62e968d 100644 --- a/app/models/smime_certificate.rb +++ b/app/models/smime_certificate.rb @@ -3,6 +3,28 @@ class SMIMECertificate < ApplicationModel validates :fingerprint, uniqueness: { case_sensitive: true } + def self.parts(raw) + raw.scan(%r{-----BEGIN[^-]+-----.+?-----END[^-]+-----}m) + end + + def self.create_private_keys(raw, secret) + parts(raw).select { |part| part.include?('PRIVATE KEY') }.each do |part| + private_key = OpenSSL::PKey.read(part, secret) + modulus = private_key.public_key.n.to_s(16) + certificate = find_by(modulus: modulus) + + raise Exceptions::UnprocessableEntity, 'Unable for find certificate for this private key.' if !certificate + + certificate.update!(private_key: part, private_key_secret: secret) + end + end + + def self.create_certificates(raw) + parts(raw).select { |part| part.include?('CERTIFICATE') }.each_with_object([]) do |part, result| + result << create!(public_key: part) + end + end + def self.parse(raw) OpenSSL::X509::Certificate.new(raw.gsub(%r{(?:TRUSTED\s)?(CERTIFICATE---)}, '\1')) end diff --git a/spec/fixtures/smime/issue_3727.key b/spec/fixtures/smime/issue_3727.key new file mode 100644 index 000000000..2609a610c --- /dev/null +++ b/spec/fixtures/smime/issue_3727.key @@ -0,0 +1,87 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- diff --git a/spec/system/system/integration/smime_spec.rb b/spec/system/system/integration/smime_spec.rb index 0d1ad99ce..13c5dab8c 100644 --- a/spec/system/system/integration/smime_spec.rb +++ b/spec/system/system/integration/smime_spec.rb @@ -23,44 +23,65 @@ RSpec.describe 'Manage > Integration > S/MIME', type: :system do click 'label[for=setting-switch]' end - it 'enabling and adding of public and private key' do + context 'when doing basic tests' do + it 'enabling and adding of public and private key' do - # add cert - click '.js-addCertificate' - fill_in 'Paste Certificate', with: certificate - click '.js-submit' + # add cert + click '.js-addCertificate' + fill_in 'Paste Certificate', with: certificate + click '.js-submit' - # add private key - click '.js-addPrivateKey' - fill_in 'Paste Private Key', with: private_key - fill_in 'Enter Private Key secret', with: private_key_secret - click '.js-submit' + # add private key + click '.js-addPrivateKey' + fill_in 'Paste Private Key', with: private_key + fill_in 'Enter Private Key secret', with: private_key_secret + click '.js-submit' - # wait for ajax - expect(page).to have_css('td', text: 'Including private key') + # check result + expect(Setting.get('smime_integration')).to be true + expect(SMIMECertificate.last.fingerprint).to be_present + expect(SMIMECertificate.last.raw).to be_present + expect(SMIMECertificate.last.private_key).to be_present + end - # check result - expect(Setting.get('smime_integration')).to be true - expect(SMIMECertificate.last.fingerprint).to be_present - expect(SMIMECertificate.last.raw).to be_present - expect(SMIMECertificate.last.private_key).to be_present + it 'adding of multiple certificates at once' do + multiple_certificates = [ + File.read(Rails.root.join('spec/fixtures/smime/ChainCA.crt')), + File.read(Rails.root.join('spec/fixtures/smime/IntermediateCA.crt')), + File.read(Rails.root.join('spec/fixtures/smime/RootCA.crt')), + ].join + + # add cert + click '.js-addCertificate' + fill_in 'Paste Certificate', with: multiple_certificates + click '.js-submit' + + # wait for ajax + expect(page).to have_text('ChainCA') + expect(page).to have_text('IntermediateCA') + expect(page).to have_text('RootCA') + end end - it 'adding of multiple certificates at once' do - multiple_certificates = [ - File.read(Rails.root.join('spec/fixtures/smime/ChainCA.crt')), - File.read(Rails.root.join('spec/fixtures/smime/IntermediateCA.crt')), - File.read(Rails.root.join('spec/fixtures/smime/RootCA.crt')), - ].join + context 'Adding private keys allows adding certificates #3727' do + let!(:private_key) do + File.read(Rails.root.join('spec/fixtures/smime/issue_3727.key')) + end - # add cert - click '.js-addCertificate' - fill_in 'Paste Certificate', with: multiple_certificates - click '.js-submit' + it 'does add public and private key in one file' do - # wait for ajax - expect(page).to have_text('ChainCA') - expect(page).to have_text('IntermediateCA') - expect(page).to have_text('RootCA') + # add private key + click '.js-addPrivateKey' + fill_in 'Paste Private Key', with: private_key + click '.js-submit' + + # check result + expect(Setting.get('smime_integration')).to be true + expect(SMIMECertificate.last.fingerprint).to eq('db49277070afcfc657bd71d04be4dd7e28f1685a') + expect(SMIMECertificate.last.raw).to include('CERTIFICATE') + expect(SMIMECertificate.last.raw).not_to include('PRIVATE') + expect(SMIMECertificate.last.private_key).to include('PRIVATE') + expect(SMIMECertificate.last.private_key).not_to include('CERTIFICATE') + end end end