feat: Implement P12 certificate generation improvements

- Replace hardcoded P12 password with randomly generated one
- Add p12_password column to certificates table
- Update certificate serializer to include p12 password in response
- Remove deprecated certificate revocation logic
- Add tests for certificate revocation functionality
- Implement async P12 generation via Sidekiq job
- Add job uniqueness to prevent parallel certificate generation

Migration changes:
- Replace p12_password_digest with p12_password column
- Add safety measures for column removal
This commit is contained in:
oleghasjanov 2025-03-19 13:25:43 +02:00
parent 072f4440e2
commit ae96863b88
13 changed files with 157 additions and 64 deletions

View file

@ -1,22 +1,22 @@
-----BEGIN X509 CRL-----
MIIDkjCCAXoCAQEwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAkVFMREwDwYD
MIIDuzCCAaMCAQEwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAkVFMREwDwYD
VQQIDAhIYXJqdW1hYTEQMA4GA1UEBwwHVGFsbGlubjEjMCEGA1UECgwaRWVzdGkg
SW50ZXJuZXRpIFNpaHRhc3V0dXMxGjAYBgNVBAMMEWVwcF9wcm94eSB0ZXN0IGNh
MSAwHgYJKoZIhvcNAQkBFhFoZWxsb0BpbnRlcm5ldC5lZRcNMTkwNzI5MDc1NTA5
WhcNMjkwNzI2MDc1NTA5WjB+MBMCAhACFw0xOTA1MjkwNjM5MTJaMBMCAhADFw0x
OTA1MjkwODQxMDJaMBMCAhAEFw0xOTA1MzExMTI0NTJaMBMCAhAFFw0xOTA1MzEx
MTQyMjJaMBMCAhAGFw0xOTA1MzExMjQzNDlaMBMCAhAHFw0xOTA3MjkwNzU0MzRa
oDAwLjAfBgNVHSMEGDAWgBT9d+ZKc72lPGWzuc+1FZVZCGRoEDALBgNVHRQEBAIC
EAkwDQYJKoZIhvcNAQELBQADggIBAEk9pyZjqyYUdnA0Sv7RyevRUQGKbbf3EXdv
JLDyvI9rpoyuWPkMT6vPsYght0cf/wO7oaEK/uustvFEYQiJss60jI0XuczWypk9
paKu3LhIy6Drm3locY2k0ESrgP9IwNzS5Xr0FiaWRIozbkcawte8M4Nqe8BO5prk
/5sLjv3eFnD7E445tZhu3vmXkD50FT3PLHVBEz4yS6Fx6nTiv+9QUu8NGf+bc6+o
YKPMy6Lh/wGC7p6sZJCOCjfzLAcqWfB2EW6XU8WeQcQCZ0au7zvZjQownCS9CeJV
KVsC4QiUt97FxR2gcEN2GJesywIF11X9o8s1K/Hz3+rrtU1ymoMLeumaRW24z35A
zVsdNwRfSPmt1qHlyaJaFhKG6jw5/nws+/wGFycIjWK0DSORiGCYdKD0cCjKJbNO
2QJnJlNOaCUUj8ULyiFOtZvdadc4JVW42NI/F+AFy/bnBK0uH6CenK5XwX3kEMme
KD8b5reUcVRhQdVJdAABFJlihIg05yENI7hlH1CKfy4vmlBKl+M2mW9cmNO8O6uS
KMH8/wLuLga9gYziNT1RmVNFbnpF0hc6CFtSnlVXXTlU/TrxheH8ykrHQhKEkQj+
3krObDFDCUMKmaGu2nxRYZwLXzUe3wVl1SAxw0eEGyON/N83sLYlcrwWTVzRG3Z7
RqRHPn+h
WhcNMjkwNzI2MDc1NTA5WjCBpjASAgEAFw0yNTAzMTgxMDI5MzdaMBICAQAXDTI1
MDMxODEwMjkzN1owEwICEAIXDTE5MDUyOTA2MzkxMlowEwICEAMXDTE5MDUyOTA4
NDEwMlowEwICEAQXDTE5MDUzMTExMjQ1MlowEwICEAUXDTE5MDUzMTExNDIyMlow
EwICEAYXDTE5MDUzMTEyNDM0OVowEwICEAcXDTE5MDcyOTA3NTQzNFqgMDAuMB8G
A1UdIwQYMBaAFP135kpzvaU8ZbO5z7UVlVkIZGgQMAsGA1UdFAQEAgIQCTANBgkq
hkiG9w0BAQsFAAOCAgEAST2nJmOrJhR2cDRK/tHJ69FRAYptt/cRd28ksPK8j2um
jK5Y+QxPq8+xiCG3Rx//A7uhoQr+66y28URhCImyzrSMjRe5zNbKmT2loq7cuEjL
oOubeWhxjaTQRKuA/0jA3NLlevQWJpZEijNuRxrC17wzg2p7wE7mmuT/mwuO/d4W
cPsTjjm1mG7e+ZeQPnQVPc8sdUETPjJLoXHqdOK/71BS7w0Z/5tzr6hgo8zLouH/
AYLunqxkkI4KN/MsBypZ8HYRbpdTxZ5BxAJnRq7vO9mNCjCcJL0J4lUpWwLhCJS3
3sXFHaBwQ3YYl6zLAgXXVf2jyzUr8fPf6uu1TXKagwt66ZpFbbjPfkDNWx03BF9I
+a3WoeXJoloWEobqPDn+fCz7/AYXJwiNYrQNI5GIYJh0oPRwKMols07ZAmcmU05o
JRSPxQvKIU61m91p1zglVbjY0j8X4AXL9ucErS4foJ6crlfBfeQQyZ4oPxvmt5Rx
VGFB1Ul0AAEUmWKEiDTnIQ0juGUfUIp/Li+aUEqX4zaZb1yY07w7q5Iowfz/Au4u
Br2BjOI1PVGZU0VuekXSFzoIW1KeVVddOVT9OvGF4fzKSsdCEoSRCP7eSs5sMUMJ
QwqZoa7afFFhnAtfNR7fBWXVIDHDR4QbI4383zewtiVyvBZNXNEbdntGpEc+f6E=
-----END X509 CRL-----

View file

@ -0,0 +1,25 @@
require 'test_helper'
class P12GeneratorJobTest < ActiveJob::TestCase
test "ensures only one job runs at a time" do
Sidekiq::Testing.inline!
api_user = users(:api_bestnames)
thread1 = Thread.new do
P12GeneratorJob.perform_later(api_user.id)
end
sleep(2)
thread2 = Thread.new do
P12GeneratorJob.perform_later(api_user.id)
end
thread1.join
thread2.join
ensure
Sidekiq::Testing.fake!
end
end

View file

@ -9,4 +9,36 @@ class CertificateTest < ActiveSupport::TestCase
def test_certificate_sign_returns_false
assert_not @certificate.sign!(password: ENV['ca_key_password']), 'false'
end
# Revocation tests
def test_revoke_with_valid_password
assert @certificate.revoke!(password: ENV['ca_key_password'])
assert @certificate.revoked?
assert_not_nil @certificate.revoked_at
assert_equal Certificate::REVOCATION_REASONS[:unspecified], @certificate.revoked_reason
end
def test_revoke_with_invalid_password
assert_not @certificate.revoke!(password: 'wrong_password')
assert_not @certificate.revoked?
assert_nil @certificate.revoked_at
assert_nil @certificate.revoked_reason
end
def test_revoke_updates_certificate_status
assert_equal Certificate::SIGNED, @certificate.status
@certificate.revoke!(password: ENV['ca_key_password'])
assert_equal Certificate::REVOKED, @certificate.status
end
def test_revokable_for_different_interfaces
@certificate.update!(interface: Certificate::REGISTRAR)
assert @certificate.revokable?
@certificate.update!(interface: Certificate::API)
assert_not @certificate.revokable?
@certificate.update!(interface: Certificate::REGISTRAR, crt: nil)
assert_not @certificate.revokable?
end
end