Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .github-assets/trustpoint_banner.png
Binary file not shown.
1 change: 1 addition & 0 deletions .github-assets/trustpoint_github_banner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
![Trustpoint](.github-assets/trustpoint_banner.png)
![Trustpoint](.github-assets/trustpoint_github_banner.svg)

<div align="center">

Expand Down
Binary file removed docs/source/_static/trustpoint_banner.png
Binary file not shown.
1 change: 1 addition & 0 deletions docs/source/_static/trustpoint_github_banner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/source/cra/CONTROLS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="../_static/trustpoint_banner.png" alt="Trustpoint Logo" width="600">
<img src="../_static/trustpoint_github_banner.svg" alt="Trustpoint Logo" width="600">
</p>

# Security Controls
Expand Down
2 changes: 1 addition & 1 deletion docs/source/cra/CRA_COMPLIANCE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="../_static/trustpoint_banner.png" alt="Trustpoint Logo" width="600">
<img src="../_static/trustpoint_github_banner.svg" alt="Trustpoint Logo" width="600">
</p>

# CRA Conformity Assessment
Expand Down
2 changes: 1 addition & 1 deletion docs/source/cra/RISK_REGISTER.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="../_static/trustpoint_banner.png" alt="Trustpoint Logo" width="600">
<img src="../_static/trustpoint_github_banner.svg" alt="Trustpoint Logo" width="600">
</p>

# Risk Register
Expand Down
2 changes: 1 addition & 1 deletion docs/source/cra/THREAT_MODEL.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center">
<img src="../_static/trustpoint_banner.png" alt="Trustpoint Logo" width="600">
<img src="../_static/trustpoint_github_banner.svg" alt="Trustpoint Logo" width="600">
</p>

# Threat Model
Expand Down
2 changes: 1 addition & 1 deletion docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.

.. image:: /_static/trustpoint_banner.png
.. image:: /_static/trustpoint_github_banner.svg
:align: center

======================================
Expand Down
95 changes: 26 additions & 69 deletions trustpoint/management/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
from pki.models.truststore import TruststoreModel
from pki.services.external_csr import certificate_matches_credential, parse_single_certificate
from pki.services.key_generation import supported_key_type_choices
from pki.util.keys import AutoGenPkiKeyAlgorithm, supported_auto_gen_pki_key_algorithms
from pki.util.x509 import CertificateVerifier
from trustpoint.logger import LoggerMixin

Expand Down Expand Up @@ -204,7 +203,7 @@ class SecurityConfigForm(forms.ModelForm[SecurityConfig]):
"""Security configuration model form."""

FEATURE_TO_FIELDS: ClassVar[dict[type[SecurityFeature], list[str]]] = {
AutoGenPkiFeature: ['auto_gen_pki', 'auto_gen_pki_key_algorithm'],
AutoGenPkiFeature: ['auto_gen_pki'],
}

def __init__(self, *args: Any, **kwargs: Any)-> None:
Expand All @@ -229,26 +228,6 @@ def __init__(self, *args: Any, **kwargs: Any)-> None:
if field_name in self.fields:
self.fields[field_name].widget.attrs['disabled'] = 'disabled'

supported_algorithms = supported_auto_gen_pki_key_algorithms()
self.supported_auto_gen_pki_key_algorithms = supported_algorithms

if self.instance and self.instance.auto_gen_pki:
self.fields['auto_gen_pki_key_algorithm'].widget.attrs['disabled'] = 'disabled'
elif 'auto_gen_pki_key_algorithm' in self.fields:
auto_gen_pki_key_algorithm_field = cast(
'forms.ChoiceField',
self.fields['auto_gen_pki_key_algorithm'],
)
if supported_algorithms:
auto_gen_pki_key_algorithm_field.choices = [
(algorithm.value, algorithm.label) for algorithm in supported_algorithms
]
else:
auto_gen_pki_key_algorithm_field.choices = [
('', _('No supported backend algorithms available')),
]
auto_gen_pki_key_algorithm_field.widget.attrs['disabled'] = 'disabled'

self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
Expand All @@ -258,15 +237,22 @@ def __init__(self, *args: Any, **kwargs: Any)-> None:
),
Fieldset(
_('Advanced security settings'),
Field('auto_gen_pki', wrapper_class='form-check form-switch'),
'auto_gen_pki_key_algorithm',
'rsa_minimum_key_size',
'max_cert_validity_days',
'max_crl_validity_days',
'credential_ttl_seconds',
Field('allow_ca_issuance', wrapper_class='form-check form-switch'),
Field('allow_auto_gen_pki', wrapper_class='form-check form-switch'),
Field('allow_self_signed_ca', wrapper_class='form-check form-switch'),
Field('auto_gen_pki', wrapper_class='form-check form-switch'),
HTML(
"{% load i18n %}"
"{% if form.instance.auto_gen_pki %}"
'<p class="form-text mt-0 mb-3">'
'<a href="{% url \'pki:issuing_cas-add-method_select\' %}">'
"{% trans 'Create an auto-generated PKI' %}"
'</a></p>'
"{% endif %}"
),
Field('allow_imported_private_keys', wrapper_class='form-check form-switch'),
'permitted_no_onboarding_pki_protocols',
'permitted_onboarding_protocols'
Expand All @@ -285,7 +271,10 @@ def __init__(self, *args: Any, **kwargs: Any)-> None:

auto_gen_pki = forms.BooleanField(
required=False,
label=_('Enable local auto-generated PKI'),
label=_('Allow local auto-generated PKI creation'),
help_text=_(
'Turning this off disables the local auto-generated PKI and revokes all certificates issued by it.'
),
widget=forms.CheckboxInput(
attrs={
'class': 'form-check-input',
Expand All @@ -297,13 +286,6 @@ def __init__(self, *args: Any, **kwargs: Any)-> None:
),
)

auto_gen_pki_key_algorithm = forms.ChoiceField(
choices=AutoGenPkiKeyAlgorithm,
label=_('Key Algorithm for auto-generated PKI'),
required=False,
widget=forms.Select(attrs={'data-hide-at-sl': '[false, false, true, true, true]'}),
)

RSA_KEY_CHOICES: ClassVar[list[tuple[object, object]]] = [
('', _('None / Not Permitted')),
(1024, '1024'),
Expand Down Expand Up @@ -343,10 +325,10 @@ class Meta:
"""Meta configuration for SecurityConfigForm."""
model = SecurityConfig
fields: ClassVar[list[str]] = [
'security_mode', 'auto_gen_pki', 'auto_gen_pki_key_algorithm',
'security_mode',
'rsa_minimum_key_size', 'max_cert_validity_days', 'max_crl_validity_days',
'credential_ttl_seconds',
'allow_ca_issuance', 'allow_auto_gen_pki', 'allow_self_signed_ca',
'credential_ttl_seconds', 'auto_gen_pki',
'allow_ca_issuance', 'allow_self_signed_ca',
'allow_imported_private_keys',
'permitted_no_onboarding_pki_protocols',
'permitted_onboarding_protocols'
Expand Down Expand Up @@ -402,24 +384,6 @@ def clean_rsa_minimum_key_size(self) -> int | None:
except (TypeError, ValueError) as err:
raise ValidationError(_('Invalid RSA key size.')) from err

def clean_auto_gen_pki_key_algorithm(self) -> AutoGenPkiKeyAlgorithm:
"""Keep the current value of `auto_gen_pki_key_algorithm` from the instance if the field was disabled."""
form_value = self.cleaned_data.get('auto_gen_pki_key_algorithm')
if form_value is None or form_value == '':
if self.instance:
return AutoGenPkiKeyAlgorithm(self.instance.auto_gen_pki_key_algorithm)
return AutoGenPkiKeyAlgorithm.RSA2048
selected_algorithm = AutoGenPkiKeyAlgorithm(form_value)
supported_algorithms = getattr(
self,
'supported_auto_gen_pki_key_algorithms',
supported_auto_gen_pki_key_algorithms(),
)
if selected_algorithm not in supported_algorithms:
msg = _('The selected auto-generated PKI algorithm is not supported by the active backend.')
raise ValidationError(msg)
return selected_algorithm

def _validate_mode_constraints(self, cleaned: dict[str, Any], mode: str) -> None:
"""Validate that submitted values comply with the given security mode defaults."""
defaults = SecurityConfig._MODE_DEFAULTS[mode] # noqa: SLF001
Expand Down Expand Up @@ -461,22 +425,15 @@ def clean(self) -> dict[str, Any]:
if mode != SecurityConfig.SecurityModeChoices.LAB:
self._validate_mode_constraints(cleaned, str(mode))

if cleaned.get('auto_gen_pki') and not cleaned.get('allow_auto_gen_pki'):
self.add_error('auto_gen_pki', 'Cannot enable auto-generated PKI when it is not permitted.')
selected_mode = str(mode)
mode_defaults = SecurityConfig._MODE_DEFAULTS.get(selected_mode) # noqa: SLF001
if self.instance and selected_mode == self.instance.security_mode:
auto_gen_pki_allowed = self.instance.allow_auto_gen_pki
else:
auto_gen_pki_allowed = mode_defaults['allow_auto_gen_pki'] if mode_defaults else False

supported_algorithms = getattr(
self,
'supported_auto_gen_pki_key_algorithms',
supported_auto_gen_pki_key_algorithms(),
)
selected_algorithm = cleaned.get('auto_gen_pki_key_algorithm')
if cleaned.get('auto_gen_pki') and not supported_algorithms:
self.add_error('auto_gen_pki', _('No auto-generated PKI algorithm is supported by the active backend.'))
elif cleaned.get('auto_gen_pki') and selected_algorithm not in supported_algorithms:
self.add_error(
'auto_gen_pki_key_algorithm',
_('The selected auto-generated PKI algorithm is not supported by the active backend.'),
)
if cleaned.get('auto_gen_pki') and not auto_gen_pki_allowed:
self.add_error('auto_gen_pki', 'Cannot enable auto-generated PKI when it is not permitted.')

return cleaned

Expand Down
83 changes: 2 additions & 81 deletions trustpoint/management/tests/test_forms/test_security_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from management.forms import SecurityConfigForm
from management.models import SecurityConfig
from onboarding.enums import NoOnboardingPkiProtocol, OnboardingProtocol
from pki.util.keys import AutoGenPkiKeyAlgorithm


class SecurityConfigFormTest(TestCase):
Expand All @@ -18,15 +17,15 @@ def setUp(self):
self.config = SecurityConfig.objects.create(
security_mode=SecurityConfig.SecurityModeChoices.BROWNFIELD,
auto_gen_pki=False,
auto_gen_pki_key_algorithm=AutoGenPkiKeyAlgorithm.RSA2048
)

def test_form_initialization_with_instance(self):
"""Test form initializes correctly with existing instance."""
form = SecurityConfigForm(instance=self.config)
self.assertIn('security_mode', form.fields)
self.assertIn('auto_gen_pki', form.fields)
self.assertIn('auto_gen_pki_key_algorithm', form.fields)
assert 'allow_auto_gen_pki' not in form.fields
assert 'auto_gen_pki_key_algorithm' not in form.fields
assert 'allow_imported_private_keys' in form.fields

def test_form_initialization_without_instance(self):
Expand Down Expand Up @@ -66,7 +65,6 @@ def test_form_with_dev_security_mode(self):
form_data = {
'security_mode': SecurityConfig.SecurityModeChoices.LAB,
'auto_gen_pki': True,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA2048,
'allow_auto_gen_pki': True,
}
form = SecurityConfigForm(data=form_data, instance=self.config)
Expand All @@ -77,7 +75,6 @@ def test_form_with_high_security_mode(self):
form_data = {
'security_mode': SecurityConfig.SecurityModeChoices.HARDENED,
'auto_gen_pki': False,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA2048,
# Hardened defaults from _MODE_DEFAULTS
'rsa_minimum_key_size': 4096,
'max_cert_validity_days': 365,
Expand All @@ -90,78 +87,6 @@ def test_form_with_high_security_mode(self):
form = SecurityConfigForm(data=form_data, instance=self.config)
self.assertTrue(form.is_valid())

def test_clean_auto_gen_pki_key_algorithm_with_none(self):
"""Test clean method returns instance value when form value is not provided."""
self.config.auto_gen_pki = True
self.config.auto_gen_pki_key_algorithm = AutoGenPkiKeyAlgorithm.SECP256R1
self.config.save()

form_data = {
'security_mode': SecurityConfig.SecurityModeChoices.BROWNFIELD,
'auto_gen_pki': True,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.SECP256R1,
# Brownfield defaults from _MODE_DEFAULTS
'rsa_minimum_key_size': 1024,
'max_cert_validity_days': 1825,
'max_crl_validity_days': 365,
'allow_ca_issuance': False,
'allow_auto_gen_pki': True,
'allow_self_signed_ca': True,
}
form = SecurityConfigForm(data=form_data, instance=self.config)
self.assertTrue(form.is_valid())
result = form.cleaned_data['auto_gen_pki_key_algorithm']
self.assertEqual(result, AutoGenPkiKeyAlgorithm.SECP256R1)

def test_clean_auto_gen_pki_key_algorithm_returns_provided_value(self):
"""Test clean method uses the provided algorithm value."""
form_data = {
'security_mode': SecurityConfig.SecurityModeChoices.BROWNFIELD,
'auto_gen_pki': True,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA2048,
# Brownfield defaults from _MODE_DEFAULTS
'rsa_minimum_key_size': 1024,
'max_cert_validity_days': 1825,
'max_crl_validity_days': 365,
'allow_ca_issuance': False,
'allow_auto_gen_pki': True,
'allow_self_signed_ca': True,
}
form = SecurityConfigForm(data=form_data)
self.assertTrue(form.is_valid())
result = form.cleaned_data['auto_gen_pki_key_algorithm']
self.assertEqual(result, AutoGenPkiKeyAlgorithm.RSA2048)

def test_clean_auto_gen_pki_key_algorithm_with_value(self):
"""Test clean method uses provided value when available."""
form_data = {
'security_mode': SecurityConfig.SecurityModeChoices.BROWNFIELD,
'auto_gen_pki': True,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA4096,
# Brownfield defaults from _MODE_DEFAULTS
'rsa_minimum_key_size': 1024,
'max_cert_validity_days': 1825,
'max_crl_validity_days': 365,
'allow_ca_issuance': False,
'allow_auto_gen_pki': True,
'allow_self_signed_ca': True,
}
form = SecurityConfigForm(data=form_data, instance=self.config)
self.assertTrue(form.is_valid())
result = form.cleaned_data['auto_gen_pki_key_algorithm']
self.assertEqual(result, AutoGenPkiKeyAlgorithm.RSA4096)

def test_form_disables_algorithm_field_when_auto_gen_pki_enabled(self):
"""Test that algorithm field is disabled when auto_gen_pki is already enabled."""
self.config.auto_gen_pki = True
self.config.save()

form = SecurityConfigForm(instance=self.config)
self.assertEqual(
form.fields['auto_gen_pki_key_algorithm'].widget.attrs.get('disabled'),
'disabled'
)

def test_form_initialization_with_data_security_mode(self):
"""Test form initialization considers security_mode from form data."""
form_data = {
Expand All @@ -185,7 +110,6 @@ def test_all_security_modes(self):
form_data = {
'security_mode': mode,
'auto_gen_pki': defaults['allow_auto_gen_pki'],
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA2048,
'rsa_minimum_key_size': defaults['rsa_minimum_key_size'] or '',
'max_cert_validity_days': defaults['max_cert_validity_days'],
'max_crl_validity_days': defaults['max_crl_validity_days'],
Expand All @@ -209,7 +133,6 @@ def test_form_saves_imported_private_key_policy(self) -> None:
data={
'security_mode': SecurityConfig.SecurityModeChoices.LAB,
'auto_gen_pki': False,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA2048,
'allow_imported_private_keys': True,
},
instance=self.config,
Expand Down Expand Up @@ -252,7 +175,6 @@ def test_protocol_allowlists_are_saved_as_int_lists() -> None:
config = SecurityConfig.objects.create(
security_mode=SecurityConfig.SecurityModeChoices.LAB,
auto_gen_pki=False,
auto_gen_pki_key_algorithm=AutoGenPkiKeyAlgorithm.RSA2048,
)

no_onboarding_values = [
Expand All @@ -268,7 +190,6 @@ def test_protocol_allowlists_are_saved_as_int_lists() -> None:
data={
'security_mode': SecurityConfig.SecurityModeChoices.LAB,
'auto_gen_pki': False,
'auto_gen_pki_key_algorithm': AutoGenPkiKeyAlgorithm.RSA2048,
'permitted_no_onboarding_pki_protocols': no_onboarding_values,
'permitted_onboarding_protocols': onboarding_values,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_get_context_data_with_software_config(self) -> None:
assert context['is_software_backend']
assert context['capability_badge'] == 'success'
assert context['supported_key_capabilities']
assert context['supported_auto_gen_pki_algorithms']
assert context['supported_auto_gen_pki_key_types']
assert 'page_title' in context

def test_get_context_data_with_softhsm_config(self) -> None:
Expand Down
Loading
Loading