feat: improve certificate download extensions

Update certificate download functionality to use appropriate file extensions:
- Use .p12 extension for PKCS#12 files
- Keep .pem extension for PEM-encoded files (CSR, CRT, private key)

This change ensures that downloaded certificate files have the correct extension based on their format, making it easier for users to identify and use the files correctly.
This commit is contained in:
oleghasjanov 2025-02-19 16:07:50 +02:00
parent 51035d1ddf
commit 5355397025
15 changed files with 281 additions and 262 deletions

9
test/fixtures/files/user.crt vendored Normal file
View file

@ -0,0 +1,9 @@
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUBgtGh4Pw8Luqq/HG4tqG3oIzfHIwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMTkxMjAwMDBaFw0yNTAy
MTkxMjAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDUVURLKdmhmEht7yz3MeQQtn9kMIaIzZDwggZvUg6J
5PlTabEixVfPzlRJixJBj37hh0Ree6mr19KECtPymy1L9U3oGfF18CJhdzc=
-----END CERTIFICATE-----

View file

@ -1,7 +0,0 @@
one:
user: api_bestnames
private_key: "encrypted_private_key_data"
csr: "dummy_csr_data"
certificate: "dummy_certificate_data"
status: "active"
expires_at: <%= 60.days.from_now %>

View file

@ -3,7 +3,22 @@ require 'test_helper'
class CertificateTest < ActiveSupport::TestCase
setup do
@certificate = certificates(:api)
@certificate.update!(csr: "-----BEGIN CERTIFICATE REQUEST-----\nMIICszCCAZsCAQAwbjELMAkGA1UEBhMCRUUxFDASBgNVBAMMC2ZyZXNoYm94LmVl\nMRAwDgYDVQQHDAdUYWxsaW5uMREwDwYDVQQKDAhGcmVzaGJveDERMA8GA1UECAwI\nSGFyanVtYWExETAPBgNVBAsMCEZyZXNoYm94MIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA1VVESynZoZhIbe8s9zHkELZ/ZDCGiM2Q8IIGb1IOieT5U2mx\nIsVXz85USYsSQY9+4YdEXnupq9fShArT8pstS/VN6BnxdfAiYXc3UWWAuaYAdNGJ\nDr5Jf6uMt1wVnCgoDL7eJq9tWMwARC/viT81o92fgqHFHW0wEolfCmnpik9o0ACD\nFiWZ9IBIevmFqXtq25v9CY2cT9+eZW127WtJmOY/PKJhzh0QaEYHqXTHWOLZWpnp\nHH4elyJ2CrFulOZbHPkPNB9Nf4XQjzk1ffoH6e5IVys2VV5xwcTkF0jY5XTROVxX\nlR2FWqic8Q2pIhSks48+J6o1GtXGnTxv94lSDwIDAQABoAAwDQYJKoZIhvcNAQEL\nBQADggEBAEFcYmQvcAC8773eRTWBJJNoA4kRgoXDMYiiEHih5iJPVSxfidRwYDTF\nsP+ttNTUg3JocFHY75kuM9T2USh+gu/trRF0o4WWa+AbK3JbbdjdT1xOMn7XtfUU\nZ/f1XCS9YdHQFCA6nk4Z+TLWwYsgk7n490AQOiB213fa1UIe83qIfw/3GRqRUZ7U\nwIWEGsHED5WT69GyxjyKHcqGoV7uFnqFN0sQVKVTy/NFRVQvtBUspCbsOirdDRie\nAB2KbGHL+t1QrRF10szwCJDyk5aYlVhxvdI8zn010nrxHkiyQpDFFldDMLJl10BW\n2w9PGO061z+tntdRcKQGuEpnIr9U5Vs=\n-----END CERTIFICATE REQUEST-----\n")
@valid_crt = <<~CRT
-----BEGIN CERTIFICATE-----
MIIDazCCAlOgAwIBAgIUBgtGh4Pw8Luqq/HG4tqG3oIzfHIwDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMTkxMjAwMDBaFw0yNTAy
MTkxMjAwMDBaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDUVURLKdmhmEht7yz3MeQQtn9kMIaIzZDwggZvUg6J
5PlTabEixVfPzlRJixJBj37hh0Ree6mr19KECtPymy1L9U3oGfF18CJhdzc=
-----END CERTIFICATE-----
CRT
@certificate.update!(
csr: "-----BEGIN CERTIFICATE REQUEST-----\nMIICszCCAZsCAQAwbjELMAkGA1UEBhMCRUUxFDASBgNVBAMMC2ZyZXNoYm94LmVl\nMRAwDgYDVQQHDAdUYWxsaW5uMREwDwYDVQQKDAhGcmVzaGJveDERMA8GA1UECAwI\nSGFyanVtYWExETAPBgNVBAsMCEZyZXNoYm94MIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA1VVESynZoZhIbe8s9zHkELZ/ZDCGiM2Q8IIGb1IOieT5U2mx\nIsVXz85USYsSQY9+4YdEXnupq9fShArT8pstS/VN6BnxdfAiYXc3UWWAuaYAdNGJ\nDr5Jf6uMt1wVnCgoDL7eJq9tWMwARC/viT81o92fgqHFHW0wEolfCmnpik9o0ACD\nFiWZ9IBIevmFqXtq25v9CY2cT9+eZW127WtJmOY/PKJhzh0QaEYHqXTHWOLZWpnp\nHH4elyJ2CrFulOZbHPkPNB9Nf4XQjzk1ffoH6e5IVys2VV5xwcTkF0jY5XTROVxX\nlR2FWqic8Q2pIhSks48+J6o1GtXGnTxv94lSDwIDAQABoAAwDQYJKoZIhvcNAQEL\nBQADggEBAEFcYmQvcAC8773eRTWBJJNoA4kRgoXDMYiiEHih5iJPVSxfidRwYDTF\nsP+ttNTUg3JocFHY75kuM9T2USh+gu/trRF0o4WWa+AbK3JbbdjdT1xOMn7XtfUU\nZ/f1XCS9YdHQFCA6nk4Z+TLWwYsgk7n490AQOiB213fa1UIe83qIfw/3GRqRUZ7U\nwIWEGsHED5WT69GyxjyKHcqGoV7uFnqFN0sQVKVTy/NFRVQvtBUspCbsOirdDRie\nAB2KbGHL+t1QrRF10szwCJDyk5aYlVhxvdI8zn010nrxHkiyQpDFFldDMLJl10BW\n2w9PGO061z+tntdRcKQGuEpnIr9U5Vs=\n-----END CERTIFICATE REQUEST-----\n",
private_key: "encrypted_private_key"
)
end
def test_does_metadata_is_api
@ -12,6 +27,41 @@ class CertificateTest < ActiveSupport::TestCase
end
def test_certificate_sign_returns_false
assert_not @certificate.sign!(password: ENV['ca_key_password']), 'false'
ENV['ca_key_password'] = 'test_password'
assert_not @certificate.sign!(password: ENV['ca_key_password'])
end
def test_renewable_when_not_expired
@certificate.update!(
crt: @valid_crt,
expires_at: 20.days.from_now
)
assert @certificate.renewable?
end
def test_not_renewable_when_expired
@certificate.update!(
crt: @valid_crt,
expires_at: 1.day.ago
)
assert @certificate.expired?
assert_not @certificate.renewable?
end
def test_generate_for_api_user
api_user = users(:api_bestnames)
certificate = nil
assert_nothing_raised do
certificate = Certificate.generate_for_api_user(api_user: api_user)
end
assert certificate.persisted?
assert_equal api_user, certificate.api_user
assert certificate.private_key.present?
assert certificate.csr.present?
assert certificate.expires_at.present?
end
end

View file

@ -1,52 +0,0 @@
require 'test_helper'
class UserCertificateTest < ActiveSupport::TestCase
def setup
@user = users(:api_bestnames)
@certificate = user_certificates(:one)
end
test "should be valid with required attributes" do
certificate = UserCertificate.new(
user: @user,
private_key: 'dummy_key',
status: 'pending'
)
assert certificate.valid?
end
test "should not be valid without user" do
@certificate.user = nil
assert_not @certificate.valid?
end
test "should not be valid without private_key" do
@certificate.private_key = nil
assert_not @certificate.valid?
end
test "renewable? should be false without certificate" do
@certificate.certificate = nil
assert_not @certificate.renewable?
end
test "renewable? should be false when revoked" do
@certificate.status = 'revoked'
assert_not @certificate.renewable?
end
test "renewable? should be true when expires in less than 30 days" do
@certificate.expires_at = 29.days.from_now
assert @certificate.renewable?
end
test "expired? should be true when certificate is expired" do
@certificate.expires_at = 1.day.ago
assert @certificate.expired?
end
test "renew should raise error when certificate is not renewable" do
@certificate.status = 'revoked'
assert_raises(RuntimeError) { @certificate.renew }
end
end

View file

@ -0,0 +1,62 @@
require 'test_helper'
module Certificates
class CertificateGeneratorTest < ActiveSupport::TestCase
setup do
@certificate = certificates(:api)
@generator = CertificateGenerator.new(
username: "test_user",
registrar_code: "REG123",
registrar_name: "Test Registrar"
)
end
def test_generates_new_certificate
result = @generator.call
assert result[:private_key].present?
assert result[:csr].present?
assert result[:crt].present?
assert result[:p12].present?
assert result[:expires_at].present?
assert_instance_of String, result[:private_key]
assert_instance_of String, result[:csr]
assert_instance_of String, result[:crt]
assert_instance_of String, result[:p12]
assert_instance_of Time, result[:expires_at]
end
def test_uses_existing_csr_and_private_key
existing_csr = @certificate.csr
existing_private_key = "existing_private_key"
@certificate.update!(private_key: existing_private_key)
result = @generator.call
assert result[:csr].present?
assert result[:private_key].present?
assert_not_equal existing_csr, result[:csr]
assert_not_equal existing_private_key, result[:private_key]
end
def test_renew_certificate
@certificate.update!(
expires_at: 20.days.from_now
)
generator = CertificateGenerator.new(
username: "test_user",
registrar_code: "REG123",
registrar_name: "Test Registrar"
)
result = generator.call
assert result[:crt].present?
assert result[:expires_at] > Time.current
assert_instance_of String, result[:crt]
assert_instance_of Time, result[:expires_at]
end
end
end