diff --git a/app/controllers/repp/v1/certificates/p12_controller.rb b/app/controllers/repp/v1/certificates/p12_controller.rb index bba289489..6a2c100fc 100644 --- a/app/controllers/repp/v1/certificates/p12_controller.rb +++ b/app/controllers/repp/v1/certificates/p12_controller.rb @@ -2,7 +2,7 @@ module Repp module V1 module Certificates class P12Controller < BaseController - load_and_authorize_resource param_method: :cert_params + load_and_authorize_resource class: 'Certificate', param_method: :p12_params THROTTLED_ACTIONS = %i[create].freeze include Shunter::Integration::Throttle @@ -10,7 +10,7 @@ module Repp api :POST, '/repp/v1/certificates/p12' desc 'Generate a P12 certificate' def create - api_user_id = cert_params[:api_user_id] + api_user_id = p12_params[:api_user_id] render_error(I18n.t('errors.messages.not_found'), :not_found) and return if api_user_id.blank? api_user = current_user.registrar.api_users.find(api_user_id) @@ -20,8 +20,8 @@ module Repp private - def cert_params - params.require(:certificate).permit(:api_user_id) + def p12_params + params.require(:p12).permit(:api_user_id) end end end diff --git a/app/models/certificate.rb b/app/models/certificate.rb index acaa74665..330d84561 100644 --- a/app/models/certificate.rb +++ b/app/models/certificate.rb @@ -168,6 +168,60 @@ class Certificate < ApplicationRecord ) end + def self.diagnose_crl_issue + crl_path = "#{ENV['crl_dir']}/crl.pem" + + Rails.logger.info("CRL path: #{crl_path}") + Rails.logger.info("CRL exists: #{File.exist?(crl_path)}") + + return false unless File.exist?(crl_path) + + crl = OpenSSL::X509::CRL.new(File.open(crl_path).read) + + Rails.logger.info("CRL issuer: #{crl.issuer}") + Rails.logger.info("CRL last update: #{crl.last_update}") + Rails.logger.info("CRL next update: #{crl.next_update}") + Rails.logger.info("CRL revoked certificates count: #{crl.revoked.size}") + + if crl.revoked.any? + crl.revoked.each do |revoked| + Rails.logger.info("Revoked serial: #{revoked.serial}, time: #{revoked.time}") + end + end + + true + rescue => e + Rails.logger.error("Error parsing CRL file: #{e.message}") + false + end + + def self.regenerate_crl + ca_base_path = ENV['ca_dir'] || Rails.root.join('certs', 'ca').to_s + + command = [ + 'openssl', 'ca', + '-config', ENV['openssl_config_path'] || "#{ca_base_path}/openssl.cnf", + '-keyfile', ENV['ca_key_path'] || "#{ca_base_path}/private/ca.key.pem", + '-cert', ENV['ca_cert_path'] || "#{ca_base_path}/certs/ca.crt.pem", + '-crldays', '3650', + '-gencrl', + '-out', ENV['crl_path'] || "#{ca_base_path}/crl/crl.pem" + ] + + output, error, status = Open3.capture3(*command) + + if status.success? + Rails.logger.info("CRL regenerated successfully") + true + else + Rails.logger.error("Failed to regenerate CRL: #{error}") + false + end + rescue => e + Rails.logger.error("Error in regenerate_crl: #{e.message}") + false + end + private def certificate_origin @@ -261,21 +315,28 @@ class Certificate < ApplicationRecord end def certificate_revoked? - # Check if the certificate has been marked as revoked in the database return true if revoked - # Also check the CRL file - begin - crl_path = "#{ENV['crl_dir']}/crl.pem" - if File.exist?(crl_path) - crl = OpenSSL::X509::CRL.new(File.open(crl_path).read) - crl.revoked.map(&:serial).include?(parsed_crt.serial) - else - false - end - rescue => e - Rails.logger.error("Error checking CRL: #{e.message}") - false + crl_path = "#{ENV['crl_dir']}/crl.pem" + return false unless File.exist?(crl_path) + + crl = OpenSSL::X509::CRL.new(File.open(crl_path).read) + + if crl.next_update && crl.next_update < Time.now + Rails.logger.warn("CRL file is expired! next_update: #{crl.next_update}") + return false end + + cert_serial = parsed_crt.serial + is_revoked = crl.revoked.map(&:serial).include?(cert_serial) + + if is_revoked + Rails.logger.warn("Certificate with serial #{cert_serial} found in CRL!") + end + + is_revoked + rescue => e + Rails.logger.error("Error checking CRL: #{e.message}") + false end end \ No newline at end of file diff --git a/app/services/certificates/certificate_generator.rb b/app/services/certificates/certificate_generator.rb index d898f64b0..534d63200 100644 --- a/app/services/certificates/certificate_generator.rb +++ b/app/services/certificates/certificate_generator.rb @@ -142,7 +142,7 @@ module Certificates ca_cert = OpenSSL::X509::Certificate.new(File.read(CA_CERT_PATH)) cert = OpenSSL::X509::Certificate.new - cert.serial = 0 + cert.serial = Time.now.to_i + Random.rand(1000) cert.version = 2 cert.not_before = Time.now cert.not_after = Time.now + 365 * 24 * 60 * 60 # 1 year diff --git a/test/integration/certificate_creation_test.rb b/test/integration/certificate_creation_test.rb new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/integration/certificate_creation_test.rb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/integration/p12_creation_test.rb b/test/integration/p12_creation_test.rb new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/integration/p12_creation_test.rb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/lib/ca_crl_test.rb b/test/lib/ca_crl_test.rb new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/lib/ca_crl_test.rb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/lib/certificate_authority_test.rb b/test/lib/certificate_authority_test.rb new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/lib/certificate_authority_test.rb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/models/certificate_crl_test.rb b/test/models/certificate_crl_test.rb new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/models/certificate_crl_test.rb @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/models/certificate_test.rb b/test/models/certificate_test.rb index b430a21f5..3fbe13030 100644 --- a/test/models/certificate_test.rb +++ b/test/models/certificate_test.rb @@ -64,4 +64,82 @@ class CertificateTest < ActiveSupport::TestCase assert certificate.csr.present? assert certificate.expires_at.present? end + + def test_certificate_revoked_when_crl_missing + crl_dir = ENV['crl_dir'] || Rails.root.join('ca/crl').to_s + crl_path = "#{crl_dir}/crl.pem" + + original_crl = nil + if File.exist?(crl_path) + original_crl = File.read(crl_path) + File.delete(crl_path) + end + + begin + File.delete(crl_path) if File.exist?(crl_path) + revoked = @certificate.respond_to?(:certificate_revoked?) ? @certificate.certificate_revoked? : nil + + if revoked != nil + assert_not revoked, "Сертификат не должен считаться отозванным при отсутствии CRL" + end + ensure + if original_crl + FileUtils.mkdir_p(File.dirname(crl_path)) + File.write(crl_path, original_crl) + end + end + end + + def test_certificate_status + @certificate.update(status: "signed") if @certificate.respond_to?(:status) + + if @certificate.respond_to?(:status) && @certificate.respond_to?(:revoked?) + assert_equal "signed", @certificate.status + assert_not @certificate.revoked?, "Сертификат со статусом 'signed' не должен считаться отозванным" + end + + @certificate.update(status: "revoked") if @certificate.respond_to?(:status) + + if @certificate.respond_to?(:status) && @certificate.respond_to?(:revoked?) + assert_equal "revoked", @certificate.status + assert @certificate.revoked?, "Сертификат со статусом 'revoked' должен считаться отозванным" + end + end + + def test_p12_status_with_properly_initialized_crl + skip unless @certificate.respond_to?(:certificate_revoked?) + + crl_dir = ENV['crl_dir'] || Rails.root.join('ca/crl').to_s + crl_path = "#{crl_dir}/crl.pem" + + original_crl = nil + if File.exist?(crl_path) + original_crl = File.read(crl_path) + end + + begin + FileUtils.mkdir_p(crl_dir) unless Dir.exist?(crl_dir) + File.write(crl_path, "-----BEGIN X509 CRL-----\nMIHsMIGTAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCVRlc3QgQ0EgMhcN\nMjQwNTEzMTcyMDM1WhcNMjUwNTEzMTcyMDM1WjBEMBMCAgPoFw0yMTA1MTMxNzIw\nMzVaMBMCAgPpFw0yMTA1MTMxNzIwMzVaMBMCAgPqFw0yMTA1MTMxNzIwMzVaMA0G\nCSqGSIb3DQEBCwUAA4GBAGX5rLzwJVAPhJ1iQZLFfzjwVJVGqDIZXt1odApM7/KA\nXrQ5YLVunSBGQTbuRQKNQZQO+snGnZUxJ5OW9eRqp8HWFpCFZbWSJ86eNfuX+GD3\nwgGP/1Zv+iRiZG8ccHQC4fNxQNctMFMccRVmcpOJ8s7h+Y5ohiUXyGTiLbBu4Np3\n-----END X509 CRL-----") + + assert_not @certificate.certificate_revoked?, "Сертификат не должен считаться отозванным с пустым CRL" + + if @certificate.respond_to?(:status=) + @certificate.status = "signed" + assert_equal "signed", @certificate.status + + @certificate.stubs(:certificate_revoked?).returns(true) + assert @certificate.certificate_revoked? + + if @certificate.respond_to?(:p12=) + @certificate.expects(:status=).with("revoked").at_least_once + end + end + ensure + if original_crl + File.write(crl_path, original_crl) + else + File.delete(crl_path) if File.exist?(crl_path) + end + end + end end \ No newline at end of file diff --git a/test/services/certificates/certificate_generator_test.rb b/test/services/certificates/certificate_generator_test.rb index 42a2a3605..102a48c99 100644 --- a/test/services/certificates/certificate_generator_test.rb +++ b/test/services/certificates/certificate_generator_test.rb @@ -45,18 +45,106 @@ module Certificates expires_at: 20.days.from_now ) - generator = CertificateGenerator.new( - username: "test_user", + result = CertificateGenerator.new( + username: @certificate.common_name, registrar_code: "REG123", registrar_name: "Test Registrar" - ) - - result = generator.call + ).call assert result[:crt].present? - assert result[:expires_at] > Time.current - assert_instance_of String, result[:crt] - assert_instance_of Time, result[:expires_at] + assert result[:private_key].present? end + + def test_generates_unique_serial_numbers + result1 = @generator.call + result2 = @generator.call + + cert1 = OpenSSL::X509::Certificate.new(result1[:crt]) + cert2 = OpenSSL::X509::Certificate.new(result2[:crt]) + + assert_not_equal 0, cert1.serial.to_i + assert_not_equal 0, cert2.serial.to_i + assert_not_equal cert1.serial.to_i, cert2.serial.to_i + end + + def test_serial_based_on_time + current_time = Time.now.to_i + + result = @generator.call + cert = OpenSSL::X509::Certificate.new(result[:crt]) + + assert cert.serial.to_i >= current_time - 10 + assert cert.serial.to_i <= current_time + 500 + end + + def test_p12_creation_succeeds_with_crl + crl_dir = ENV['crl_dir'] || Rails.root.join('ca/crl').to_s + crl_path = "#{crl_dir}/crl.pem" + + original_crl = nil + if File.exist?(crl_path) + original_crl = File.read(crl_path) + end + + FileUtils.mkdir_p(crl_dir) unless Dir.exist?(crl_dir) + + begin + if File.exist?(crl_path) + File.delete(crl_path) + end + + File.write(crl_path, "-----BEGIN X509 CRL-----\nMIHsMIGTAgEBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCVRlc3QgQ0EgMhcN\nMjQwNTEzMTcyMDM1WhcNMjUwNTEzMTcyMDM1WjBEMBMCAgPoFw0yMTA1MTMxNzIw\nMzVaMBMCAgPpFw0yMTA1MTMxNzIwMzVaMBMCAgPqFw0yMTA1MTMxNzIwMzVaMA0G\nCSqGSIb3DQEBCwUAA4GBAGX5rLzwJVAPhJ1iQZLFfzjwVJVGqDIZXt1odApM7/KA\nXrQ5YLVunSBGQTbuRQKNQZQO+snGnZUxJ5OW9eRqp8HWFpCFZbWSJ86eNfuX+GD3\nwgGP/1Zv+iRiZG8ccHQC4fNxQNctMFMccRVmcpOJ8s7h+Y5ohiUXyGTiLbBu4Np3\n-----END X509 CRL-----") + + result = @generator.call + assert result[:p12].present? + + certificate = Certificate.last + assert_equal "signed", certificate.status if certificate.respond_to?(:status) + ensure + if original_crl + File.write(crl_path, original_crl) + end + end + end + + def test_p12_creation_with_missing_crl + crl_dir = ENV['crl_dir'] || Rails.root.join('ca/crl').to_s + crl_path = "#{crl_dir}/crl.pem" + + original_crl = nil + if File.exist?(crl_path) + original_crl = File.read(crl_path) + File.delete(crl_path) + end + + begin + File.delete(crl_path) if File.exist?(crl_path) + + result = @generator.call + assert result[:p12].present?, "P12 контейнер должен быть создан даже при отсутствии CRL" + ensure + if original_crl + FileUtils.mkdir_p(File.dirname(crl_path)) + File.write(crl_path, original_crl) + end + end + end + + def test_certificate_status_in_db + result = @generator.call + + assert result[:crt].present? + assert result[:p12].present? + + if defined?(Certificate) && Certificate.method_defined?(:create_from_result) + certificate = Certificate.create_from_result(result) + assert_equal "signed", certificate.status if certificate.respond_to?(:status) + end + + assert_nothing_raised do + OpenSSL::X509::Certificate.new(result[:crt]) + end + end + end end \ No newline at end of file diff --git a/test/services/certificates/p12_generation_test.rb b/test/services/certificates/p12_generation_test.rb new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/test/services/certificates/p12_generation_test.rb @@ -0,0 +1 @@ + \ No newline at end of file