From 6cbf74ddfafc7c2927386bc02ee1f44f16f3ee63 Mon Sep 17 00:00:00 2001 From: Rohit Bohara Date: Tue, 31 Mar 2026 16:01:14 +0200 Subject: [PATCH 1/2] add revoke cmp help view --- trustpoint/devices/urls.py | 8 +- trustpoint/help_pages/commands.py | 26 +++ trustpoint/help_pages/devices_help_views.py | 229 ++++++++++++-------- 3 files changed, 171 insertions(+), 92 deletions(-) diff --git a/trustpoint/devices/urls.py b/trustpoint/devices/urls.py index 39436db63..d3f858587 100644 --- a/trustpoint/devices/urls.py +++ b/trustpoint/devices/urls.py @@ -24,7 +24,7 @@ path( 'new-onboarding/', views.DeviceCreateAddOnboardingTypeView.as_view(), - name=f'{DEVICES_PAGE_DEVICES_SUBCATEGORY}_new_onboarding' + name=f'{DEVICES_PAGE_DEVICES_SUBCATEGORY}_new_onboarding', ), path( 'opc-ua-gds/create/', @@ -100,6 +100,11 @@ devices_help_views.DeviceNoOnboardingCmpSharedSecretHelpView.as_view(), name=f'{DEVICES_PAGE_DEVICES_SUBCATEGORY}_no_onboarding_cmp_shared_secret_help', ), + path( + 'certificate-lifecycle-management//revoke/cmp/', + devices_help_views.DeviceCmpRevokeHelpView.as_view(), + name=f'{DEVICES_PAGE_DEVICES_SUBCATEGORY}_device_revoke_cmp_help', + ), path( ( 'opc-ua-gds/certificate-lifecycle-management/' @@ -444,7 +449,6 @@ views.DeviceBulkDeleteView.as_view(), name=f'{DEVICES_PAGE_DEVICES_SUBCATEGORY}_device_delete', ), - path( 'zero-touch-credentials/', ztc_views.OwnerCredentialTableView.as_view(), diff --git a/trustpoint/help_pages/commands.py b/trustpoint/help_pages/commands.py index 54f975a21..25cc43023 100644 --- a/trustpoint/help_pages/commands.py +++ b/trustpoint/help_pages/commands.py @@ -82,6 +82,32 @@ def get_dynamic_cert_profile_command( f'-extracertsout full-chain-{cred_number}.pem' ) + @staticmethod + def get_dynamic_cert_revoke_command( + host: str, cred_number: int) -> str: + """Gets the dynamic certificate revoke command. + + Args: + host: The full host name and url path, e.g. https://127.0.0.1/.well-known./cmp/p/... + pk: The primary key of the device in question used as Key Identifier (KID). + shared_secret: The shared secret. + cred_number: The credential number - counter of issued credentials. + sample_request: The sample certificate request in JSON format. + + Returns: + The constructed command. + """ + return ( + 'openssl cmp \\\n' + '-cmd rr \\\n' + f'-server {host} \\\n' + f'-cert domain-credential-certificate-{cred_number}.pem \\\n' + f'-key domain-credential-key-{cred_number}.pem \\\n' + f'-oldcert certificate-{cred_number}.pem \\\n' + f'-revreason 0 \\\n' + f'-trusted domain-credential-full-chain-{cred_number}.pem \\\n' + ) + @staticmethod def get_domain_credential_profile_command(host: str, pk: int, shared_secret: str) -> str: """Get the domain credential profile command. diff --git a/trustpoint/help_pages/devices_help_views.py b/trustpoint/help_pages/devices_help_views.py index 30d9e1a70..98dc6a7f1 100644 --- a/trustpoint/help_pages/devices_help_views.py +++ b/trustpoint/help_pages/devices_help_views.py @@ -89,7 +89,9 @@ def _make_context(self, host_ip: str = '127.0.0.1') -> HelpContext: allowed_app_profiles = list( domain.get_allowed_cert_profiles().exclude( - certificate_profile__unique_name=domain.get_domain_credential_profile_name())) + certificate_profile__unique_name=domain.get_domain_credential_profile_name() + ) + ) return HelpContext( device=device, @@ -197,9 +199,7 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], cred = help_context.cred_count - def _build_section( - title: str, profile_name: str, cmd: str, *, hidden: bool = False - ) -> HelpSection: + def _build_section(title: str, profile_name: str, cmd: str, *, hidden: bool = False) -> HelpSection: return HelpSection( title, [ @@ -254,6 +254,77 @@ def _build_section( return sections, _non_lazy('Help - Issue Application Certificates using CMP with a shared-secret (HMAC)') +class CmpRevocationStrategy(HelpPageStrategy): + """Strategy for building the no-onboarding CMP shared-secret help page.""" + + @override + def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], str]: + device = help_context.get_device_or_http_404() + no_onboarding_config = getattr(device, 'no_onboarding_config', None) + if not no_onboarding_config: + raise Http404(_('Onboarding is configured for this device.')) + operation = 'revocation' + base = help_context.host_cmp_path + + summary = HelpSection( + _non_lazy('Summary'), + [ + HelpRow( + _non_lazy('Certificate Revocation URL'), + f'{base}/{operation}', + ValueRenderType.CODE, + ), + HelpRow(_non_lazy('Key Identifier (KID)'), str(device.pk), ValueRenderType.CODE), + ], + ) + + cred = help_context.cred_count + + def _build_section(title: str, profile_name: str, cmd: str, *, hidden: bool = False) -> HelpSection: + return HelpSection( + title, + [ + HelpRow(_non_lazy('OpenSSL Command'), cmd, ValueRenderType.CODE), + ], + css_id=profile_name, + hidden=hidden, + ) + + sections = [summary] + + for i, profile in enumerate(help_context.allowed_app_profiles): + name = profile.alias or profile.certificate_profile.unique_name + title = profile.certificate_profile.display_name or name + + try: + cmd = CmpSharedSecretCommandBuilder.get_dynamic_cert_revoke_command( + host=f'{base}/{operation}', + cred_number=cred, + ) + except (json.JSONDecodeError, PydanticValidationError, ProfileValidationError, ValueError) as e: + err_msg = f'The command cannot be generated because the Certificate Profile is malformed: {e}' + err_sect = HelpSection( + _non_lazy(f'Revoke Request for a {title} Certificate'), + [ + HelpRow(_non_lazy('OpenSSL Command'), err_msg, ValueRenderType.PLAIN), + ], + css_id=name, + hidden=(i > 0), + ) + sections.append(err_sect) + continue + + sect = _build_section( + _non_lazy(f'Revoke Request for a {title} Certificate'), + name, + cmd, + hidden=(i > 0), + ) + sections.append(sect) + + return sections, _non_lazy('Help - Revoke CMP Domain Credential Certificate') + + class DeviceNoOnboardingCmpSharedSecretHelpView(BaseHelpView): """Help view for the case of no onboarding using CMP shared-secret for generic device abstractions.""" @@ -261,6 +332,13 @@ class DeviceNoOnboardingCmpSharedSecretHelpView(BaseHelpView): strategy = NoOnboardingCmpSharedSecretStrategy() +class DeviceCmpRevokeHelpView(BaseHelpView): + """Help view for the case of revocation of Cmp Credential for generic device abstractions.""" + + page_name = DEVICES_PAGE_DEVICES_SUBCATEGORY + strategy = CmpRevocationStrategy() + + class OpcUaGdsNoOnboardingCmpSharedSecretHelpView(BaseHelpView): """Help view for the case of no onboarding using CMP shared-secret for OPC-UA GDS abstractions.""" @@ -312,9 +390,7 @@ def _get_enroll_path(cert_profile_name: str) -> str: cred = help_context.cred_count - def _build_section( - title: str, cert_profile_name: str, cmd: str, *, hidden: bool = False - ) -> HelpSection: + def _build_section(title: str, cert_profile_name: str, cmd: str, *, hidden: bool = False) -> HelpSection: return HelpSection( title, [ @@ -485,7 +561,6 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], operation = 'simpleenroll' base = help_context.host_est_path - summary = HelpSection( _non_lazy('Summary'), [ @@ -607,9 +682,7 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], cred = help_context.cred_count - def _build_section( - title: str, cert_profile_name: str, cmd: str, *, hidden: bool = False - ) -> HelpSection: + def _build_section(title: str, cert_profile_name: str, cmd: str, *, hidden: bool = False) -> HelpSection: return HelpSection( title, [ @@ -708,9 +781,7 @@ def _get_enroll_path(cert_profile_name: str) -> str: cred = help_context.cred_count - def _build_section( - title: str, cert_profile_name: str, cmd: str, *, hidden: bool = False - ) -> HelpSection: + def _build_section(title: str, cert_profile_name: str, cmd: str, *, hidden: bool = False) -> HelpSection: return HelpSection( title, [ @@ -842,9 +913,7 @@ def _get_enroll_path(cert_profile_name: str) -> str: cred = help_context.cred_count - def _build_section( - title: str, cert_profile_name: str, csr_cmd: str, *, hidden: bool = False - ) -> HelpSection: + def _build_section(title: str, cert_profile_name: str, csr_cmd: str, *, hidden: bool = False) -> HelpSection: return HelpSection( title, [ @@ -889,20 +958,24 @@ def _build_section( ) except (json.JSONDecodeError, PydanticValidationError, ProfileValidationError, ValueError) as e: err_msg = f'The command cannot be generated because the Certificate Profile is malformed: {e}' - sections.append(HelpSection( - _non_lazy(f'Certificate Request for a {title} Certificate'), - [HelpRow(_non_lazy('Generate CSR'), err_msg, ValueRenderType.PLAIN)], - css_id=name, - hidden=(i > 0), - )) + sections.append( + HelpSection( + _non_lazy(f'Certificate Request for a {title} Certificate'), + [HelpRow(_non_lazy('Generate CSR'), err_msg, ValueRenderType.PLAIN)], + css_id=name, + hidden=(i > 0), + ) + ) continue - sections.append(_build_section( - _non_lazy(f'Certificate Request for a {title} Certificate'), - name, - csr_cmd, - hidden=(i > 0), - )) + sections.append( + _build_section( + _non_lazy(f'Certificate Request for a {title} Certificate'), + name, + csr_cmd, + hidden=(i > 0), + ) + ) return sections, _non_lazy('Help - Issue Application Certificates using REST with username and password') @@ -1058,9 +1131,7 @@ def _get_reenroll_path(cert_profile_name: str) -> str: cred = help_context.cred_count - def _build_section( - title: str, cert_profile_name: str, csr_cmd: str, *, hidden: bool = False - ) -> HelpSection: + def _build_section(title: str, cert_profile_name: str, csr_cmd: str, *, hidden: bool = False) -> HelpSection: return HelpSection( title, [ @@ -1096,9 +1167,7 @@ def _build_section( ), HelpRow( _non_lazy('Extract certificate chain from JSON response'), - value=RestClientCertificateCommandBuilder.get_extract_cert_chain_command( - cred_number=cred - ), + value=RestClientCertificateCommandBuilder.get_extract_cert_chain_command(cred_number=cred), value_render_type=ValueRenderType.CODE, ), ], @@ -1126,20 +1195,24 @@ def _build_section( ) except (json.JSONDecodeError, PydanticValidationError, ProfileValidationError, ValueError) as e: err_msg = f'The command cannot be generated because the Certificate Profile is malformed: {e}' - sections.append(HelpSection( - _non_lazy(f'Certificate Request for a {title} Certificate'), - [HelpRow(_non_lazy('Generate CSR'), err_msg, ValueRenderType.PLAIN)], - css_id=name, - hidden=(i > 0), - )) + sections.append( + HelpSection( + _non_lazy(f'Certificate Request for a {title} Certificate'), + [HelpRow(_non_lazy('Generate CSR'), err_msg, ValueRenderType.PLAIN)], + css_id=name, + hidden=(i > 0), + ) + ) continue - sections.append(_build_section( - _non_lazy(f'Certificate Request for a {title} Certificate'), - name, - csr_cmd, - hidden=(i > 0), - )) + sections.append( + _build_section( + _non_lazy(f'Certificate Request for a {title} Certificate'), + name, + csr_cmd, + hidden=(i > 0), + ) + ) return sections, _non_lazy('Help - Issue Application Certificates using REST with a Domain Credential') @@ -1208,14 +1281,10 @@ def _build_summary_section(self, help_context: HelpContext) -> HelpSection: def _build_actions_section(self, device: DeviceModel) -> HelpSection: """Build the actions section with available operations.""" has_domain_credential = IssuedCredentialModel.objects.filter( - device=device, - issued_credential_type=IssuedCredentialModel.IssuedCredentialType.DOMAIN_CREDENTIAL + device=device, issued_credential_type=IssuedCredentialModel.IssuedCredentialType.DOMAIN_CREDENTIAL ).exists() - discover_server_url = reverse( - 'devices:devices_discover_server', - kwargs={'pk': device.pk} - ) + discover_server_url = reverse('devices:devices_discover_server', kwargs={'pk': device.pk}) discover_html = ( '
' @@ -1227,14 +1296,8 @@ def _build_actions_section(self, device: DeviceModel) -> HelpSection: ) if has_domain_credential: - update_trustlist_url = reverse( - 'devices:devices_update_trustlist', - kwargs={'pk': device.pk} - ) - update_cert_url = reverse( - 'devices:devices_update_server_certificate', - kwargs={'pk': device.pk} - ) + update_trustlist_url = reverse('devices:devices_update_trustlist', kwargs={'pk': device.pk}) + update_cert_url = reverse('devices:devices_update_server_certificate', kwargs={'pk': device.pk}) trustlist_html = ( '' @@ -1302,10 +1365,7 @@ def _build_ca_hierarchy_html(self, ca_chain: list[CaModel]) -> tuple[str, bool]: Returns: Tuple of (hierarchy_html, has_missing_crl). """ - hierarchy_html = ( - '
' - 'Certificate Authority Hierarchy:
' - ) + hierarchy_html = '
Certificate Authority Hierarchy:
' has_missing_crl = False for idx, ca in enumerate(ca_chain): @@ -1340,8 +1400,7 @@ def _build_ca_hierarchy_html(self, ca_chain: list[CaModel]) -> tuple[str, bool]: ca_detail_url = reverse('pki:issuing_cas-detail', kwargs={'pk': ca.pk}) indent = ' ' * (idx * 4) hierarchy_html += ( - f'{indent}└─ {cn_value} ' - f'[{crl_link}{crl_status}]
' + f'{indent}└─ {cn_value} [{crl_link}{crl_status}]
' ) except (ValueError, TypeError, AttributeError): @@ -1371,7 +1430,6 @@ def _build_ca_hierarchy_section(self, device: DeviceModel) -> HelpSection: hierarchy_html, has_missing_crl = self._build_ca_hierarchy_html(ca_chain) rows = [ - HelpRow( _non_lazy('Certificate Chain'), hierarchy_html, @@ -1413,10 +1471,7 @@ def _build_ca_hierarchy_section(self, device: DeviceModel) -> HelpSection: def _build_download_section(self, device: DeviceModel) -> HelpSection: """Build the download section for trust bundle.""" if device.domain and device.domain.issuing_ca: - download_url = reverse( - 'devices:trust_bundle_download', - kwargs={'pk': device.domain.issuing_ca.pk} - ) + download_url = reverse('devices:trust_bundle_download', kwargs={'pk': device.domain.issuing_ca.pk}) download_html = ( f'Download Trust Bundle' '

Download a ZIP file containing all CA certificates ' @@ -1456,10 +1511,7 @@ def _build_renewal_settings_section(self, device: DeviceModel) -> HelpSection: :param device: The OPC UA GDS Push device instance. :return: A HelpSection containing the renewal configuration form. """ - renewal_url = reverse( - 'devices:devices_cert_renewal_settings', - kwargs={'pk': device.pk} - ) + renewal_url = reverse('devices:devices_cert_renewal_settings', kwargs={'pk': device.pk}) enabled = device.opc_gds_push_enable_periodic_update interval = device.opc_gds_push_renewal_interval @@ -1555,6 +1607,7 @@ class OpcUaGdsPushOnboardingHelpView(BaseHelpView): page_name = DEVICES_PAGE_DEVICES_SUBCATEGORY strategy = OpcUaGdsPushOnboardingStrategy() + class AokiCmpIDevIDStrategy(HelpPageStrategy): """Strategy for building the AOKI CMP with IDevID help page.""" @@ -1600,8 +1653,7 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], [ HelpRow( _non_lazy('DevOwnerID Configuration'), - 'A DevOwnerID must be configured in Trustpoint with the corresponding certificate and ' - 'private key.', + 'A DevOwnerID must be configured in Trustpoint with the corresponding certificate and private key.', ValueRenderType.PLAIN, ), HelpRow( @@ -1667,9 +1719,7 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], ) keygen_cmd = AokiCmpIDevIDCommandBuilder.get_keygen_command() - cmp_ir_cmd = AokiCmpIDevIDCommandBuilder.get_cmp_ir_command( - help_context.host_cmp_path - ) + cmp_ir_cmd = AokiCmpIDevIDCommandBuilder.get_cmp_ir_command(help_context.host_cmp_path) example_commands = HelpSection( _non_lazy('Example Commands'), @@ -1738,8 +1788,7 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], [ HelpRow( _non_lazy('DevOwnerID Configuration'), - 'A DevOwnerID must be configured in Trustpoint with the corresponding certificate and ' - 'private key.', + 'A DevOwnerID must be configured in Trustpoint with the corresponding certificate and private key.', ValueRenderType.PLAIN, ), HelpRow( @@ -1773,7 +1822,6 @@ def build_sections(self, help_context: HelpContext) -> tuple[list[HelpSection], ], ) - how_it_works = HelpSection( _non_lazy('How AOKI with EST Works'), [ @@ -1884,7 +1932,9 @@ def _make_context(self, host_ip: str = '127.0.0.1') -> HelpContext: allowed_app_profiles = list( domain.get_allowed_cert_profiles().exclude( - certificate_profile__unique_name=domain.get_domain_credential_profile_name())) + certificate_profile__unique_name=domain.get_domain_credential_profile_name() + ) + ) return HelpContext( device=None, @@ -1969,7 +2019,9 @@ def _make_context(self, host_ip: str = '127.0.0.1') -> HelpContext: allowed_app_profiles = list( domain.get_allowed_cert_profiles().exclude( - certificate_profile__unique_name=domain.get_domain_credential_profile_name())) + certificate_profile__unique_name=domain.get_domain_credential_profile_name() + ) + ) return HelpContext( device=None, @@ -2024,7 +2076,6 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: return context - _AOKI_DEMO_CERT_FILES: list[tuple[str, str]] = [ ('idevid.pem', 'IDevID Certificate'), ('idevid_pk.pem', 'IDevID Private Key'), @@ -2034,9 +2085,7 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: ('ownerid_ca.pem', 'Owner CA Certificate'), ] -_AOKI_DEMO_CERTS_DIR: Path = ( - Path(__file__).resolve().parents[1] / 'aoki' / 'tests' / 'certs' -) +_AOKI_DEMO_CERTS_DIR: Path = Path(__file__).resolve().parents[1] / 'aoki' / 'tests' / 'certs' _AOKI_DEMO_ALLOWED_FILES: frozenset[str] = frozenset(name for name, _ in _AOKI_DEMO_CERT_FILES) From 552905ef5df52a6848f90e21968ff154299468a6 Mon Sep 17 00:00:00 2001 From: Christian Schwinne Date: Tue, 30 Jun 2026 12:39:19 +0200 Subject: [PATCH 2/2] CMP shared secret revocation view --- trustpoint/help_pages/commands.py | 63 ++++++++++++++++++++- trustpoint/help_pages/devices_help_views.py | 53 ++++++++--------- trustpoint/request/authorization/cmp.py | 6 +- 3 files changed, 86 insertions(+), 36 deletions(-) diff --git a/trustpoint/help_pages/commands.py b/trustpoint/help_pages/commands.py index 25cc43023..3fff3feba 100644 --- a/trustpoint/help_pages/commands.py +++ b/trustpoint/help_pages/commands.py @@ -83,9 +83,11 @@ def get_dynamic_cert_profile_command( ) @staticmethod - def get_dynamic_cert_revoke_command( + def get_app_cert_domain_revoke_command( host: str, cred_number: int) -> str: - """Gets the dynamic certificate revoke command. + """Gets the command for CMP application credential revocation req. using domain credential authentication. + + Only for CMP with Domain Credential (onboarding) Args: host: The full host name and url path, e.g. https://127.0.0.1/.well-known./cmp/p/... @@ -108,6 +110,63 @@ def get_dynamic_cert_revoke_command( f'-trusted domain-credential-full-chain-{cred_number}.pem \\\n' ) + @staticmethod + def get_app_cert_self_revoke_command( + host: str, cred_number: int) -> str: + """Gets the command for CMP application credential self-revocation request. + + Usable for revoking application credentials for both onboarding and no-onboarding, + only if app credential private key is available + + Args: + host: The full host name and url path, e.g. https://127.0.0.1/.well-known./cmp/p/... + pk: The primary key of the device in question used as Key Identifier (KID). + shared_secret: The shared secret. + cred_number: The credential number - counter of issued credentials. + sample_request: The sample certificate request in JSON format. + + Returns: + The constructed command. + """ + return ( + 'openssl cmp \\\n' + '-cmd rr \\\n' + f'-server {host} \\\n' + f'-cert certificate-{cred_number}.pem \\\n' + f'-key key-{cred_number}.pem \\\n' + f'-oldcert certificate-{cred_number}.pem \\\n' + f'-revreason 0 \\\n' + f'-trusted domain-credential-full-chain-{cred_number}.pem \\\n' + ) + + @staticmethod + def get_domain_credential_self_revoke_command( + host: str, cred_number: int) -> str: + """Gets the command for CMP domain credential self-revocation request. + + Only for revoking domain credentials (onboarding) + + Args: + host: The full host name and url path, e.g. https://127.0.0.1/.well-known./cmp/p/... + pk: The primary key of the device in question used as Key Identifier (KID). + shared_secret: The shared secret. + cred_number: The credential number - counter of issued credentials. + sample_request: The sample certificate request in JSON format. + + Returns: + The constructed command. + """ + return ( + 'openssl cmp \\\n' + '-cmd rr \\\n' + f'-server {host} \\\n' + f'-cert domain-credential-certificate-{cred_number}.pem \\\n' + f'-key domain-credential-key-{cred_number}.pem \\\n' + f'-oldcert domain-credential-certificate-{cred_number}.pem \\\n' + f'-revreason 0 \\\n' + f'-trusted domain-credential-full-chain-{cred_number}.pem \\\n' + ) + @staticmethod def get_domain_credential_profile_command(host: str, pk: int, shared_secret: str) -> str: """Get the domain credential profile command. diff --git a/trustpoint/help_pages/devices_help_views.py b/trustpoint/help_pages/devices_help_views.py index 20baee3ee..d1436080e 100644 --- a/trustpoint/help_pages/devices_help_views.py +++ b/trustpoint/help_pages/devices_help_views.py @@ -290,37 +290,30 @@ def _build_section(title: str, profile_name: str, cmd: str, *, hidden: bool = Fa sections = [summary] - for i, profile in enumerate(help_context.allowed_app_profiles): - name = profile.alias or profile.certificate_profile.unique_name - title = profile.certificate_profile.display_name or name - - try: - cmd = CmpSharedSecretCommandBuilder.get_dynamic_cert_revoke_command( - host=f'{base}/{operation}', - cred_number=cred, - ) - except (json.JSONDecodeError, PydanticValidationError, ProfileValidationError, ValueError) as e: - err_msg = f'The command cannot be generated because the Certificate Profile is malformed: {e}' - err_sect = HelpSection( - _non_lazy(f'Revoke Request for a {title} Certificate'), - [ - HelpRow(_non_lazy('OpenSSL Command'), err_msg, ValueRenderType.PLAIN), - ], - css_id=name, - hidden=(i > 0), - ) - sections.append(err_sect) - continue - - sect = _build_section( - _non_lazy(f'Revoke Request for a {title} Certificate'), - name, - cmd, - hidden=(i > 0), + try: + cmd = CmpSharedSecretCommandBuilder.get_app_cert_self_revoke_command( + host=f'{base}/{operation}', + cred_number=cred, ) - sections.append(sect) + except (json.JSONDecodeError, PydanticValidationError, ProfileValidationError, ValueError) as e: + err_msg = f'The command cannot be generated because the Certificate Profile is malformed: {e}' + err_sect = HelpSection( + _non_lazy('Revocation Request for an application cCertificate'), + [ + HelpRow(_non_lazy('OpenSSL Command'), err_msg, ValueRenderType.PLAIN), + ], + css_id='error', + ) + sections.append(err_sect) + + sect = _build_section( + _non_lazy('Revocation Request for an application Certificate'), + 'rr', + cmd, + ) + sections.append(sect) - return sections, _non_lazy('Help - Revoke CMP Domain Credential Certificate') + return sections, _non_lazy('Help - Revoke CMP Application Credential Certificate') class DeviceNoOnboardingCmpSharedSecretHelpView(BaseHelpView): @@ -331,7 +324,7 @@ class DeviceNoOnboardingCmpSharedSecretHelpView(BaseHelpView): class DeviceCmpRevokeHelpView(BaseHelpView): - """Help view for the case of revocation of Cmp Credential for generic device abstractions.""" + """Help view for the case of revocation of CMP Application Credential for generic device abstractions.""" page_name = DEVICES_PAGE_DEVICES_SUBCATEGORY strategy = CmpRevocationStrategy() diff --git a/trustpoint/request/authorization/cmp.py b/trustpoint/request/authorization/cmp.py index 7481582c3..74a7e0b7b 100644 --- a/trustpoint/request/authorization/cmp.py +++ b/trustpoint/request/authorization/cmp.py @@ -4,7 +4,7 @@ from pyasn1_modules.rfc4210 import PKIMessage # type: ignore[import-untyped] from cmp.models import CmpTransactionModel -from cmp.util import PKIFailureInfo +from cmp.util import PKI_STATUS_REJECTION, PKIFailureInfo from pki.models import IssuedCredentialModel from request.cmp_transaction_state import CmpTransactionState from request.request_context import ( @@ -147,9 +147,7 @@ def authorize(self, context: BaseRequestContext) -> None: if not isinstance(context, CmpCertConfRequestContext): return - # PKIStatus value 2 means "rejection" per RFC 4210 Section 5.2.3. - pki_status_rejection = 2 - if context.cert_conf_status != pki_status_rejection: + if context.cert_conf_status != PKI_STATUS_REJECTION: self.logger.debug('certConf: status is accepted (or absent) — no credential lookup required.') return