From e111fde854e512e24c7a6827f34d76a92e4e945c Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:53:18 -0700 Subject: [PATCH 1/8] Adding CoRIM Profile Spec and Schema This commit squashes all the intermediate commits from PR #49 Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- ...E-CoRIM-Extension-Profile-Specification.md | 509 ++++++++++++++++ .../examples/ocp-safe-sfr-fw-example.diag | 104 ++++ .../corim_profile/ocp-safe-sfr-profile.cddl | 59 ++ shortform_report-main/.gitignore | 7 + shortform_report-main/AUDITOR_GUIDE.md | 222 +++++++ shortform_report-main/OcpReportLib.py | 544 ++++++++++++++++-- shortform_report-main/README.md | 36 +- shortform_report-main/cbor_human_inspector.py | 433 ++++++++++++++ .../example_dual_format_generation.py | 319 ++++++++++ .../example_gen_sign_verify.py | 60 +- shortform_report-main/requirements.txt | 5 + shortform_report-main/tests/README.md | 121 ++++ .../tests/cbor_structure_analyzer.py | 222 +++++++ .../tests/final_validation_summary.py | 333 +++++++++++ .../tests/json_to_corim_converter.py | 318 ++++++++++ .../tests/test_cddl_validation.py | 198 +++++++ .../tests/test_corim_generation.py | 363 ++++++++++++ 17 files changed, 3773 insertions(+), 80 deletions(-) create mode 100644 Documentation/corim_profile/OCP-SAFE-CoRIM-Extension-Profile-Specification.md create mode 100755 Documentation/corim_profile/examples/ocp-safe-sfr-fw-example.diag create mode 100755 Documentation/corim_profile/ocp-safe-sfr-profile.cddl create mode 100644 shortform_report-main/.gitignore create mode 100644 shortform_report-main/AUDITOR_GUIDE.md create mode 100644 shortform_report-main/cbor_human_inspector.py create mode 100644 shortform_report-main/example_dual_format_generation.py create mode 100644 shortform_report-main/tests/README.md create mode 100644 shortform_report-main/tests/cbor_structure_analyzer.py create mode 100644 shortform_report-main/tests/final_validation_summary.py create mode 100644 shortform_report-main/tests/json_to_corim_converter.py create mode 100644 shortform_report-main/tests/test_cddl_validation.py create mode 100644 shortform_report-main/tests/test_corim_generation.py diff --git a/Documentation/corim_profile/OCP-SAFE-CoRIM-Extension-Profile-Specification.md b/Documentation/corim_profile/OCP-SAFE-CoRIM-Extension-Profile-Specification.md new file mode 100644 index 0000000..57613b3 --- /dev/null +++ b/Documentation/corim_profile/OCP-SAFE-CoRIM-Extension-Profile-Specification.md @@ -0,0 +1,509 @@ +# OCP Profile for IETF Concise Reference Integrity Manifest (CoRIM) Security Framework Review Extension (Draft) + +**Version 0.1 (Git commit c1b3501)** + +## Table of Contents + +1. [List of Tables](#list-of-tables) +2. [Acknowledgements](#acknowledgements) +3. [Compliance with OCP Tenets](#compliance-with-ocp-tenets) +4. [Overview](#overview) +5. [Integration with OCP Attestation Framework](#integration-with-ocp-attestation-framework) +6. [Terms and Definitions](#terms-and-definitions) +7. [Introduction](#introduction) +8. [Motivation](#motivation) +9. [Scope](#scope) +10. [CoRIM Profile Structure](#corim-profile-structure) +11. [OCP S.A.F.E. SFR Extension](#ocp-safe-sfr-extension) +12. [Security Considerations](#security-considerations) +13. [Implementation Guidelines](#implementation-guidelines) +14. [Appendix](#appendix) +15. [References](#references) + + +## List of Tables + +- Table 1: OCP S.A.F.E. SFR Map Fields +- Table 2: Issue Entry Structure +- Table 3: Device Category Mappings +- Table 4: Firmware Identifier Components + +## Acknowledgements + +The Contributors of this Specification would like to acknowledge the following: + +- Alex Tzonkov (AMD) +- Eric Eilertson (MSFT) +- Rob Wood (Tetrel Security) +- Fabrizio D'Amato (AMD) +- Isaac Assay (AMD) +- Matt King (NVIDIA) +- Thomas Fossati (Linaro) + +## Compliance with OCP Tenets + +### Openness +This specification is open and builds upon established IETF standards for CoRIM while extending them for OCP S.A.F.E. security review requirements. + +### Efficiency +This specification provides an efficient method for embedding security review findings within standardized reference integrity manifests, reducing overhead and complexity. + +### Impact +This specification enables standardized security review reporting across diverse datacenter hardware components, significantly improving security transparency and assessment capabilities. + +### Scale +This specification is designed to scale across various device categories including storage, network, GPU, CPU, APU, and BMC components within datacenter environments. + +### Sustainability +This specification promotes sustainable security practices by providing a standardized framework for ongoing security assessments and reviews. + +## Overview + +This profile extends the IETF Concise Reference Integrity Manifest (CoRIM) specification to support OCP S.A.F.E. Short Form Report (SFR) reporting. The extension enables security auditors and review providers to embed comprehensive security assessment findings directly within CoRIM structures, providing a standardized method for representing security review results alongside reference integrity measurements. + +The profile defines a dedicated extension to the CoRIM measurement-values-map that encapsulates security review metadata, firmware identifiers, vulnerability findings, and assessment scope information. This approach ensures that security review data maintains the same cryptographic integrity guarantees as the underlying reference measurements while providing rich contextual information for security evaluation. + +The primary objective is to establish a unified format for security review reporting that integrates seamlessly with existing CoRIM-based supply chain security infrastructure, enabling automated processing and verification of security assessment results across diverse platforms. + +## Terms and Definitions + +- **CoRIM**: Concise Reference Integrity Manifest +- **SFR**: Short Form Report +- **S.A.F.E.**: Security Appraisal Framework and Evaluation +- **CDDL**: Concise Data Definition Language +- **CBOR**: Concise Binary Object Representation +- **CVSS**: Common Vulnerability Scoring System +- **CWE**: Common Weakness Enumeration +- **CVE**: Common Vulnerabilities and Exposures +- **OID**: Object Identifier +- **SRP**: Security Review Provider + +## Introduction + +This specification details how security review providers should represent their assessment findings within a CoRIM extension profile. The extension leverages the existing CoRIM measurement-values-map structure to embed OCP S.A.F.E. SFR-specific data, ensuring compatibility with standard CoRIM processing tools while providing rich security assessment context. + +The specification defines the essential data structures required to represent comprehensive security review findings, including vulnerability assessments, firmware identification, scope definitions, and metadata about the review process itself. By embedding this information within CoRIM structures, the specification enables cryptographic verification of security review integrity alongside traditional reference measurements. + +## Motivation + +Modern datacenter security requires comprehensive assessment of firmware and hardware components across diverse vendor ecosystems. Traditional security review reporting lacks standardization, making it difficult for Cloud Service Providers (CSPs) and system integrators to consistently evaluate and compare security postures across different components. + +This specification addresses these challenges by: + +1. **Standardizing Security Review Reporting**: Providing a common format for representing security assessment findings across all device categories +2. **Enabling Cryptographic Verification**: Leveraging CoRIM's integrity protection mechanisms to ensure security review authenticity +3. **Facilitating Automated Processing**: Supporting machine-readable security assessment data for automated policy enforcement +4. **Improving Supply Chain Transparency**: Enabling verifiable security assessment results throughout the hardware supply chain + +## Scope + +This profile defines a CoRIM extension for representing OCP S.A.F.E. Security Framework Review findings. The extension is designed to be: + +- **Device Category Agnostic**: Supporting storage, network, GPU, CPU, APU, and BMC components +- **Review Framework Flexible**: Accommodating various security assessment methodologies and frameworks +- **Vulnerability Standard Compliant**: Supporting CVSS, CWE, and CVE standard vulnerability representations +- **Cryptographically Verifiable**: Maintaining CoRIM's integrity protection properties + +The profile focuses solely on the representation of security review findings and does not define: +- Security assessment methodologies +- Vulnerability discovery processes +- Remediation procedures +- Policy enforcement mechanisms + +## CoRIM Profile Structure + +The OCP S.A.F.E. SFR CoRIM profile is identified by the Object Identifier (OID) `1.3.6.1.4.1.42623.1.1` and extends the standard CoRIM measurement-values-map with security review specific data structures. + +### Profile Identification + +```cddl +ocp-safe-sfr-profile-oid = h'060A2B0601040182F4170101' ; OID 1.3.6.1.4.1.42623.1.1 in DER encoding +``` + +### Extension Integration + +The profile integrates with CoRIM through the measurement-values-map extension mechanism: + +```cddl +$$measurement-values-map-extension //= ( + &(ocp-safe-sfr: -1) => ocp-safe-sfr-map ; Private extension for OCP S.A.F.E. SFR +) +``` + +## OCP S.A.F.E. SFR Extension + +### Core Data Structure + +The OCP S.A.F.E. SFR extension is represented by the `ocp-safe-sfr-map` structure, which contains all security review findings and metadata: + +```cddl +ocp-safe-sfr-map = { + &(review-framework-version: 0) => tstr + &(report-version: 1) => tstr + &(completion-date: 2) => time + &(scope-number: 3) => integer + &(fw-identifiers: 4) => [ + fw-identifier ] + ? &(device-category: 5) => $device-category + ? &(issues: 6) => [ + issue-entry ] + * $$ocp-safe-sfr-map-ext +} +``` + +### Field Definitions + +#### Mandatory Fields + +**Table 1: OCP S.A.F.E. SFR Map Fields** + +| Field | Key | Type | Description | +|-------|-----|------|-------------| +| review-framework-version | 0 | tstr | Version of the OCP S.A.F.E. framework used for the review | +| report-version | 1 | tstr | Version of the specific security review report | +| completion-date | 2 | time | Date when the security review was completed | +| scope-number | 3 | integer | Numerical identifier for the review scope | +| fw-identifiers | 4 | array | Array of firmware identifier objects | + +#### Optional Fields + +| Field | Key | Type | Description | +|-------|-----|------|-------------| +| device-category | 5 | enum | Category of the device being reviewed | +| issues | 6 | array | Array of security issues identified during review | + +### Firmware Identifiers + +Firmware identifiers provide detailed information about the firmware components that were subject to security review: + +```cddl +fw-identifier = non-empty<{ + ? &(fw-version: 0) => version-map + ? &(fw-file-digests: 1) => digests-type + ? &(repo-tag: 2) => tstr + ? &(src-manifest: 3) => src-manifest +}> +``` + +**Table 4: Firmware Identifier Components** + +| Field | Key | Type | Description | +|-------|-----|------|-------------| +| fw-version | 0 | version-map | Semantic version information | +| fw-file-digests | 1 | digests-type | Cryptographic hashes of firmware files | +| repo-tag | 2 | tstr | Source repository tag or commit identifier | +| src-manifest | 3 | src-manifest | Source code manifest with file hashes | + +### Device Categories + +The specification supports the following device categories: + +**Table 3: Device Category Mappings** + +| Category | Value | Description | +|----------|-------|-------------| +| storage | 0 | Storage devices (SSDs, HDDs, etc.) | +| network | 1 | Network interface controllers and switches | +| gpu | 2 | Graphics processing units and accelerators | +| cpu | 3 | Central processing units | +| apu | 4 | Accelerated processing units | +| bmc | 5 | Baseboard management controllers | + +### Security Issues + +Security issues identified during the review are represented using the `issue-entry` structure: + +```cddl +issue-entry = { + &(title: 0) => tstr + &(cvss-score: 1) => tstr + &(cvss-vector: 2) => tstr + &(cwe: 3) => tstr + &(description: 4) => tstr + ? &(cvss-version: 5) => tstr + ? &(cve: 6) => tstr + * $$ocp-safe-issue-entry-ext +} +``` + +**Table 2: Issue Entry Structure** + +| Field | Key | Type | Required | Description | +|-------|-----|------|----------|-------------| +| title | 0 | tstr | Yes | Brief title describing the security issue | +| cvss-score | 1 | tstr | Yes | CVSS numerical score (e.g., "7.9") | +| cvss-vector | 2 | tstr | Yes | CVSS vector string | +| cwe | 3 | tstr | Yes | Common Weakness Enumeration identifier | +| description | 4 | tstr | Yes | Detailed description of the security issue | +| cvss-version | 5 | tstr | No | CVSS version used for scoring (default: "3.1") | +| cve | 6 | tstr | No | CVE identifier if assigned | + +### Source Manifest Support + +For comprehensive firmware tracking, the profile supports source code manifests: + +```cddl +src-manifest = { + &(manifest-digest: 0) => digests-type + &(manifest: 1) => [ + manifest-entry ] +} + +manifest-entry = { + &(filename: 0) => tstr + &(file-hash: 1) => digests-type +} +``` + +## Security Considerations + +### Cryptographic Integrity + +The OCP S.A.F.E. SFR extension inherits all cryptographic integrity protections provided by the underlying CoRIM structure. Security review findings are protected by the same digital signatures that protect reference measurements, ensuring: + +- **Authenticity**: Verification that security review data originates from authorized review providers +- **Integrity**: Detection of any tampering with security assessment findings +- **Non-repudiation**: Cryptographic proof of review provider responsibility for findings + +### Data Sensitivity + +Security review findings may contain sensitive information about vulnerabilities and system weaknesses. Implementers should consider: + +- **Access Control**: Restricting access to security review data based on organizational policies +- **Disclosure Coordination**: Following responsible disclosure practices for vulnerability information +- **Data Retention**: Implementing appropriate retention policies for security assessment data + +### Verification Requirements + +Consumers of OCP S.A.F.E. SFR CoRIM data MUST: + +1. Verify the cryptographic integrity of the CoRIM structure +2. Validate the review provider's authorization and credentials +3. Check the freshness and validity period of security review findings +4. Ensure compatibility between review framework versions and local policies + +## Implementation Guidelines + +### Review Provider Requirements + +Security Review Providers implementing this profile MUST: + +1. **Use Assigned OID**: Include the correct OCP S.A.F.E. SFR profile OID in all CoRIM structures +2. **Maintain Data Integrity**: Ensure all security review findings are accurately represented +3. **Follow CVSS Standards**: Use standardized CVSS scoring and vector formats +4. **Provide Complete Metadata**: Include all mandatory fields in the SFR extension +5. **Sign CoRIM Structures**: Apply appropriate cryptographic signatures to ensure integrity + +### Consumer Implementation + +Systems consuming OCP S.A.F.E. SFR CoRIM data SHOULD: + +1. **Validate Profile Compatibility**: Check for supported profile OID before processing +2. **Implement Policy Enforcement**: Define policies for handling different severity levels +3. **Support Multiple Categories**: Handle security review data for all supported device categories +4. **Maintain Audit Trails**: Log all security review data processing activities + +### Interoperability Considerations + +To ensure broad interoperability: + +- **Standard Compliance**: Adhere to all referenced IETF and industry standards +- **Version Compatibility**: Support backward compatibility with previous framework versions +- **Extension Points**: Use defined extension mechanisms for vendor-specific additions +- **Error Handling**: Implement robust error handling for malformed or incomplete data + +## Appendix + +### Profile CDDL + +```cddl +; Auditor CoRIM โ€“ Corim which embeds the SFR fields +; OCP S.A.F.E. SFR CoRIM Profile OID: 1.3.6.1.4.1.42623.1.1 + +$$measurement-values-map-extension //= ( + &(ocp-safe-sfr: -1) => ocp-safe-sfr-map ; Private extension for OCP S.A.F.E. SFR +) + +; Profile definition for OCP S.A.F.E. SFR CoRIM +ocp-safe-sfr-profile-oid = h'060A2B0601040182F4170101' ; OID 1.3.6.1.4.1.42623.1.1 in DER encoding + +ocp-safe-sfr-map = { + &(review-framework-version: 0) => tstr + &(report-version: 1) => tstr + &(completion-date: 2) => time + &(scope-number: 3) => integer + &(fw-identifiers: 4) => [ + fw-identifier ] + ? &(device-category: 5) => $device-category + ? &(issues: 6) => [ + issue-entry ] + * $$ocp-safe-sfr-map-ext +} + +issue-entry = { + &(title: 0) => tstr + &(cvss-score: 1) => tstr + &(cvss-vector: 2) => tstr + &(cwe: 3) => tstr + &(description: 4) => tstr + ? &(cvss-version: 5) => tstr + ? &(cve: 6) => tstr + * $$ocp-safe-issue-entry-ext +} + +fw-identifier = non-empty<{ + ? &(fw-version: 0) => version-map + ? &(fw-file-digests: 1) => digests-type + ? &(repo-tag: 2) => tstr + ? &(src-manifest: 3) => src-manifest +}> + +manifest-entry = { + &(filename: 0) => tstr + &(file-hash: 1) => digests-type +} + +src-manifest = { + &(manifest-digest: 0) => digests-type + &(manifest: 1) => [ + manifest-entry ] +} + +$device-category /= storage +$device-category /= network +$device-category /= gpu +$device-category /= cpu +$device-category /= apu +$device-category /= bmc + +storage = 0 +network = 1 +gpu = 2 +cpu = 3 +apu = 4 +bmc = 5 +``` + +### Example CoRIM with SFR Extension + +The following example demonstrates a complete CoRIM structure containing OCP S.A.F.E. SFR security review findings: + +```cbor-diag +/ tagged-corim-map / 501({ + / corim.id (0) / + 0: "acme-trap-audit-2025-08-03", + / corim.profile (3) / + 3: 111(h'060A2B0601040182F4170101'), / OID 1.3.6.1.4.1.42623.1.1 for OCP S.A.F.E. SFR profile / + / corim.entities (5) / + 5: [ + / audit-entity / { + / entity-name (0) / 0: "My Pentest Corporation", + / role (2) / 2: [ 1 ] / manifest-creator / + } + ], + / corim.tags / 1 : [ + / concise-mid-tag / 506( << + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : "acme-trap-review-comid-001" + }, + / comid.triples / 4 : { + / conditional-endorsement-triples / 10 : [ + [ / conditional-endorsement-triple-record / + [ / conditions array / + [ / *** stateful-environment-record *** / + / environment-map / { + / comid.class / 0 : { + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Trap" + } + }, + [ / claims-list / + / *** measurement-map *** / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / -43, / sha384 / + / hash-value / h'52047e070cddf496a7f77bf6a47792797e8ee90a149bb7555d08c5f93c5ca7ea46a63a7c99edaa1659e8afadfb9c6114' + ], + [ + / hash-alg-id / -44, / sha512 / + / hash-value / h'12a5b961a5eb7e548ed436fe7b5848d428bff908cb6ffcb47ec3ac1e2a43e0b8d1ff047d387fb0a940dc7b8b0014acf344364c43ab4de624dcd15f98bee552a5' + ] + ] + } + } + ] + ] + ], /*** end stateful-environment-records ***/ + [ /*** endorsements ***/ + [ /*** endorsed-triple-record ***/ + / environment-map / { + / class / 0 : { + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner Trap" + } + }, + [ / endorsement #1/ + / measurement-map / { + / comid.mval / 1 : { / measurement-values-map / + / ocp-safe-sfr / -1 : { + / 0: review-framework-version / 0: "1.1", + / 1: report-version / 1: "1.2", + / 2: completion-date / 2: 1(1687651200), + / 3: scope-number / 3: 1, + / 4: fw-identifiers / 4: [ + /fw-identifier / { + / 0: fw-version / 0: { + / 0: version / 0: "1.2.3", + / 1: version-scheme / 1: "semver" + } + } + ], + / 5: device-category / 5: 0, / 0: storage / + / 6: issues / 6: [ + / issue-entry / { + / 0: title / 0: "Memory corruption when reading record from SPI flash", + / 1: cvss-score / 1: "7.9", + / 2: cvss-vector / 2: "AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L", + / 3: cwe / 3: "CWE-111", + / 4: description / 4: "Due to insufficient input validation in the firmware, a local attacker who tampers with a configuration structure in SPI flash, can cause stack-based memory corruption.", + / 5: cvss-version / 5: "3.1" + }, + / issue-entry / { + / 0: title / 0: "Debug commands enable arbitrary memory read/write", + / 1: cvss-score / 1: "8.7", + / 2: cvss-vector / 2: "AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L", + / 3: cwe / 3: "CWE-222", + / 4: description / 4: "The firmware exposes debug command handlers that enable host-side drivers to read and write arbitrary regions of the device's SRAM.", + / 5: cvss-version / 5: "3.1", + / 6: cve / 6: "CVE-2014-10000" + } + ] + } + } + } + ] + ] + ] + ] + ] + } + } + >> ) + ] +}) +``` + +## References + +[1] "Concise Reference Integrity Manifest." IETF, Nov. 2020. Available: https://datatracker.ietf.org/doc/draft-ietf-rats-corim + +[2] "Concise Binary Object Representation (CBOR)." IETF, Dec. 2020. Available: https://datatracker.ietf.org/doc/html/rfc8949 + +[3] "Concise Data Definition Language (CDDL): A Notational Convention to Express Concise Binary Object Representation (CBOR) and JSON Data Structures." IETF, Jun. 2019. Available: https://datatracker.ietf.org/doc/html/rfc8610 + +[4] "Common Vulnerability Scoring System Version 3.1: Specification Document." FIRST, Jun. 2019. Available: https://www.first.org/cvss/v3.1/specification-document + +[5] "Common Weakness Enumeration (CWE)." MITRE Corporation. Available: https://cwe.mitre.org/ + +[6] "Common Vulnerabilities and Exposures (CVE)." MITRE Corporation. Available: https://cve.mitre.org/ + +[7] "OCP Security Assurance Framework for Enterprises (S.A.F.E.)." Open Compute Project Foundation. Available: https://www.opencompute.org/projects/safe + +[8] "Remote ATtestation procedureS (RATS) Architecture." IETF, Oct. 2021. Available: https://datatracker.ietf.org/doc/html/rfc9334 + +[9] "OCP Profile for IETF Entity Attestation Token (EAT)." Open Compute Project Foundation. Available: https://www.opencompute.org/projects/safe diff --git a/Documentation/corim_profile/examples/ocp-safe-sfr-fw-example.diag b/Documentation/corim_profile/examples/ocp-safe-sfr-fw-example.diag new file mode 100755 index 0000000..ec8c8c6 --- /dev/null +++ b/Documentation/corim_profile/examples/ocp-safe-sfr-fw-example.diag @@ -0,0 +1,104 @@ +/ tagged-corim-map / 501({ + / corim.id (0) / + 0: "acme-trap-audit-2025-08-03", + / corim.profile (3) / + 3: 111(h'060A2B0601040182F4170101'), / OID 1.3.6.1.4.1.42623.1.1 for OCP SAFE SFR profile / + / corim.entities (5) / + 5: [ + / audit-entity / { + / entity-name (0) / 0: "My Pentest Corporation", + / role (2) / 2: [ 1 ] / manifest-creator / + } + ], + / corim.tags / 1 : [ + / concise-mid-tag / 506( << + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : "acme-trap-review-comid-001" + }, + / comid.triples / 4 : { + / conditional-endorsement-triples / 10 : [ + [ / conditional-endorsement-triple-record / + [ / conditions array / + [ / *** stateful-environment-record *** / + / environment-map / { + / comid.class / 0 : { + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Trap" + } + }, + [ / claims-list / + / *** measurement-map *** / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / -43, / sha384 / + / hash-value / h'52047e070cddf496a7f77bf6a47792797e8ee90a149bb7555d08c5f93c5ca7ea46a63a7c99edaa1659e8afadfb9c6114' + ], + [ + / hash-alg-id / -44, / sha512 / + / hash-value / h'12a5b961a5eb7e548ed436fe7b5848d428bff908cb6ffcb47ec3ac1e2a43e0b8d1ff047d387fb0a940dc7b8b0014acf344364c43ab4de624dcd15f98bee552a5' + ] + ] + } + } + ] + ] + ], /*** end stateful-environment-records ***/ + [ /*** endorsements ***/ + [ /*** endorsed-triple-record ***/ + / environment-map / { + / class / 0 : { + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner Trap" + } + }, + [ / endorsement #1/ + / measurement-map / { + / comid.mval / 1 : { / measurement-values-map / + / ocp-safe-sfr / -1 : { + / 0: review-framework-version / 0: "1.1", + / 1: report-version / 1: "1.2", + / 2: completion-date / 2: 1(1687651200), + / 3: scope-number / 3: 1, + / 4: fw-identifiers / 4: [ + /fw-identifier / { + / 0: fw-version / 0: { + / 0: version / 0: "1.2.3", + / 1: version-scheme / 1: "semver" + } + } + ], + / 5: device-category / 5: 0, / 0: storage, 1: network, 2: gpu, 3: cpu, 4: apu, 5: bmc / + / 6: issues / 6: [ + / issue-entry / { + / 0: title / 0: "Memory corruption when reading record from SPI flash", + / 1: cvss-score / 1: "7.9", + / 2: cvss-vector / 2: "AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L", + / 3: cwe / 3: "CWE-111", + / 4: description / 4: "Due to insufficient input validation in the firmware, a local attacker who tampers with a configuration structure in SPI flash, can cause stack-based memory corruption.", + / 5: cvss-version / 5: "3.1" + }, + / issue-entry / { + / 0: title / 0: "Debug commands enable arbitrary memory read/write", + / 1: cvss-score / 1: "8.7", + / 2: cvss-vector / 2: "AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L", + / 3: cwe / 3: "CWE-222", + / 4: description / 4: "The firmware exposes debug command handlers that enable host-side drivers to read and write arbitrary regions of the device's SRAM.", + / 5: cvss-version / 5: "3.1", + / 6: cve / 6: "CVE-2014-10000" + } + ] + } + } + } + ] + ] + ] + ] + ] + } + } + >> ) + ] + } +) diff --git a/Documentation/corim_profile/ocp-safe-sfr-profile.cddl b/Documentation/corim_profile/ocp-safe-sfr-profile.cddl new file mode 100755 index 0000000..8cd2570 --- /dev/null +++ b/Documentation/corim_profile/ocp-safe-sfr-profile.cddl @@ -0,0 +1,59 @@ +; Auditor CoRIM โ€“ Corim which embeds the SFR fields +; OCP SAFE SFR CoRIM Profile OID: 1.3.6.1.4.1.42623.1.1 + +$$measurement-values-map-extension //= ( + &(ocp-safe-sfr: -1) => ocp-safe-sfr-map ; Private extension for OCP SAFE SFR +) + +ocp-safe-sfr-map = { + &(review-framework-version: 0) => tstr + &(report-version: 1) => tstr + &(completion-date: 2) => time + &(scope-number: 3) => integer + &(fw-identifiers: 4) => [ + fw-identifier ] + ? &(device-category: 5) => $device-category + ? &(issues: 6) => [ + issue-entry ] + * $$ocp-safe-sfr-map-ext +} + +issue-entry = { + &(title: 0) => tstr + &(cvss-score: 1) => tstr + &(cvss-vector: 2) => tstr + &(cwe: 3) => tstr + &(description: 4) => tstr + ? &(cvss-version: 5) => tstr + ? &(cve: 6) => tstr + * $$ocp-safe-issue-entry-ext +} + +fw-identifier = non-empty<{ + ? &(fw-version: 0) => version-map + ? &(fw-file-digests: 1) => digests-type + ? &(repo-tag: 2) => tstr + ? &(src-manifest: 3) => src-manifest +}> + +manifest-entry = { + &(filename: 0) => tstr + &(file-hash: 1) => digests-type +} + +src-manifest = { + &(manifest-digest: 0) => digests-type + &(manifest: 1) => [ + manifest-entry ] +} + +$device-category /= storage +$device-category /= network +$device-category /= gpu +$device-category /= cpu +$device-category /= apu +$device-category /= bmc + +storage = 0 +network = 1 +gpu = 2 +cpu = 3 +apu = 4 +bmc = 5 diff --git a/shortform_report-main/.gitignore b/shortform_report-main/.gitignore new file mode 100644 index 0000000..788c732 --- /dev/null +++ b/shortform_report-main/.gitignore @@ -0,0 +1,7 @@ +file_hashes.txt +testkey_ecdsa_p521.pub +testkey_p521.pem +__pycache__ +*.cbor +*.jws +*.json diff --git a/shortform_report-main/AUDITOR_GUIDE.md b/shortform_report-main/AUDITOR_GUIDE.md new file mode 100644 index 0000000..3c5704e --- /dev/null +++ b/shortform_report-main/AUDITOR_GUIDE.md @@ -0,0 +1,222 @@ +# CBOR CoRIM Human-Readable Inspector - Auditor Guide + +## Overview + +The CBOR CoRIM Human-Readable Inspector is a tool designed specifically for security auditors and reviewers who need to visually inspect and verify the contents of CBOR-encoded CoRIM (CBOR Object Representation of Information Model) files. This tool converts the binary CBOR format into a clear, human-readable report that shows all fields, their meanings, and their values. + +## Why This Tool is Needed + +CBOR is a binary format that is not human-readable in its raw form. While CBOR is efficient for machine processing, auditors need to be able to: + +- **Verify Data Integrity**: Ensure all expected fields are present and correctly formatted +- **Validate Content**: Check that security review data matches expectations +- **Audit Compliance**: Confirm that CoRIM files follow the OCP SAFE SFR profile specification +- **Troubleshoot Issues**: Identify problems in CoRIM generation or encoding + +## Quick Start + +### Basic Usage + +```bash +# Inspect a CoRIM file +python cbor_human_inspector.py my_security_review.cbor + +# Show raw CBOR data in addition to decoded structure (currently only supports unsigned CoRIM) +python cbor_human_inspector.py my_security_review.cbor --show-raw +``` + +## Understanding the Output + +The inspector provides a hierarchical view of the CoRIM structure with clear explanations: + +### 1. Top-Level Information +``` +๐Ÿ“Š Total CBOR size: 1286 bytes +๐Ÿ” Analysis timestamp: 2025-01-15 14:30:22 +โœ… CBOR Tag Found: 501 (CoRIM (CBOR Object Representation of Information Model)) +``` + +### 2. CoRIM Fields Breakdown + +The tool explains each field with its purpose: + +- **Field 0 (CoRIM ID)**: Unique identifier for this CoRIM +- **Field 1 (Tags)**: List of COMID tags containing the actual security review data +- **Field 3 (Profile)**: Should show OID 1.3.6.1.4.1.42623.1.1 for OCP SAFE SFR +- **Field 5 (Entities)**: Information about who created/maintains this CoRIM + +### 3. Security Review Data (SFR Extension) + +The most important section for auditors shows the actual security review findings: + +``` +๐Ÿ” SFR Extension (-1) Found! + ๐Ÿ“‹ SFR Data contains 7 fields: + + ๐Ÿ”ธ Field 0: Review Framework Version + ๐Ÿ”ธ Field 1: Report Version + ๐Ÿ”ธ Field 2: Completion Date + ๐Ÿ”ธ Field 3: Scope Number + ๐Ÿ”ธ Field 4: Firmware Identifiers + ๐Ÿ”ธ Field 5: Device Category + ๐Ÿ”ธ Field 6: Issues - List of security issues found +``` + +### 4. Security Issues Detail + +Each security issue is clearly displayed: + +``` +๐Ÿ”ด Issue #1: + Title: Buffer Overflow in Firmware Parser + CVSS Score: 9.1 + CVSS Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + CWE: CWE-787 + Description: A critical buffer overflow vulnerability... + CVE: CVE-2025-0123 +``` + +## Validation Checklist for Auditors + +When reviewing a CoRIM file, verify the following: + +### โœ… Structure Validation +- [ ] File has correct CoRIM CBOR tag (501) +- [ ] CoRIM contains required fields (0, 1, 3, 5) - **Inspector automatically checks this** +- [ ] Profile field (3) contains OID 1.3.6.1.4.1.42623.1.1 - **Inspector flags if missing** +- [ ] Tags field (1) contains at least one COMID tag (506) + +### โœ… SFR Extension Validation +- [ ] SFR extension (-1) is present in measurement values +- [ ] All required SFR fields are present (0-6) +- [ ] Completion date is properly encoded with CBOR timestamp tag +- [ ] Device category is a valid integer (0-5) +- [ ] Framework version matches expected value + +### โœ… Security Issues Validation +- [ ] Each issue has required fields: title, CVSS score, CVSS vector, CWE, description +- [ ] CVSS scores are valid (0.0-10.0) +- [ ] CVSS vectors follow proper format +- [ ] CWE identifiers are properly formatted +- [ ] CVE identifiers are present when applicable + +### โœ… Data Quality Validation +- [ ] Firmware identifiers contain vendor, product, version information +- [ ] Hash values are properly formatted (SHA384/SHA512) +- [ ] Entity information includes name and roles +- [ ] All text fields contain meaningful content + +## Common Issues and Troubleshooting + +### Missing Profile Field +``` +โŒ Profile should be OID tag (111), found: +``` +**Solution**: The CoRIM must include the OCP SAFE SFR profile OID. Check CoRIM generation code. + +### Incorrect Extension Value +``` +โŒ Missing SFR extension (-1) +``` +**Solution**: Verify that the SFR extension is using -1 (private extension) instead of 1029. + +### Invalid Date Encoding +``` +โŒ Should be datetime with CBOR tag 1 +``` +**Solution**: Completion dates must be encoded with CBOR timestamp tag (1). + +### Missing Required Fields +``` +โŒ Required CoRIM field X missing +``` +**Solution**: Check that all mandatory CoRIM fields are included during generation. + +## Device Category Reference + +The tool automatically translates device category numbers: + +- **0**: CPU (Central Processing Unit) +- **1**: GPU (Graphics Processing Unit) +- **2**: BMC (Baseboard Management Controller) +- **3**: NIC (Network Interface Controller) +- **4**: Storage (Storage devices) +- **5**: Other (Other device types) + +## CBOR Tag Reference + +Common CBOR tags you'll see in the output: + +- **Tag 1**: POSIX timestamp (seconds since epoch) +- **Tag 111**: Object Identifier (OID) +- **Tag 501**: CoRIM (CBOR Object Representation of Information Model) +- **Tag 506**: COMID (Concise Module Identifier) + +## Advanced Usage + +### Comparing Multiple CoRIMs + +```bash +# Inspect multiple files for comparison +python cbor_human_inspector.py review_v1.cbor > review_v1_analysis.txt +python cbor_human_inspector.py review_v2.cbor > review_v2_analysis.txt +diff review_v1_analysis.txt review_v2_analysis.txt +``` + +### Automated Validation + +The inspector can be integrated into automated validation workflows: + +```bash +# Check if inspection succeeds (exit code 0 = success) +if python cbor_human_inspector.py security_review.cbor; then + echo "CoRIM structure is valid" +else + echo "CoRIM has structural issues" +fi +``` + +### Raw Data Analysis + +Use `--show-raw` to see the actual CBOR bytes for deep analysis: + +```bash +python cbor_human_inspector.py review.cbor --show-raw +``` + +This shows the first 100 bytes of raw CBOR data, useful for debugging encoding issues. + +## Integration with Other Tools + +### With CDDL Validation + +The human inspector complements CDDL schema validation: + +1. **First**: Run CDDL validation to check schema compliance +2. **Then**: Use human inspector to verify content and meaning +3. **Finally**: Review the human-readable output for audit purposes + +### With JSON Reports + +The inspector works with CoRIM files generated from JSON security review reports: + +``` +JSON Report โ†’ CoRIM Generation โ†’ CBOR Encoding โ†’ Human Inspector +``` + +## Best Practices for Auditors + +1. **Always inspect the profile field** - Ensure it contains the correct OCP SAFE SFR OID +2. **Verify issue count matches expectations** - Check that all reported issues are present +3. **Validate timestamps** - Ensure completion dates are reasonable and properly encoded +4. **Check firmware identifiers** - Verify they match the actual firmware being reviewed +5. **Review CVSS scores** - Ensure they align with the severity of described issues +6. **Examine entity information** - Confirm the Security Review Provider is correctly identified + +## Support and Troubleshooting + +If you encounter issues with the inspector: + +1. **Check file format**: Ensure the file is a valid CBOR file +2. **Verify file size**: Empty or corrupted files will cause errors +3. **Review error messages**: The tool provides detailed error information diff --git a/shortform_report-main/OcpReportLib.py b/shortform_report-main/OcpReportLib.py index d89928f..6ec8cc7 100644 --- a/shortform_report-main/OcpReportLib.py +++ b/shortform_report-main/OcpReportLib.py @@ -1,22 +1,26 @@ """ -A simple library for generating the short-form vendor security review report. +A library for generating Security Review Short-Form Reports (SFR) in both JSON and CoRIM formats. This script is intended to be used by Security Review Providers who are participating in the Open Compute Project's Firmware Security Review Framework. The script complies with version 0.3 (draft) of the Security Review Framework -document. +document and supports the new CoRIM (CBOR) format. More details about the OCP review framework can be found here: * https://www.opencompute.org/wiki/Security For example usage of this script, refer to the following: - * example_generate.py: - Demonstrates how to generate, sign and verify the JSON report. - * sample_report.json - An example JSON report that was created by this script. - -Author: Jeremy Boone, NCC Group -Date : June 5th, 2023 + * example_gen_sign_verify.py: + Demonstrates how to generate, sign and verify reports in legacy JSON format. + * example_dual_format_generation.py + Demonstrates how to generate, sign and verify reports in both formats. + * samples/* + Example reports that were created by this library + +Author: Jeremy Boone, NCC Group (original), + Alex Tzonkov, AMD and Rob Wood, Tetrel Security (Extended for CoRIM support) +Date : June 5th, 2023 (original) + October 2025 (CoRIM extension) """ import time @@ -24,6 +28,13 @@ import jwt import base64 import hashlib +import cbor2 +import cwt +import logging +import prettyprinter +from datetime import datetime +from typing import Dict, List, Any + from cryptography.hazmat.primitives import serialization from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey @@ -56,12 +67,85 @@ 4096, # RSA 512 ) +# CoRIM specific constants +DEVICE_CATEGORIES = {"storage": 0, "network": 1, "gpu": 2, "cpu": 3, "apu": 4, "bmc": 5} + +# CBOR tags for CoRIM +CORIM_TAG = 501 +COMID_TAG = 506 + +# OCP SAFE SFR Profile OID: 1.3.6.1.4.1.42623.1.1 +# DER encoded OID bytes +OCP_SAFE_SFR_PROFILE_OID = bytes.fromhex("060A2B0601040182F4170101") + + +# Define the custom pretty-print function for CBORTag +@prettyprinter.register_pretty(cbor2.CBORTag) +def pretty_cbor_tag(value, ctx): + """ + Pretty-prints a cbor2.CBORTag object using the modern prettyprinter API. + """ + if isinstance(value.value, bytes): + try: + # attempt to handle a bytes object as a nested CBORTag + c = cbor2.loads(value.value) + except Exception: + c = value.value + else: + c = value.value + return prettyprinter.pretty_call(ctx, cbor2.CBORTag, (value.tag, c)) + + +class AzureKeyVaultSigner(cwt.Signer): + """ + A custom Signer class that uses Azure Key Vault for the actual signing + operation, adhering to the required interface of the python-cwt library. + """ + + def __init__(self, vault: str, kid: str, debug: bool = False): + self._signature_value = "" + super().__init__( + cose_key=None, protected=None, unprotected={4: kid.encode("utf-8")} + ) + if not debug: + logger = logging.getLogger("azure") + logger.setLevel(logging.ERROR) + + credential = DefaultAzureCredential(logging_enable=debug) + key_client = KeyClient(vault_url=vault, credential=credential) + key = key_client.get_key(kid) + self.crypto_client = CryptographyClient(key, credential=credential) + + if key.key.crv != "P-521": + print(f"Key must be a P-521 key, but is actually a {key.key.crv}.") + raise Exception("unsupported algorithm.") + + def sign(self, message: bytes) -> bytes: + """ + Calculates the message hash and delegates the signing operation to Azure Key Vault. + """ + digest = hashlib.sha512(message).digest() + + # Call Azure Key Vault to sign the digest + try: + self._signature_value = self.crypto_client.sign( + SignatureAlgorithm.es512, digest + ).signature + return self._signature_value + except Exception as e: + raise Exception(f"Failed to sign using Azure Key Vault: {e}") + + @property + def signature(self): + return self._signature_value + class ShortFormReport(object): def __init__(self, framework_ver: str = "1.1"): self.report = {} self.report["review_framework_version"] = f"{framework_ver}".strip() - self.signed_report = None + self.signed_json_report = None + self.signed_corim_report = None def add_device( self, @@ -183,19 +267,252 @@ def get_report_as_dict(self) -> dict: """Returns the short-form report as a Python dict.""" return self.report - def get_report_as_str(self) -> str: + def get_json_report_as_str(self) -> str: """Return the short-form report as a formatted and indented string.""" return json.dumps(self.get_report_as_dict(), indent=4) - def print_report(self) -> None: + def print_json_report(self) -> None: """Pretty-prints the short-form report""" print(self.get_report_as_str()) + ########################################################################### + # APIs for creating the CoRIM CBOR format methods + ########################################################################### + + def _convert_to_corim_structure(self) -> Dict[str, Any]: + """Convert internal JSON structure to CoRIM structure.""" + if "audit" not in self.report or "device" not in self.report: + raise ValueError("Report must have both device and audit information") + + # Parse completion date to Unix timestamp with CBOR tag 1 + date_str = self.report["audit"]["completion_date"] + try: + dt = datetime.strptime(date_str, "%Y-%m-%d") + completion_timestamp = cbor2.CBORTag(1, int(dt.timestamp())) + except ValueError: + raise ValueError(f"Invalid date format: {date_str}. Expected YYYY-MM-DD") + + # Build fw-identifier structure + fw_identifiers = [] + fw_id = {} + + # Add version information + if self.report["device"]["fw_version"]: + fw_id[0] = { # fw-version + 0: self.report["device"]["fw_version"], # version + 1: "semver", # version-scheme (default) + } + + # Add digests + digests = self._get_fw_digests() + if digests: + fw_id[1] = digests # fw-file-digests + + # Add repo tag + if self.report["device"]["repo_tag"]: + fw_id[2] = self.report["device"]["repo_tag"] # repo-tag + + # Add manifest if present + if "manifest" in self.report["device"]: + manifest_entries = [] + for entry in self.report["device"]["manifest"]: + manifest_entries.append( + { + 0: entry["file_name"], # filename + # file-hash (assuming SHA-512) + 1: [[-44, bytes.fromhex(entry["file_hash"])]], + } + ) + + # Calculate manifest digest + manifest_str = json.dumps( + self.report["device"]["manifest"], + sort_keys=False, + separators=(",", ":"), + ).encode("utf-8") + manifest_digest = hashlib.sha512(manifest_str).digest() + + fw_id[3] = { # src-manifest + 0: [[-44, manifest_digest]], # manifest-digest + 1: manifest_entries, # manifest + } + + fw_identifiers.append(fw_id) + + # Convert device category to integer + category_str = self.report["device"]["category"].lower() + device_category = None + for cat, val in DEVICE_CATEGORIES.items(): + if cat in category_str: + device_category = val + break + + # Convert issues + corim_issues = [] + for issue in self.report["audit"]["issues"]: + corim_issue = { + 0: issue["title"], # title + 1: issue["cvss_score"], # cvss-score + 2: issue["cvss_vector"], # cvss-vector + 3: issue["cwe"], # cwe + 4: issue["description"], # description + } + + # Add optional fields + if "cvss_version" in self.report["audit"]: + # cvss-version + corim_issue[5] = self.report["audit"]["cvss_version"] + + if issue.get("cve"): + corim_issue[6] = issue["cve"] # cve + + corim_issues.append(corim_issue) + + # Build the ocp-safe-sfr-map + sfr_map = { + # review-framework-version + 0: self.report["review_framework_version"], + 1: self.report["audit"]["report_version"], # report-version + 2: completion_timestamp, # completion-date + 3: self.report["audit"]["scope_number"], # scope-number + 4: fw_identifiers, # fw-identifiers + } + + if device_category is not None: + sfr_map[5] = device_category # device-category + + if corim_issues: + sfr_map[6] = corim_issues # issues + + return sfr_map + + def _build_corim_structure(self, sfr_map: Dict[str, Any]) -> Dict[str, Any]: + """Build the complete CoRIM structure with embedded SFR data.""" + + # Create the measurement-values-map with SFR extension + measurement_values = { + -1: sfr_map # ocp-safe-sfr extension + } + + # Create measurement-map for endorsement + endorsement_measurement_map = { + 1: measurement_values # mval + } + + # Create measurement-map for conditions (with digests) + condition_measurement_map = { + 1: { # mval + 2: self._get_fw_digests() # digests + } + } + + # Create endorsed-triple-record + endorsed_triple = [ + # environment-map + { + 0: { # class + 1: self.report["device"]["vendor"], # vendor + 2: self.report["device"]["product"], # model + } + }, + # endorsement (array of measurement-map) + [endorsement_measurement_map], + ] + + # Create stateful-environment-record for conditions + stateful_environment = [ + # environment-map + { + 0: { # class + 1: self.report["device"]["vendor"], # vendor + 2: self.report["device"]["product"], # model + } + }, + # claims-list (measurement-map array) + [condition_measurement_map], + ] + + # Create conditional-endorsement-triple-record + conditional_endorsement = [ + # conditions (stateful-environment-record array) + [stateful_environment], + # endorsements (endorsed-triple-record array) + [endorsed_triple], + ] + + # Create concise-mid-tag + comid = { + 1: { # tag-identity + # tag-id + 0: f"{self.report['device']['vendor'].lower().replace(' ', '-')}-review-comid-001" + }, + 4: { # triples + # conditional-endorsement-triples + 10: [conditional_endorsement] + }, + } + + # Create the main CoRIM structure + corim = { + 0: f"sfr-corim-{int(time.time())}", # id + 1: [cbor2.CBORTag(COMID_TAG, cbor2.dumps(comid))], # tags + 3: cbor2.CBORTag( + 111, OCP_SAFE_SFR_PROFILE_OID + ), # profile: OID 1.3.6.1.4.1.42623.1.1 + 5: [ # entities + { + 0: self.report["audit"]["srp"], # entity-name + 2: [1], # role: manifest-creator + } + ], + } + + return corim + + def _get_fw_digests(self) -> List[List]: + """Get firmware digests in CoRIM format.""" + digests = [] + if self.report["device"]["fw_hash_sha2_384"]: + digests.append( + [-43, bytes.fromhex(self.report["device"]["fw_hash_sha2_384"])] + ) + if self.report["device"]["fw_hash_sha2_512"]: + digests.append( + [-44, bytes.fromhex(self.report["device"]["fw_hash_sha2_512"])] + ) + return digests + + def get_report_as_corim_dict(self) -> Dict[str, Any]: + """Returns the report as a CoRIM-structured dictionary.""" + sfr_map = self._convert_to_corim_structure() + return self._build_corim_structure(sfr_map) + + def get_report_as_corim_cbor(self) -> bytes: + """Returns the report as CBOR-encoded CoRIM bytes.""" + corim_dict = self.get_report_as_corim_dict() + tagged_corim = cbor2.CBORTag(CORIM_TAG, corim_dict) + return cbor2.dumps(tagged_corim) + + def get_corim_report_as_str(self) -> str: + """return the report as human-readable CBOR diagnostic notation.""" + c = cbor2.loads(self.get_report_as_corim_cbor()) + return prettyprinter.pformat(c) + + def print_corim_report(self) -> None: + """Pretty-prints the short-form report""" + print(self.get_corim_report_as_str()) + ########################################################################### # APIs for signing the report ########################################################################### - def sign_report(self, priv_key: bytes, algo: str, kid: str) -> bool: + def get_signed_json_report(self) -> bytes: + """Returns the signed short form report (a JWS) as a bytes object. May + return a 'None' object if the report hasn't been signed yet. + """ + return self.signed_json_report + + def sign_json_report_pem(self, priv_key: bytes, algo: str, kid: str) -> bool: """Sign the JSON object to make a JSON Web Signature. Refer to RFC7515 for additional details of the JWS specification. @@ -239,7 +556,9 @@ def sign_report(self, priv_key: bytes, algo: str, kid: str) -> bool: if algo in ALLOWED_JWA_RSA_ALGOS: if pem.key_size not in ALLOWED_RSA_KEY_SIZES: print( - f"RSA key is too small: {pem.key_size}, must be one of: {ALLOWED_RSA_KEY_SIZES}" + f"RSA key is too small: {pem.key_size}, must be one of: { + ALLOWED_RSA_KEY_SIZES + }" ) return False @@ -257,18 +576,12 @@ def sign_report(self, priv_key: bytes, algo: str, kid: str) -> bool: jws_headers = {"kid": f"{kid}"} # Finally, we can sign the short-form report. - self.signed_report = jwt.encode( + self.signed_json_report = jwt.encode( self.get_report_as_dict(), key=priv_key, algorithm=algo, headers=jws_headers ) return True - def get_signed_report(self) -> bytes: - """Returns the signed short form report (a JWS) as a bytes object. May - return a 'None' object if the report hasn't been signed yet. - """ - return self.signed_report - - def sign_report_azure(self, vault: str, kid: str) -> bool: + def sign_json_report_azure(self, vault: str, kid: str, debug: bool = False) -> bool: """Sign the JSON object to make a JSON Web Signature. Refer to RFC7515 for additional details of the JWS specification. @@ -285,7 +598,10 @@ def sign_report_azure(self, vault: str, kid: str) -> bool: Returns True on success, and False on failure. """ - credential = DefaultAzureCredential() + if not debug: + logger = logging.getLogger("azure") + logger.setLevel(logging.ERROR) + credential = DefaultAzureCredential(logging_enable=debug) key_client = KeyClient(vault_url=vault, credential=credential) key = key_client.get_key(kid) crypto_client = CryptographyClient(key, credential=credential) @@ -306,38 +622,144 @@ def sign_report_azure(self, vault: str, kid: str) -> bool: .decode() .rstrip("="), } - to_sign = f'{token_components.get("header")}.{token_components.get("payload")}' + to_sign = f"{token_components.get('header')}.{token_components.get('payload')}" digest = hashlib.sha512(to_sign.encode()).digest() result = crypto_client.sign(SignatureAlgorithm.es512, digest) token_components["signature"] = ( base64.urlsafe_b64encode(result.signature).decode().rstrip("=") ) - self.signed_report = f'{token_components.get("header")}.{token_components.get("payload")}.{token_components["signature"]}' + self.signed_json_report = f"{token_components.get('header')}.{ + token_components.get('payload') + }.{token_components['signature']}" return True + def get_signed_corim_report(self) -> bytes: + """Returns the signed CoRIM report (COSE-Sign1).""" + return self.signed_corim_report + + def _sign_corim_report_internal(self, signer) -> bool: + """Sign the CoRIM report using COSE-Sign1 with the cwt library. + + Uses the cwt (CBOR Web Token) library for better COSE compatibility. + do not call directly, use either sign_corim_report_pem() or sign_corim_report_azure() + which provide appropriate signer modules. + """ + # Get CoRIM payload as claims (cwt expects claims, not raw payload) + corim_cbor = self.get_report_as_corim_cbor() + + # For COSE signing, we need to create claims structure + # The CoRIM data becomes the payload claim + claims = { + # Use a custom claim number for CoRIM data + -65537: corim_cbor # Custom claim for CoRIM payload + } + + # Sign using cwt library with the signer + signed_corim_report = cwt.encode_and_sign( + claims=claims, + signers=[signer], + tagged=True, # Use CBOR tag for COSE_Sign1 + ) + + self.signed_corim_report = signed_corim_report + return True + + def sign_corim_report_pem(self, priv_key: bytes, algo: str, kid: str) -> bool: + """Sign the CoRIM report using COSE-Sign1 with the cwt library. + + Uses the cwt (CBOR Web Token) library for better COSE compatibility. + """ + try: + # Load private key using cryptography + pem = serialization.load_pem_private_key( + priv_key, None, backend=default_backend() + ) + + # Map algorithm to COSE algorithm identifier + cose_alg = None + if ( + algo == "ES512" + and isinstance(pem, EllipticCurvePrivateKey) + and pem.curve.name == "secp521r1" + ): + cose_alg = -36 # ES512 + elif ( + algo == "ES384" + and isinstance(pem, EllipticCurvePrivateKey) + and pem.curve.name == "secp384r1" + ): + cose_alg = -35 # ES384 + elif algo == "PS512" and isinstance(pem, RSAPrivateKey): + cose_alg = -38 # PS512 + elif algo == "PS384" and isinstance(pem, RSAPrivateKey): + cose_alg = -37 # PS384 + else: + print(f"Unsupported algorithm/key combination: {algo} with {type(pem)}") + return False + + # Create Signer using cwt library + signer = cwt.Signer.from_pem(priv_key, alg=cose_alg, kid=kid) + + # sign and return result + return self._sign_corim_report_internal(signer) + + except Exception as e: + print(f"Error signing CoRIM with cwt: {e}") + return False + + def sign_corim_report_azure(self, vault: str, kid: str) -> bool: + """Sign the CoRIM report using COSE-Sign1 with the cwt library. + + Uses the cwt (CBOR Web Token) library for better COSE compatibility. + + This uses an Azure Key Vault key for signing. Login must be performed + using the Azure CLI (i.e., `az login`) before running this function. + + Only P-521 keys are supported currently. Any other key type will fail. + + vault: The Azure Key Vault URL to use. + kid: The Key ID to be included in the JWS header. This field will + be used to uniquely identify the unique SRP key that was used + to sign the report. It also is used as the key name in Azure. + + Returns True on success, and False on failure. + """ + try: + # Create Signer using Azure KeyVault + signer = AzureKeyVaultSigner(vault=vault, kid=kid) + + # sign and return result + return self._sign_corim_report_internal(signer) + + except Exception as e: + print(f"Error signing CoRIM with cwt/azure: {e}") + return False + ########################################################################### # APIs for verifying a signed report ########################################################################### - def get_signed_report_kid(self, signed_report: bytes) -> str: + def get_signed_json_report_kid(self, signed_json_report: bytes) -> str: """Read the unverified JWS header to extract the Key ID. This will be used to find the appropriate public key for verifying the report signature. - signed_report: A bytes object containing the signed report as a JWS + signed_json_report: A bytes object containing the signed report as a JWS object. Returns None if the 'kid' isn't present, otherwise return the 'kid' string. """ - header = jwt.get_unverified_header(signed_report) + header = jwt.get_unverified_header(signed_json_report) kid = header.get("kid", None) return kid - def verify_signed_report(self, signed_report: bytes, pub_key: bytes) -> dict: + def verify_signed_json_report( + self, signed_json_report: bytes, pub_key: bytes + ) -> dict: """Verify the signed report using the provided public key. - signed_report: A bytes object containing the signed report as a JWS + signed_json_report: A bytes object containing the signed report as a JWS object. pub_key: A bytes object containing the public key used to verify the signed report, which corresponds to the SRP's 'kid'. @@ -345,15 +767,35 @@ def verify_signed_report(self, signed_report: bytes, pub_key: bytes) -> dict: Returns a dictionary containing the decoded JSON short-form report payload. """ - decoded = jwt.decode(signed_report, pub_key, algorithms=ALLOWED_JWA_ALGOS) + decoded = jwt.decode(signed_json_report, pub_key, algorithms=ALLOWED_JWA_ALGOS) # verify additional contents of the report - if self.verify_report_contents(decoded) is not True: - raise Exception("Report contents failed to validate!") + if self.verify_json_report_contents(decoded) is not True: + raise Exception("JSON report contents failed to validate!") return decoded - def get_public_key_azure(self, vault, kid): + def verify_signed_corim_report( + self, signed_corim_report: bytes, pub_key: bytes, kid: str + ) -> bool: + """Verify the signed report using the provided public key. + + signed_corim_report: A bytes object containing the signed report as a CoRIM CBOR object. + pub_key: A bytes object containing the public key used to verify the signed report, which corresponds to the SRP's 'kid'. + + Returns a dictionary containing the decoded short-form report payload. + """ + try: + cwt.decode( + data=signed_corim_report, + keys=cwt.COSEKey.from_pem(pub_key, alg=-36, kid=kid), + ) # alg -36 is ES512 + return True + + except Exception as e: + raise Exception(f"CBOR report contents failed to validate! {e}") + + def get_public_key_azure(self, vault, kid, debug: bool = False): """Get the public key of an Azure KeyVault key. vault: The Azure Key Vault URL to use. @@ -361,7 +803,10 @@ def get_public_key_azure(self, vault, kid): Returns the public key in PEM format. """ - credential = DefaultAzureCredential() + if not debug: + logger = logging.getLogger("azure") + logger.setLevel(logging.ERROR) + credential = DefaultAzureCredential(logging_enable=debug) key_client = KeyClient(vault_url=vault, credential=credential) key = key_client.get_key(kid) pub = EllipticCurvePublicNumbers( @@ -373,29 +818,29 @@ def get_public_key_azure(self, vault, kid): serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo ).decode() - def verify_signed_report_azure( - self, vault: str, kid: str, signed_report: bytes + def verify_signed_json_report_azure( + self, signed_json_report: bytes, vault: str, kid: str, debug: bool = False ) -> dict: """Verify the signed report using an Azure KeyVault key. vault: The Azure Key Vault URL to use. kid: The Azure key name to use. - signed_report: A bytes object containing the signed report as a JWS + signed_json_report: A bytes object containing the signed report as a JWS object. Returns a dictionary containing the decoded JSON short-form report payload. """ - pubkey = self.get_public_key_azure(vault, kid) - decoded = jwt.decode(signed_report, pubkey, algorithms=ALLOWED_JWA_ALGOS) + pubkey = self.get_public_key_azure(vault, kid, debug) + decoded = jwt.decode(signed_json_report, pubkey, algorithms=ALLOWED_JWA_ALGOS) # verify additional contents of the report - if self.verify_report_contents(decoded) is not True: + if self.verify_json_report_contents(decoded) is not True: raise Exception("Report contents failed to validate!") return decoded - def verify_report_contents(self, decoded: dict) -> bool: + def verify_json_report_contents(self, decoded: dict) -> bool: """Verify the contents of the report wherever possible. decoded: A SFR report, decoded, with its signature assumed to have already been validated. @@ -406,7 +851,10 @@ def verify_report_contents(self, decoded: dict) -> bool: # At least one of the hashes must be present for this JSON to be valid. if ( "fw_hash_sha2_384" not in decoded["device"] - and "fw_hash_sha2_512" not in decoded["device"] + or len(decoded["device"]["fw_hash_sha2_384"]) == 0 + ) and ( + "fw_hash_sha2_512" not in decoded["device"] + or len(decoded["device"]["fw_hash_sha2_512"]) == 0 ): # Suppress this error for the one report that has no hash: # https://github.com/opencomputeproject/OCP-Security-SAFE/blob/main/Reports/CHIPS_Alliance/2023/Caliptra @@ -419,20 +867,26 @@ def verify_report_contents(self, decoded: dict) -> bool: "fw_hash_sha2_384" in decoded["device"] and len(decoded["device"]["fw_hash_sha2_384"]) != hashlib.sha384().digest_size * 2 + and len(decoded["device"]["fw_hash_sha2_384"]) != 0 ): l3 = len(decoded["device"]["fw_hash_sha2_384"]) print( - f"fw_hash_sha2_384 hash digest length must be {hashlib.sha384().digest_size*2} (found {l3})!" + f"fw_hash_sha2_384 hash digest length must be { + hashlib.sha384().digest_size * 2 + } (found {l3})!" ) return False if ( "fw_hash_sha2_512" in decoded["device"] and len(decoded["device"]["fw_hash_sha2_512"]) != hashlib.sha512().digest_size * 2 + and len(decoded["device"]["fw_hash_sha2_512"]) != 0 ): l5 = len(decoded["device"]["fw_hash_sha2_512"]) print( - f"fw_hash_sha2_512 hash digest length must be {hashlib.sha512().digest_size*2} (found {l5})!" + f"fw_hash_sha2_512 hash digest length must be { + hashlib.sha512().digest_size * 2 + } (found {l5})!" ) return False diff --git a/shortform_report-main/README.md b/shortform_report-main/README.md index e3cd422..ab67674 100644 --- a/shortform_report-main/README.md +++ b/shortform_report-main/README.md @@ -1,7 +1,8 @@ This repository contains an example implementation of the [Open Compute Project's](https://www.opencompute.org/) short-form report generator for [Vendor Security Reviews](https://drive.google.com/file/d/18m0q3ZFZarYJzZ5lOuPShyBKIx6QfGVA/view). -The short-form report is a JSON object which must be signed according to the JSON Web Signature ([RFC-7515](https://www.rfc-editor.org/rfc/rfc7515)) specification. +The short-form report is a JSON object which must be signed according to the JSON Web Signature ([RFC-7515](https://www.rfc-editor.org/rfc/rfc7515)) specification. +**NEW: CoRIM Format Support** - The library now also supports generating reports in CoRIM (Concise Reference Integrity Manifest) format using CBOR encoding, which complies with the OCP SAFE SFR CDDL schema. This provides a more compact binary format while maintaining full backward compatibility with existing JSON workflows. # Installation @@ -15,6 +16,31 @@ pip install -r requirements.txt The `example_gen_sign_verify.py` script demonstrates how to use the API exported by `OcpReportLib.py`, covering all use cases from report generation, to signing, and also the signature verification process. +## New CoRIM Methods + +In addition to the existing JSON methods, the following new methods are available for CoRIM format: + +```python +# Generate CoRIM format +corim_dict = report.get_report_as_corim_dict() # Returns CoRIM as Python dict +corim_cbor = report.get_report_as_corim_cbor() # Returns CBOR-encoded bytes + +# Sign CoRIM format (experimental) +report.sign_corim(private_key, "ES512", "key-id") # COSE-Sign1 signing +signed_corim = report.get_signed_corim_report() # Returns signed COSE +``` + +See `example_dual_format_generation.py` for a complete example of generating both JSON and CoRIM formats from the same data. + +## Testing and Validation + +The `tests/` directory contains comprehensive testing and validation scripts for both JSON and CoRIM formats: + +- **Test Scripts**: Validate CoRIM generation and CDDL compliance +- **Analysis Tools**: Debug CBOR structure and schema issues +- **Conversion Utilities**: Migrate existing JSON reports to CoRIM format + +See `tests/README.md` for detailed information about running tests and validation tools. ## Integration With Workflows @@ -28,6 +54,7 @@ Use of the API is straight forward: 2. Call `add_audit()` to add audit details to the report. 3. Call `add_issue()` any number of times to add vulnerability details to the report. 4. Call `sign_report()` (or `sign_report_azure()`) to sign the JSON report. +5. **NEW:** Optionally call `get_report_as_corim_cbor()` and `sign_corim()` to generate CoRIM format. When signing the report, the SRP must use an asymmetric signing key per those specified in the [Allowed Algorithms](#Allowed-Algorithms) section. [Azure Key Vault](https://azure.microsoft.com/en-us/products/key-vault) support is included as an example of more sophisticated key management, however this Python package does not attempt to solve the key management problem in all possible ways, and we encourage SRPs to protect the private key as appropriate. @@ -35,12 +62,12 @@ The SRP's public key and [Key ID](#Header-Fields), as well as the signed short-f ### Report Consumption (By OCP Members) -OCP members, such as cloud service providers, will be the primary consumers of these reports. Whenever an OCP member obtains a new firmware image from a vendor, they will pull the corresponding short-form report to decide whether the firmware image is safe to deploy into production. +OCP members, such as cloud service providers, will be the primary consumers of these reports. Whenever an OCP member obtains a new firmware image from a vendor, they will pull the corresponding short-form report to decide whether the firmware image is safe to deploy into production. 1. The OCP member will extract the `kid` header field from the report, and use it to lookup the correct SRP public key. -2. The OCP member will then use the public key to verify the report's signature, using the `verify_signed_report()` API. +2. The OCP member will then use the public key to verify the report's signature, using the `verify_signed_json_report()` API. 3. Once the report authenticity is proven, the firmware hash contained in the report (e.g., `fw_hash_sha2_384/512`) can be safely extracted. -4. This extracted hash can be compared to a locally-calculated hash of the vendor-provided firmware image. +4. This extracted hash can be compared to a locally-calculated hash of the vendor-provided firmware image. 5. If these hashes match, then the OCP member has now successfully verified that the firmware they wish to deploy has undergone a security audit. @@ -225,4 +252,3 @@ Note above that the RSA-PSS algorithms "PS384" and "PS512" are named after the h * 3072 bits (384 bytes) * 4096 bits (512 bytes) - diff --git a/shortform_report-main/cbor_human_inspector.py b/shortform_report-main/cbor_human_inspector.py new file mode 100644 index 0000000..ad4ad86 --- /dev/null +++ b/shortform_report-main/cbor_human_inspector.py @@ -0,0 +1,433 @@ +#!/usr/bin/env python3 +""" +Human-Readable CBOR CoRIM Inspector +This tool provides a clear, auditor-friendly view of CBOR CoRIM files. +""" + +import cbor2 +import sys +import os +from datetime import datetime +import json + +# Add parent directory to path to import OcpReportLib +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +def print_header(title, level=1): + """Print formatted headers for different levels.""" + if level == 1: + print(f"\n{'='*80}") + print(f" {title}") + print(f"{'='*80}") + elif level == 2: + print(f"\n{'-'*60}") + print(f" {title}") + print(f"{'-'*60}") + elif level == 3: + print(f"\n{'ยท'*40}") + print(f" {title}") + print(f"{'ยท'*40}") + +def format_bytes_display(data, max_length=32): + """Format bytes for human-readable display.""" + if len(data) <= max_length: + return data.hex() + else: + return f"{data[:max_length//2].hex()}...{data[-max_length//2:].hex()} ({len(data)} bytes total)" + +def format_oid_display(oid_bytes): + """Convert OID bytes to human-readable dotted notation.""" + try: + # Simple OID decoder for common cases + if oid_bytes.hex() == '060a2b0601040182f4170101': + return "1.3.6.1.4.1.42623.1.1 (OCP SAFE SFR Profile)" + elif oid_bytes.hex() == '2b0601040182f4170101': + return "1.3.6.1.4.1.42623.1.1 (OCP SAFE SFR Profile - DER content only)" + else: + return f"Unknown OID: {oid_bytes.hex()}" + except: + return f"Invalid OID: {oid_bytes.hex()}" + +def explain_cbor_tag(tag_num): + """Provide human-readable explanations for CBOR tags.""" + tag_explanations = { + 1: "POSIX timestamp (seconds since epoch)", + 111: "Object Identifier (OID)", + 501: "CoRIM (CBOR Object Representation of Information Model)", + 506: "COMID (Concise Module Identifier)", + # Add more as needed + } + return tag_explanations.get(tag_num, f"CBOR tag {tag_num}") + +def explain_corim_field(field_num): + """Explain CoRIM top-level fields.""" + corim_fields = { + 0: "CoRIM ID - Unique identifier for this CoRIM", + 1: "Tags - List of COMID tags containing the actual data", + 2: "Dependent RIMs - References to other CoRIMs (optional)", + 3: "Profile - Identifies the CoRIM profile being used", + 4: "RIM Validity - Validity period for this CoRIM (optional)", + 5: "Entities - Information about entities involved in creating this CoRIM" + } + return corim_fields.get(field_num, f"Unknown CoRIM field {field_num}") + +def explain_comid_field(field_num): + """Explain COMID fields.""" + comid_fields = { + 0: "Language - Language tag for text content (optional)", + 1: "Tag Identity - Unique identifier for this COMID tag", + 2: "Entities - Entities responsible for this COMID (optional)", + 3: "Linked Tags - References to other tags (optional)", + 4: "Triples - The actual attestation data (reference/endorsed/conditional values)" + } + return comid_fields.get(field_num, f"Unknown COMID field {field_num}") + +def explain_sfr_field(field_num): + """Explain SFR extension fields.""" + sfr_fields = { + 0: "Review Framework Version - Version of the OCP SAFE framework used", + 1: "Report Version - Version of this specific security review report", + 2: "Completion Date - When the security review was completed", + 3: "Scope Number - Numerical identifier for the review scope", + 4: "Firmware Identifiers - Information about the reviewed firmware", + 5: "Device Category - Type of device (CPU, GPU, BMC, etc.)", + 6: "Issues - List of security issues found during review", + 7: "Methodology - Review methodology used (whitebox, blackbox, etc.)", + 8: "Security Review Provider - Organization that performed the review" + } + return sfr_fields.get(field_num, f"Unknown SFR field {field_num}") + +def explain_device_category(category_num): + """Explain device category numbers.""" + categories = { + 0: "CPU (Central Processing Unit)", + 1: "GPU (Graphics Processing Unit)", + 2: "BMC (Baseboard Management Controller)", + 3: "NIC (Network Interface Controller)", + 4: "Storage (Storage devices)", + 5: "Other (Other device types)" + } + return categories.get(category_num, f"Unknown category {category_num}") + +def inspect_corim_structure(cbor_data, show_raw_data=False): + """Provide human-readable inspection of CoRIM structure.""" + + print_header("CBOR CoRIM Human-Readable Inspector", 1) + print(f"๐Ÿ“Š Total CBOR size: {len(cbor_data)} bytes") + print(f"๐Ÿ” Analysis timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + if show_raw_data: + print(f"๐Ÿ“‹ Raw CBOR data (first 100 bytes): {cbor_data[:100].hex()}") + + try: + # Decode the top-level CBOR + decoded = cbor2.loads(cbor_data) + + print_header("Top-Level Structure Analysis", 2) + + if isinstance(decoded, cbor2.CBORTag): + print(f"โœ… CBOR Tag Found: {decoded.tag} ({explain_cbor_tag(decoded.tag)})") + + if decoded.tag == 501: # CoRIM tag + print("โœ… This is a valid CoRIM structure") + corim_content = decoded.value + + if isinstance(corim_content, dict): + print(f"โœ… CoRIM contains {len(corim_content)} top-level fields") + + # First, validate required fields + print_header("Required Fields Validation", 3) + required_fields = { + 0: "CoRIM ID", + 1: "Tags", + 3: "Profile", + 5: "Entities" + } + + validation_passed = True + for field_num, field_name in required_fields.items(): + if field_num in corim_content: + print(f"โœ… Required field {field_num} ({field_name}) is present") + else: + print(f"โŒ MISSING REQUIRED FIELD {field_num} ({field_name})") + validation_passed = False + + if not validation_passed: + print(f"\nโš ๏ธ WARNING: This CoRIM is missing required fields and may not be valid!") + + print_header("CoRIM Fields Breakdown", 3) + + # Check for profile field specifically if missing + if 3 not in corim_content: + print(f"\n๐Ÿ”ธ Field 3: {explain_corim_field(3)}") + print(f" โŒ CRITICAL: Profile field is MISSING!") + print(f" โŒ CoRIM MUST include profile OID 1.3.6.1.4.1.42623.1.1 for OCP SAFE SFR") + print(f" โŒ This CoRIM will not validate against the OCP SAFE SFR profile") + + for field_num in sorted(corim_content.keys()): + field_value = corim_content[field_num] + print(f"\n๐Ÿ”ธ Field {field_num}: {explain_corim_field(field_num)}") + + if field_num == 0: # CoRIM ID + if isinstance(field_value, bytes): + print(f" Value: {format_bytes_display(field_value)}") + else: + print(f" Value: {field_value}") + + elif field_num == 1: # Tags + if isinstance(field_value, list): + print(f" ๐Ÿ“ Contains {len(field_value)} COMID tag(s)") + + for i, tag in enumerate(field_value): + print(f"\n ๐Ÿ“‹ COMID Tag #{i+1}:") + if isinstance(tag, cbor2.CBORTag) and tag.tag == 506: + print(f" โœ… Proper COMID tag (506)") + + try: + comid_content = cbor2.loads(tag.value) + inspect_comid_content(comid_content, indent=" ") + except Exception as e: + print(f" โŒ Error decoding COMID: {e}") + else: + print(f" โŒ Invalid COMID tag structure") + else: + print(f" โŒ Tags field should be a list, found: {type(field_value)}") + + elif field_num == 3: # Profile + if isinstance(field_value, cbor2.CBORTag) and field_value.tag == 111: + print(f" โœ… Proper OID tag (111)") + oid_display = format_oid_display(field_value.value) + print(f" ๐Ÿ†” Profile: {oid_display}") + else: + print(f" โŒ Profile should be OID tag (111), found: {type(field_value)}") + + elif field_num == 5: # Entities + if isinstance(field_value, list): + print(f" ๐Ÿ‘ฅ Contains {len(field_value)} entity/entities") + for i, entity in enumerate(field_value): + if isinstance(entity, dict): + print(f" Entity #{i+1}:") + if 0 in entity: # entity-name + print(f" Name: {entity[0]}") + if 1 in entity: # reg-id + print(f" Registration ID: {entity[1]}") + if 2 in entity: # role + roles = entity[2] + if isinstance(roles, list): + role_names = [] + for role in roles: + if role == 0: + role_names.append("tag-creator") + elif role == 1: + role_names.append("tag-maintainer") + else: + role_names.append(f"role-{role}") + print(f" Roles: {', '.join(role_names)}") + else: + print(f" โŒ Entities should be a list, found: {type(field_value)}") + + else: + print(f" Value type: {type(field_value)}") + if isinstance(field_value, (str, int, float)): + print(f" Value: {field_value}") + elif isinstance(field_value, bytes): + print(f" Value: {format_bytes_display(field_value)}") + + else: + print(f"โŒ CoRIM content should be a dictionary, found: {type(corim_content)}") + else: + print(f"โŒ Expected CoRIM tag (501), found tag {decoded.tag}") + else: + print(f"โŒ Expected CBOR tag at top level, found: {type(decoded)}") + + except Exception as e: + print(f"โŒ Error decoding CBOR: {e}") + import traceback + traceback.print_exc() + +def inspect_comid_content(comid_content, indent=""): + """Inspect COMID content in detail.""" + if not isinstance(comid_content, dict): + print(f"{indent}โŒ COMID content should be a dictionary") + return + + print(f"{indent}๐Ÿ“‹ COMID contains {len(comid_content)} fields") + + for field_num in sorted(comid_content.keys()): + field_value = comid_content[field_num] + print(f"\n{indent}๐Ÿ”น Field {field_num}: {explain_comid_field(field_num)}") + + if field_num == 1: # Tag Identity + if isinstance(field_value, dict): + if 0 in field_value: # tag-id + tag_id = field_value[0] + if isinstance(tag_id, bytes): + print(f"{indent} Tag ID: {format_bytes_display(tag_id)}") + else: + print(f"{indent} Tag ID: {tag_id}") + + elif field_num == 4: # Triples + if isinstance(field_value, dict): + print(f"{indent} ๐Ÿ“Š Triples structure:") + + if 10 in field_value: # conditional-endorsement-triples + cond_endorsements = field_value[10] + if isinstance(cond_endorsements, list): + print(f"{indent} ๐Ÿ”„ {len(cond_endorsements)} conditional endorsement(s)") + + for i, cond_endorsement in enumerate(cond_endorsements): + if isinstance(cond_endorsement, list) and len(cond_endorsement) == 2: + conditions, endorsements = cond_endorsement + print(f"\n{indent} ๐Ÿ“‹ Conditional Endorsement #{i+1}:") + print(f"{indent} Conditions: {len(conditions) if isinstance(conditions, list) else 'Invalid'}") + + if isinstance(endorsements, list): + print(f"{indent} Endorsements: {len(endorsements)}") + + for j, endorsement in enumerate(endorsements): + if isinstance(endorsement, list) and len(endorsement) == 2: + env, measurements = endorsement + print(f"\n{indent} ๐ŸŽฏ Endorsement #{j+1}:") + print(f"{indent} Environment: {type(env).__name__}") + + if isinstance(measurements, list): + print(f"{indent} ๐Ÿ“ {len(measurements)} measurement(s):") + + for k, measurement in enumerate(measurements): + if isinstance(measurement, dict) and 1 in measurement: + mval = measurement[1] + print(f"\n{indent} ๐Ÿ“Š Measurement #{k+1}:") + + if isinstance(mval, dict): + for ext_key, ext_value in mval.items(): + if ext_key == -1: # SFR extension + print(f"{indent} ๐Ÿ” SFR Extension (-1) Found!") + inspect_sfr_data(ext_value, indent + " ") + else: + print(f"{indent} Extension {ext_key}: {type(ext_value).__name__}") + +def inspect_sfr_data(sfr_data, indent=""): + """Inspect SFR extension data in detail.""" + if not isinstance(sfr_data, dict): + print(f"{indent}โŒ SFR data should be a dictionary") + return + + print(f"{indent}๐Ÿ“‹ SFR Data contains {len(sfr_data)} fields:") + + for field_num in sorted(sfr_data.keys()): + field_value = sfr_data[field_num] + print(f"\n{indent}๐Ÿ”ธ Field {field_num}: {explain_sfr_field(field_num)}") + + if field_num == 0: # Framework version + print(f"{indent} Version: {field_value}") + + elif field_num == 1: # Report version + print(f"{indent} Version: {field_value}") + + elif field_num == 2: # Completion date + if isinstance(field_value, datetime): + print(f"{indent} Date: {field_value.strftime('%Y-%m-%d %H:%M:%S UTC')}") + print(f"{indent} โœ… Properly encoded with CBOR timestamp tag") + else: + print(f"{indent} Date: {field_value} (Type: {type(field_value).__name__})") + print(f"{indent} โŒ Should be datetime with CBOR tag 1") + + elif field_num == 3: # Scope number + print(f"{indent} Scope: {field_value}") + + elif field_num == 4: # Firmware identifiers + if isinstance(field_value, dict): + print(f"{indent} ๐Ÿ“ฆ Firmware Info:") + for fw_key, fw_value in field_value.items(): + if fw_key == 0: # vendor + print(f"{indent} Vendor: {fw_value}") + elif fw_key == 1: # product + print(f"{indent} Product: {fw_value}") + elif fw_key == 2: # version + print(f"{indent} Version: {fw_value}") + elif fw_key == 3: # hash-sha384 + print(f"{indent} SHA384: {format_bytes_display(fw_value)}") + elif fw_key == 4: # hash-sha512 + print(f"{indent} SHA512: {format_bytes_display(fw_value)}") + else: + print(f"{indent} Field {fw_key}: {fw_value}") + + elif field_num == 5: # Device category + if isinstance(field_value, int): + print(f"{indent} Category: {field_value} ({explain_device_category(field_value)})") + else: + print(f"{indent} Category: {field_value} (Type: {type(field_value).__name__})") + + elif field_num == 6: # Issues + if isinstance(field_value, list): + print(f"{indent} ๐Ÿšจ {len(field_value)} security issue(s) found:") + + for i, issue in enumerate(field_value): + if isinstance(issue, dict): + print(f"\n{indent} ๐Ÿ”ด Issue #{i+1}:") + if 0 in issue: # title + print(f"{indent} Title: {issue[0]}") + if 1 in issue: # cvss-score + print(f"{indent} CVSS Score: {issue[1]}") + if 2 in issue: # cvss-vector + print(f"{indent} CVSS Vector: {issue[2]}") + if 3 in issue: # cwe + print(f"{indent} CWE: {issue[3]}") + if 4 in issue: # description + desc = issue[4] + if len(desc) > 100: + desc = desc[:100] + "..." + print(f"{indent} Description: {desc}") + if 5 in issue: # cve + print(f"{indent} CVE: {issue[5]}") + else: + print(f"{indent} Issues: {type(field_value).__name__}") + + elif field_num == 7: # Methodology + print(f"{indent} Method: {field_value}") + + elif field_num == 8: # Security Review Provider + print(f"{indent} Provider: {field_value}") + + else: + print(f"{indent} Value: {field_value} (Type: {type(field_value).__name__})") + +def main(): + """Main function for command-line usage.""" + if len(sys.argv) < 2: + print("Usage: python cbor_human_inspector.py [--show-raw]") + print("\nThis tool provides human-readable inspection of CBOR CoRIM files.") + print("Perfect for auditors who need to visually verify CoRIM contents.") + print("\nOptions:") + print(" --show-raw Show raw CBOR data in addition to decoded structure") + print("\nExample:") + print(" python cbor_human_inspector.py my_corim.cbor") + print(" python cbor_human_inspector.py my_corim.cbor --show-raw") + return 1 + + cbor_file = sys.argv[1] + show_raw = "--show-raw" in sys.argv + + if not os.path.exists(cbor_file): + print(f"โŒ Error: File '{cbor_file}' not found") + return 1 + + try: + with open(cbor_file, "rb") as f: + cbor_data = f.read() + + print(f"๐Ÿ“‚ Inspecting file: {cbor_file}") + inspect_corim_structure(cbor_data, show_raw_data=show_raw) + + print_header("Inspection Complete", 2) + print("โœ… Human-readable analysis finished successfully") + print("๐Ÿ“‹ This report can be used by auditors to verify CoRIM structure and content") + + return 0 + + except Exception as e: + print(f"โŒ Error reading file: {e}") + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/shortform_report-main/example_dual_format_generation.py b/shortform_report-main/example_dual_format_generation.py new file mode 100644 index 0000000..d83151a --- /dev/null +++ b/shortform_report-main/example_dual_format_generation.py @@ -0,0 +1,319 @@ +""" +Example script demonstrating dual-format SFR generation (JSON and CoRIM). + +This script shows how to use the ExtendedShortFormReport class to generate +Security Findings Reports in both the original JSON format and the new +CoRIM (CBOR) format that complies with the OCP SAFE SFR CDDL schema. + +Author: Extended from Jeremy Boone's original example +Date : January 2025 +""" + +from OcpReportLib import ShortFormReport +import traceback +import sys +import json +import hashlib +import os + +# Test key configuration (same as original example) +MY_PRIV_KEY = "testkey_p521.pem" +MY_PUB_KEY = "testkey_ecdsa_p521.pub" +MY_SIGN_ALGO = "ES512" +MY_KID = "Wile E Coyote" + +MY_VAULT = "https://MYVAULT.vault.azure.net/" +MY_KID_AZURE = "srp-ocp-key" +MY_PUB_KEY_AZURE = "testkey_ecdsa_p521.pub" + + +def generate_test_keys(): + """Generate test keys if they don't exist.""" + if not os.path.exists(MY_PRIV_KEY): + print("Generating test ECDSA P-521 key pair...") + os.system( + f"openssl ecparam -name secp521r1 -genkey -noout -out {MY_PRIV_KEY}") + os.system(f"openssl ec -in {MY_PRIV_KEY} -pubout -out {MY_PUB_KEY}") + print(f"Generated {MY_PRIV_KEY} and {MY_PUB_KEY}") + + +def main(): + print("=== OCP SAFE SFR Dual-Format Generation Example ===\n") + + # Generate test keys if needed + generate_test_keys() + + # Create the report object + rep = ShortFormReport(framework_ver="1.1") + + # Add device information (same API as original SFR generation library) + fw_hash_sha384 = "cd484defa77e8c3e4a8dd73926e32365ea0dbd01e4eff017f211d4629cfcd8e4890dd66ab1bded9be865cd1c849800d4" + fw_hash_sha512 = "84635baabc039a8c74aed163a8deceab8777fed32dc925a4a8dacfd478729a7b6ab1cb91d7d35b49e2bd007a80ae16f292be3ea2b9d9a88cb3cc8dff6a216988" + + rep.add_device( + "ACME Inc", # vendor name + "Roadrunner Trap", # product name + "storage", # device category + "release_v1_2_3", # repo tag + "1.2.3", # firmware version + fw_hash_sha384, # SHA-384 hash + fw_hash_sha512 # SHA-512 hash + ) + + # Add audit information + rep.add_audit( + "My Pentest Corporation", # SRP name + "whitebox", # Test methodology + "2023-06-25", # Test completion date + "1.2", # Report version + 1, # The OCP SAFE scope level + ) + + # Add security issues + rep.add_issue( + "Memory corruption when reading record from SPI flash", + "7.9", + "AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:H/A:L", + "CWE-111", + "Due to insufficient input validation in the firmware, a local" + " attacker who tampers with a configuration structure in" + " SPI flash, can cause stack-based memory corruption." + ) + + rep.add_issue( + "Debug commands enable arbitrary memory read/write", + "8.7", + "AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:L", + "CWE-222", + "The firmware exposes debug command handlers that enable host-side" + " drivers to read and write arbitrary regions of the device's" + " SRAM.", + cve="CVE-2014-10000" + ) + + print("=== JSON FORMAT OUTPUT ===") + print(rep.get_json_report_as_str()) + + print("\n=== CoRIM FORMAT OUTPUT ===") + generated_files = [] + try: + corim_cbor = rep.get_report_as_corim_cbor() + print(f"CoRIM CBOR bytes ({len(corim_cbor)} bytes):") + print(corim_cbor.hex()) + + print("\nCoRIM structure (Python dict):") + txt = rep.get_corim_report_as_str() + filename = "example_report_unsigned.cbor.txt" + with open(filename, "w") as f: + f.write(txt) + print(f"\nCoRIM saved to: {filename}") + generated_files.append(filename) + + # Save CoRIM to file + filename = "example_report_unsigned.cbor" + with open(filename, "wb") as f: + f.write(corim_cbor) + print(f"\nCoRIM saved to: {filename}") + generated_files.append(filename) + + except Exception as e: + print(f"Error generating CoRIM: {e}") + traceback.print_exc() + + print("\n=== SIGNING DEMONSTRATION ===") + + # Load private key + try: + with open(MY_PRIV_KEY, "rb") as f: + privkey = f.read() + except FileNotFoundError: + print(f"Private key file { + MY_PRIV_KEY} not found. Please generate keys first.") + return + + # Save JSON report (unsigned) + print("Saving JSON report...") + json_report = rep.get_json_report_as_str() + filename = "example_report_unsigned.json" + with open(filename, "w") as f: + f.write(json_report) + print(f"JSON report saved to: {filename}") + generated_files.append(filename) + + # Sign JSON format (test keys) + print("\nSigning JSON report...") + success_j1 = rep.sign_json_report_pem(privkey, MY_SIGN_ALGO, MY_KID) + if success_j1: + signed_json1 = rep.get_signed_json_report() + print(f"JSON JWS signature created ({len(signed_json1)} bytes)") + + # Save signed JSON + filename = "example_report_signed_testkey.jws" + with open(filename, "w") as f: + f.write(signed_json1.decode() if isinstance( + signed_json1, bytes) else signed_json1) + print(f"Signed JSON saved to: {filename}") + generated_files.append(filename) + else: + print("Failed to sign JSON report") + + # Sign JSON format (azure) + print("\nSigning JSON report...") + try: + success_j2 = rep.sign_json_report_azure(MY_VAULT, MY_KID_AZURE) + except: + success_j2 = False + if success_j2: + signed_json2 = rep.get_signed_json_report() + print(f"JSON JWS signature created ({len(signed_json2)} bytes)") + + # Save signed JSON + filename = "example_report_signed_azurekey.jws" + with open(filename, "w") as f: + f.write(signed_json2.decode() if isinstance( + signed_json2, bytes) else signed_json2) + print(f"Signed JSON saved to: {filename}") + generated_files.append(filename) + else: + print("Failed to sign JSON report") + print("Note: This test requires Azure Key Vault (see README.md)") + + # Sign CoRIM format (test key) + print("\nSigning CoRIM report (test key)...") + try: + success_c1 = rep.sign_corim_report_pem(privkey, MY_SIGN_ALGO, MY_KID) + if success_c1: + signed_corim1 = rep.get_signed_corim_report() + print( + f"CoRIM COSE-Sign1 signature created ({len(signed_corim1)} bytes)") + + # Save signed CoRIM + filename = "example_report_signed_testkey.cbor" + with open(filename, "wb") as f: + f.write(signed_corim1) + print(f"Signed CoRIM saved to: {filename}") + generated_files.append(filename) + else: + print("Failed to sign CoRIM report") + except Exception as e: + print(f"Error signing CoRIM: {e}") + + # Sign CoRIM format (azure) + print("\nSigning CoRIM report (azure)...") + try: + success_c2 = rep.sign_corim_report_azure( MY_VAULT, MY_KID_AZURE ) + except Exception as e: + print(f"Error signing CoRIM: {e}") + if success_c2: + signed_corim2 = rep.get_signed_corim_report() + print( + f"CoRIM COSE-Sign1 signature created ({len(signed_corim2)} bytes)") + + # Save signed CoRIM + filename = "example_report_signed_azurekey.cbor" + with open(filename, "wb") as f: + f.write(signed_corim2) + print(f"Signed CoRIM saved to: {filename}") + generated_files.append(filename) + else: + print("Failed to sign CoRIM report") + print("Note: This test requires Azure Key Vault (see README.md)") + + print("\n=== VERIFICATION DEMONSTRATION ===") + + # Verify JSON signature (test key) + if success_j1 and os.path.exists(MY_PUB_KEY): + try: + with open(MY_PUB_KEY, "rb") as f: + pubkey = f.read() + + print("Verifying JSON signature (test key)...") + rep.verify_signed_json_report(signed_json1, pubkey) + print("JSON signature verification: SUCCESS") + + except Exception as e: + print(f"JSON verification failed: {e}") + + # Verify JSON signature (azure) + if success_j2 and os.path.exists(MY_PUB_KEY_AZURE): + try: + with open(MY_PUB_KEY_AZURE, "rb") as f: + pubkey = f.read() + + print("Verifying JSON signature (azure)...") + rep.verify_signed_json_report(signed_json2, pubkey) + print("JSON signature verification: SUCCESS") + + except Exception as e: + print(f"JSON verification failed: {e}") + + # Verify CBOR signature (test key) + if success_c1 and os.path.exists(MY_PUB_KEY): + try: + with open(MY_PUB_KEY, "rb") as f: + pubkey = f.read() + + print("Verifying CoRIM CBOR signature (test key)...") + rep.verify_signed_corim_report(signed_corim1, pubkey, MY_KID) + print("CoRIM CBOR signature verification: SUCCESS") + + except Exception as e: + print(f"CoRIM CBOR verification failed: {e}") + + # Verify CBOR signature + if success_c2 and os.path.exists(MY_PUB_KEY_AZURE): + try: + with open(MY_PUB_KEY_AZURE, "rb") as f: + pubkey = f.read() + + print("Verifying CoRIM CBOR signature (azure)...") + rep.verify_signed_corim_report(signed_corim2, pubkey, MY_KID_AZURE) + print("CoRIM CBOR signature verification: SUCCESS") + + except Exception as e: + print(f"CoRIM CBOR verification failed: {e}") + + print("\n=== FORMAT COMPARISON ===") + + # Compare file sizes + files_info = [] + for filename in generated_files: + if os.path.exists(filename): + size = os.path.getsize(filename) + files_info.append((filename, size)) + + if files_info: + print("File size comparison:") + for filename, size in files_info: + print(f" {filename}: {size} bytes") + + print("\n=== SUMMARY ===") + if success_j1: + print("โœ“ ", end='') + else: + print("x ", end='') + print("JSON format: Backward compatible, uses JWS signing (test key)") + if success_j2: + print("โœ“ ", end='') + else: + print("x ", end='') + print("JSON format: Backward compatible, uses JWS signing (azure key)") + if success_c1: + print("โœ“ ", end='') + else: + print("x ", end='') + print("CoRIM format: New CBOR format, uses COSE-Sign1 signing (test key)") + if success_c2: + print("โœ“ ", end='') + else: + print("x ", end='') + print("CoRIM format: New CBOR format, uses COSE-Sign1 signing (azure key)") + + print("\nFiles generated:") + for filename in generated_files: + if os.path.exists(filename): + print(f" {filename}") + + +if __name__ == "__main__": + main() diff --git a/shortform_report-main/example_gen_sign_verify.py b/shortform_report-main/example_gen_sign_verify.py index 08f7b3b..2efe138 100644 --- a/shortform_report-main/example_gen_sign_verify.py +++ b/shortform_report-main/example_gen_sign_verify.py @@ -13,7 +13,7 @@ # Hardcoding these is crude, but whatever, this is just an example script to -# show how it might work in field. +# show how it might work in field. # # To quickly get up and running, you can use these openssl commands to generate the keypair: # $ openssl genrsa -out testkey_rsa3k.pem 3072 @@ -27,8 +27,8 @@ #MY_SIGN_ALGO = "PS512" MY_SIGN_ALGO = "ES512" -# XXX: Note to SRPs: You must include a 'kid' header to uniquely identify your -# signing key. +# XXX: Note to SRPs: You must include a 'kid' header to uniquely identify your +# signing key. MY_KID = "Wile E Coyote" ############################################################################### @@ -42,7 +42,8 @@ # XXX: Note to SRP: if this is a certification for a source code deliverable rather # than a binary deliverable, then uncomment this section and provide the file # input file generated as: -# "find . -type f -exec sha512sum {} \; > file_hashes.txt" +# "find . -type f -exec sha512sum {} \; | sort -k 2 > file_hashes.txt" +# The sorting step above is redundant with the sort below, but may make diffs easier # manifest = [] # with open("file_hashes.txt") as hashfile: # for line in hashfile: @@ -62,9 +63,8 @@ # XXX - # Add vendor device information -# XXX: Note to SRP: This is where you must calculate the hash of the firmware +# XXX: Note to SRP: This is where you must calculate the hash of the firmware # image that you tested. fw_hash_sha384 = "cd484defa77e8c3e4a8dd73926e32365ea0dbd01e4eff017f211d4629cfcd8e4890dd66ab1bded9be865cd1c849800d4" fw_hash_sha512 = "84635baabc039a8c74aed163a8deceab8777fed32dc925a4a8dacfd478729a7b6ab1cb91d7d35b49e2bd007a80ae16f292be3ea2b9d9a88cb3cc8dff6a216988" @@ -77,7 +77,7 @@ # fw_hash_sha384 fw_hash_sha384, # fw_hash_sha512 - fw_hash_sha512 + fw_hash_sha512, # manifest # optional: comment if not using ) @@ -86,9 +86,9 @@ "My Pentest Corporation", # SRP name "whitebox", # Test methodology "2023-06-25", # Test completion date - "1.2", # Report version + "1.2", # Report version 1, # The OCP SAFE scope level - ) +) # Add issue details. rep.add_issue("Memory corruption when reading record from SPI flash", @@ -112,25 +112,25 @@ ) # Print the short form report to console -print( "The short-form report:" ) -print( rep.get_report_as_str() ) +print("The short-form report:") +print(rep.get_json_report_as_str()) # Sign the short-form report (as a JWS) and print the signed report to the console print("\n\n") with open(MY_PRIV_KEY, "rb") as f: privkey = f.read() -success = rep.sign_report( privkey, MY_SIGN_ALGO, MY_KID ) -# XXX: note that sign_report_azure() could be used here instead if using the +success = rep.sign_json_report_pem(privkey, MY_SIGN_ALGO, MY_KID) +# XXX: note that sign_json_report_azure() could be used here instead if using the # Azure Key Vault for key management. In that case use the following: -# rep.sign_report_azure( MY_KEYVAULT_URL, MY_KID ) +# success = rep.sign_json_report_azure(MY_KEYVAULT_URL, MY_KID) if not success: - print( "Error encountered while signing short-form report" ) + print("Error encountered while signing short-form report") sys.exit(1) print("The corresponding signed JWS:") -signed_report = rep.get_signed_report() -print( signed_report ) +signed_json_report = rep.get_signed_json_report() +print(signed_json_report) ############################################################################### # Verify the signature @@ -139,18 +139,18 @@ # Step 1. Read the JWS header and ensure we have the correct key for the kid. print("\n\n") print("Checking the signed header:") -kid = rep.get_signed_report_kid( signed_report ) +kid = rep.get_signed_json_report_kid(signed_json_report) if kid is None: - print( "kid is not present in JWS header." ) + print("kid is not present in JWS header.") sys.exit(1) -# XXX: Note for consumers of the short-form report: This is where you must +# XXX: Note for consumers of the short-form report: This is where you must # lookup the correct key that corresponds to the kid. if kid != MY_KID: - print( "Unknown kid in JWS header." ) + print("Unknown kid in JWS header.") sys.exit(1) else: - print( f"Found the correct kid='{kid}'" ) + print(f"Found the correct kid='{kid}'") # Step 2. Read the public key print("\n\n") @@ -159,18 +159,18 @@ pubkey = f.read() try: - decoded = rep.verify_signed_report( signed_report, pubkey ) - # XXX: note that verify_signed_report_azure() could be used here for online + decoded = rep.verify_signed_json_report( signed_json_report, pubkey ) + # XXX: note that verify_signed_json_report_azure() could be used here for online # validation by the SRP, but the above is more appropriate for the OCP # member once the public key is published. Azure-based public keys are # retreived using get_public_key_azure()). # If testing online validation replace the above with the following: - # rep.verify_signed_report_azure( MY_KEYVAULT_URL, MY_KID, signed_report ) - print( "Success!" ) - print( "\n\n" ) - print( "Decoded report:" ) - print( decoded ) + # decoded = rep.verify_signed_json_report_azure( MY_KEYVAULT_URL, MY_KID, signed_json_report) + print("Success!") + print("\n\n") + print("Decoded report:") + print(decoded) except Exception: - print( "Error during JWS decoding:" ) + print("Error during JWS decoding:") traceback.print_exc() diff --git a/shortform_report-main/requirements.txt b/shortform_report-main/requirements.txt index 1601fce..b69ae47 100644 --- a/shortform_report-main/requirements.txt +++ b/shortform_report-main/requirements.txt @@ -3,3 +3,8 @@ pyjwt[crypto] azure-core azure-keyvault azure-identity + +# New dependencies for CoRIM support +cbor2>=5.4.0 +cwt>=1.5.0 +prettyprinter diff --git a/shortform_report-main/tests/README.md b/shortform_report-main/tests/README.md new file mode 100644 index 0000000..844efb8 --- /dev/null +++ b/shortform_report-main/tests/README.md @@ -0,0 +1,121 @@ +# Testing and Validation Scripts + +This directory contains testing and validation scripts for the OCP SAFE SFR CoRIM implementation. + +## Scripts Overview + +### Core Testing Scripts + +- **`test_corim_generation.py`** - Comprehensive test suite for CoRIM generation functionality + - Tests basic JSON to CoRIM conversion + - Validates schema compliance + - Tests device category mapping + - Validates error handling + - Tests backward compatibility + +- **`test_cddl_validation.py`** - CDDL schema validation testing + - Generates test CoRIM and validates against CDDL schema + - Outputs diagnostic information for debugging + - Tests CoRIM signing functionality + +### Analysis and Debugging Tools + +- **`cbor_structure_analyzer.py`** - CBOR structure analysis tool + - Decodes CBOR data recursively + - Provides detailed structure validation + - Helps debug CDDL compliance issues + +- **`final_validation_summary.py`** - Comprehensive validation summary + - Complete end-to-end validation of CoRIM implementation + - Tests all aspects of CDDL compliance + - Provides detailed validation report + +### Utility Scripts + +- **`json_to_corim_converter.py`** - Migration utility for existing reports + - Converts existing JSON SFR reports to CoRIM format + - Supports single file and batch directory conversion + - Provides conversion statistics and error reporting + +## Running the Tests + +### Individual Test Scripts + +```bash +# Run comprehensive test suite +python tests/test_corim_generation.py + +# Run CDDL validation test +python tests/test_cddl_validation.py + +# Run final validation summary +python tests/final_validation_summary.py + +# Analyze CBOR structure (requires test_corim_output.cbor) +python tests/cbor_structure_analyzer.py +``` + +### Convert Existing Reports + +```bash +# Convert single JSON file +python tests/json_to_corim_converter.py path/to/report.json + +# Convert all JSON files in a directory +python tests/json_to_corim_converter.py path/to/reports/ -o path/to/output/ + +# Dry run to see what would be converted +python tests/json_to_corim_converter.py path/to/reports/ --dry-run +``` + +## Test Dependencies + +The test scripts require the following additional dependencies beyond the main library requirements: + +- `cryptography` - For key generation in signing tests +- `cbor2` - For CBOR encoding/decoding +- `cwt` - For COSE signing operations + +These are already included in the main `requirements.txt` file. + +## Expected Outputs + +### Successful Test Run + +When all tests pass, you should see output like: + +``` +โœ“ JSON generation: PASS +โœ“ CoRIM generation: PASS +โœ“ Top-level CoRIM structure: PASS +โœ“ SFR structure compliance: PASS +โœ“ All tests passed! CoRIM generation is working correctly. +``` + +### Validation Summary + +The final validation summary provides comprehensive compliance checking: + +``` +โœ“ JSON to CoRIM conversion: PASSED +โœ“ CBOR encoding: PASSED +โœ“ CDDL schema compliance: PASSED +โœ“ CoRIM tag structure: PASSED +โœ“ COMID tag structure: PASSED +โœ“ SFR extension mapping: PASSED +๐ŸŽ‰ CoRIM implementation is fully CDDL compliant! +``` + +## Troubleshooting + +If tests fail: + +1. Check that all dependencies are installed: `pip install -r requirements.txt` +2. Ensure you're running from the correct directory +3. Check the detailed error output for specific issues +4. Use the structure analyzer to debug CBOR format issues +5. Verify that the CDDL schema files are present and accessible + +## Integration with CI/CD + +These scripts are designed to be integrated with GitHub Actions workflows for automated validation of incoming reports in both JSON and CBOR formats. diff --git a/shortform_report-main/tests/cbor_structure_analyzer.py b/shortform_report-main/tests/cbor_structure_analyzer.py new file mode 100644 index 0000000..146beb8 --- /dev/null +++ b/shortform_report-main/tests/cbor_structure_analyzer.py @@ -0,0 +1,222 @@ +#!/usr/bin/env python3 +""" +CBOR Structure Analyzer for CoRIM validation. +This script decodes CBOR and provides detailed structure analysis. +""" + +import cbor2 +from datetime import datetime + +def decode_cbor_recursively(data, indent=0): + """Recursively decode CBOR data and format it for analysis.""" + prefix = " " * indent + + if isinstance(data, cbor2.CBORTag): + print(f"{prefix}CBORTag({data.tag}): {type(data.value).__name__}") + if isinstance(data.value, bytes): + print(f"{prefix} Raw bytes length: {len(data.value)}") + # Try to decode nested CBOR + try: + nested = cbor2.loads(data.value) + print(f"{prefix} Nested content:") + decode_cbor_recursively(nested, indent + 2) + except: + print(f"{prefix} Binary data (first 50 bytes): {data.value[:50].hex()}") + else: + decode_cbor_recursively(data.value, indent + 1) + elif isinstance(data, dict): + print(f"{prefix}Dict with {len(data)} keys:") + for key, value in data.items(): + print(f"{prefix} Key {key} ({type(key).__name__}):") + decode_cbor_recursively(value, indent + 2) + elif isinstance(data, list): + print(f"{prefix}List with {len(data)} items:") + for i, item in enumerate(data): + print(f"{prefix} Item {i}:") + decode_cbor_recursively(item, indent + 2) + elif isinstance(data, bytes): + print(f"{prefix}Bytes ({len(data)}): {data[:20].hex()}{'...' if len(data) > 20 else ''}") + elif isinstance(data, str): + print(f"{prefix}String: '{data}'") + elif isinstance(data, int): + if data == 1759435582: # Unix timestamp + dt = datetime.fromtimestamp(data) + print(f"{prefix}Integer: {data} (timestamp: {dt})") + else: + print(f"{prefix}Integer: {data}") + else: + print(f"{prefix}{type(data).__name__}: {data}") + +def analyze_corim_structure(): + """Analyze the generated CoRIM structure.""" + print("=== CoRIM Structure Analysis ===\n") + + try: + with open("test_corim_output.cbor", "rb") as f: + cbor_data = f.read() + + print(f"Total CBOR size: {len(cbor_data)} bytes") + print(f"First 50 bytes: {cbor_data[:50].hex()}\n") + + # Decode the CBOR + decoded = cbor2.loads(cbor_data) + + print("=== Decoded Structure ===") + decode_cbor_recursively(decoded) + + print("\n=== Structure Validation ===") + + # Validate top-level structure + if isinstance(decoded, cbor2.CBORTag) and decoded.tag == 501: + print("โœ“ Top-level CoRIM tag (501) is correct") + corim_content = decoded.value + + if isinstance(corim_content, dict): + print("โœ“ CoRIM content is a dictionary") + + # Check required fields + required_fields = {0: "id", 1: "tags", 3: "profile", 5: "entities"} + for field_num, field_name in required_fields.items(): + if field_num in corim_content: + print(f"โœ“ Field {field_num} ({field_name}) present") + else: + print(f"โœ— Field {field_num} ({field_name}) missing") + + # Validate profile field specifically + if 3 in corim_content: + profile = corim_content[3] + if isinstance(profile, cbor2.CBORTag) and profile.tag == 111: + print("โœ“ Profile field properly tagged with OID tag (111)") + profile_oid = profile.value + expected_oid = bytes.fromhex('060A2B0601040182F4170101') # OID 1.3.6.1.4.1.42623.1.1 + if profile_oid == expected_oid: + print("โœ“ Profile OID matches expected value (1.3.6.1.4.1.42623.1.1)") + else: + print(f"โœ— Profile OID mismatch. Expected: {expected_oid.hex()}, Got: {profile_oid.hex()}") + else: + print("โœ— Profile field not properly tagged or missing OID tag") + + # Analyze tags structure + if 1 in corim_content: + tags = corim_content[1] + if isinstance(tags, list) and len(tags) > 0: + print(f"โœ“ Tags field is a list with {len(tags)} items") + + first_tag = tags[0] + if isinstance(first_tag, cbor2.CBORTag) and first_tag.tag == 506: + print("โœ“ First tag has correct COMID tag (506)") + + # Decode the COMID content + try: + comid_content = cbor2.loads(first_tag.value) + print("โœ“ COMID content successfully decoded") + + # Check COMID structure + if isinstance(comid_content, dict): + print("โœ“ COMID content is a dictionary") + + if 1 in comid_content: # tag-identity + print("โœ“ COMID has tag-identity field") + + if 4 in comid_content: # triples + print("โœ“ COMID has triples field") + triples = comid_content[4] + + if isinstance(triples, dict) and 10 in triples: + print("โœ“ Triples has conditional-endorsement-triples field (10)") + + cond_endorsements = triples[10] + if isinstance(cond_endorsements, list): + print(f"โœ“ Conditional endorsements is a list with {len(cond_endorsements)} items") + + # Analyze the structure deeper + if len(cond_endorsements) > 0: + first_cond = cond_endorsements[0] + if isinstance(first_cond, list) and len(first_cond) == 2: + print("โœ“ First conditional endorsement has correct structure [conditions, endorsements]") + + conditions, endorsements = first_cond + + # Check conditions + if isinstance(conditions, list): + print(f"โœ“ Conditions is a list with {len(conditions)} items") + + # Check endorsements + if isinstance(endorsements, list): + print(f"โœ“ Endorsements is a list with {len(endorsements)} items") + + if len(endorsements) > 0: + first_endorsement = endorsements[0] + if isinstance(first_endorsement, list) and len(first_endorsement) == 2: + print("โœ“ First endorsement has correct structure [environment, measurements]") + + env, measurements = first_endorsement + if isinstance(measurements, list) and len(measurements) > 0: + first_measurement = measurements[0] + if isinstance(first_measurement, dict) and 1 in first_measurement: + mval = first_measurement[1] + if isinstance(mval, dict) and -1 in mval: + print("โœ“ Found SFR extension (-1) in measurement values") + + # Validate SFR data structure + sfr_data = mval[-1] + if isinstance(sfr_data, dict): + print(f" - SFR data contains {len(sfr_data)} fields") + if 0 in sfr_data: + print(f" - Framework version: {sfr_data[0]}") + if 1 in sfr_data: + print(f" - Report version: {sfr_data[1]}") + if 3 in sfr_data: + print(f" - Scope number: {sfr_data[3]}") + sfr_data = mval[-1] + + # Check SFR structure + sfr_fields = { + 0: "review-framework-version", + 1: "report-version", + 2: "completion-date", + 3: "scope-number", + 4: "fw-identifiers" + } + + for field_num, field_name in sfr_fields.items(): + if field_num in sfr_data: + print(f"โœ“ SFR field {field_num} ({field_name}) present") + else: + print(f"โœ— SFR field {field_num} ({field_name}) missing") + + # Check completion date format + if 2 in sfr_data: + completion_date = sfr_data[2] + if isinstance(completion_date, cbor2.CBORTag) and completion_date.tag == 1: + print("โœ“ Completion date has correct CBOR tag (1)") + timestamp = completion_date.value + dt = datetime.fromtimestamp(timestamp) + print(f" Date: {dt}") + elif isinstance(completion_date, datetime): + print("โœ“ Completion date properly decoded as datetime (CBOR tag 1 was present)") + print(f" Date: {completion_date}") + else: + print("โœ— Completion date missing CBOR tag (1)") + print(f" Found type: {type(completion_date)}, value: {completion_date}") + + except Exception as e: + print(f"โœ— Error decoding COMID content: {e}") + else: + print("โœ— First tag missing COMID tag (506)") + else: + print("โœ— Tags field is not a proper list") + else: + print("โœ— Tags field missing") + else: + print("โœ— CoRIM content is not a dictionary") + else: + print("โœ— Missing or incorrect top-level CoRIM tag") + + except Exception as e: + print(f"Error analyzing structure: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + analyze_corim_structure() diff --git a/shortform_report-main/tests/final_validation_summary.py b/shortform_report-main/tests/final_validation_summary.py new file mode 100644 index 0000000..d93125b --- /dev/null +++ b/shortform_report-main/tests/final_validation_summary.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python3 +""" +Final Validation Summary for CoRIM CDDL Compliance +This script provides a comprehensive summary of the CoRIM implementation and validation results. +""" + +import sys +import os +import cbor2 +import json +from datetime import datetime + +# Add parent directory to path to import OcpReportLib +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from OcpReportLib import ShortFormReport + +def print_section(title): + """Print a formatted section header.""" + print(f"\n{'='*60}") + print(f" {title}") + print(f"{'='*60}") + +def validate_corim_compliance(): + """Comprehensive validation of CoRIM CDDL compliance.""" + + print_section("CoRIM CDDL Compliance Validation Summary") + + # Create a comprehensive test report + report = ShortFormReport("1.1") + + # Add device information + report.add_device( + vendor="Example Vendor", + product="Example Product", + category="cpu", + repo_tag="v2.1.0", + fw_ver="2.1.0", + fw_hash_sha384="1234567890abcdef" * 6, # 48 bytes = 96 hex chars + fw_hash_sha512="fedcba0987654321" * 8, # 64 bytes = 128 hex chars + ) + + # Add audit information + report.add_audit( + srp="Example Security Review Provider", + methodology="whitebox", + date="2025-01-02", + report_ver="2.0", + scope_number=3, + cvss_ver="3.1" + ) + + # Add multiple test issues + report.add_issue( + title="Critical Memory Corruption", + cvss_score="9.8", + cvss_vec="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + cwe="CWE-787", + description="Buffer overflow in firmware parsing routine", + cve="CVE-2025-0002" + ) + + report.add_issue( + title="Information Disclosure", + cvss_score="5.3", + cvss_vec="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + cwe="CWE-200", + description="Sensitive data exposed in debug output" + ) + + print_section("1. JSON Report Generation") + print("โœ“ JSON report structure created successfully") + print(f"โœ“ Framework version: {report.report['review_framework_version']}") + print(f"โœ“ Device category: {report.report['device']['category']}") + print(f"โœ“ Issues count: {len(report.report['audit']['issues'])}") + + print_section("2. CoRIM Structure Generation") + try: + corim_dict = report.get_report_as_corim_dict() + print("โœ“ CoRIM dictionary structure generated successfully") + + # Validate top-level structure + required_fields = [0, 1, 3, 5] # id, tags, profile, entities + for field in required_fields: + if field in corim_dict: + print(f"โœ“ Required CoRIM field {field} present") + else: + print(f"โœ— Required CoRIM field {field} missing") + return False + + # Validate profile field specifically + if 3 in corim_dict: + profile = corim_dict[3] + if isinstance(profile, cbor2.CBORTag) and profile.tag == 111: + print("โœ“ Profile field properly tagged with OID tag (111)") + profile_oid = profile.value + expected_oid = bytes.fromhex('060A2B0601040182F4170101') # OID 1.3.6.1.4.1.42623.1.1 + if profile_oid == expected_oid: + print("โœ“ Profile OID matches expected value (1.3.6.1.4.1.42623.1.1)") + else: + print(f"โœ— Profile OID mismatch. Expected: {expected_oid.hex()}, Got: {profile_oid.hex()}") + return False + else: + print("โœ— Profile field not properly tagged or missing OID tag") + return False + + print_section("3. CBOR Encoding") + corim_cbor = report.get_report_as_corim_cbor() + print(f"โœ“ CBOR encoding successful ({len(corim_cbor)} bytes)") + + # Validate CBOR structure + decoded = cbor2.loads(corim_cbor) + if isinstance(decoded, cbor2.CBORTag) and decoded.tag == 501: + print("โœ“ Correct CoRIM CBOR tag (501)") + else: + print("โœ— Missing or incorrect CoRIM CBOR tag") + return False + + print_section("4. CDDL Schema Compliance") + + # Deep structure validation + corim_content = decoded.value + if not isinstance(corim_content, dict): + print("โœ— CoRIM content is not a dictionary") + return False + + # Validate tags structure + if 1 not in corim_content: + print("โœ— Missing tags field") + return False + + tags = corim_content[1] + if not isinstance(tags, list) or len(tags) == 0: + print("โœ— Tags field is not a proper list") + return False + + first_tag = tags[0] + if not (isinstance(first_tag, cbor2.CBORTag) and first_tag.tag == 506): + print("โœ— First tag missing COMID tag (506)") + return False + + print("โœ“ COMID structure properly tagged") + + # Decode and validate COMID content + try: + comid_content = cbor2.loads(first_tag.value) + + # Check COMID required fields + if 1 not in comid_content: + print("โœ— COMID missing tag-identity field") + return False + + if 4 not in comid_content: + print("โœ— COMID missing triples field") + return False + + triples = comid_content[4] + if not isinstance(triples, dict) or 10 not in triples: + print("โœ— COMID missing conditional-endorsement-triples") + return False + + print("โœ“ COMID structure compliant") + + # Validate conditional endorsement structure + cond_endorsements = triples[10] + if not isinstance(cond_endorsements, list) or len(cond_endorsements) == 0: + print("โœ— Invalid conditional endorsements structure") + return False + + first_cond = cond_endorsements[0] + if not (isinstance(first_cond, list) and len(first_cond) == 2): + print("โœ— Invalid conditional endorsement record structure") + return False + + conditions, endorsements = first_cond + + # Validate endorsements contain SFR data + if not isinstance(endorsements, list) or len(endorsements) == 0: + print("โœ— Invalid endorsements structure") + return False + + first_endorsement = endorsements[0] + if not (isinstance(first_endorsement, list) and len(first_endorsement) == 2): + print("โœ— Invalid endorsed triple record structure") + return False + + env, measurements = first_endorsement + if not isinstance(measurements, list) or len(measurements) == 0: + print("โœ— Invalid measurements structure") + return False + + first_measurement = measurements[0] + if not (isinstance(first_measurement, dict) and 1 in first_measurement): + print("โœ— Invalid measurement map structure") + return False + + mval = first_measurement[1] + if not (isinstance(mval, dict) and -1 in mval): + print("โœ— Missing SFR extension (-1)") + return False + + print("โœ“ SFR extension (-1) found in measurement values") + + # Validate SFR structure + sfr_data = mval[-1] + required_sfr_fields = { + 0: "review-framework-version", + 1: "report-version", + 2: "completion-date", + 3: "scope-number", + 4: "fw-identifiers" + } + + for field_num, field_name in required_sfr_fields.items(): + if field_num in sfr_data: + print(f"โœ“ SFR field {field_num} ({field_name}) present") + else: + print(f"โœ— SFR field {field_num} ({field_name}) missing") + return False + + # Validate completion date has proper CBOR tag 1 (decoded as datetime) + completion_date = sfr_data[2] + if isinstance(completion_date, datetime): + print("โœ“ Completion date properly encoded with CBOR tag 1") + else: + print(f"โœ— Completion date incorrect format: {type(completion_date)}") + return False + + # Validate device category mapping + if 5 in sfr_data: + device_category = sfr_data[5] + if isinstance(device_category, int) and 0 <= device_category <= 5: + print(f"โœ“ Device category properly mapped to integer: {device_category}") + else: + print(f"โœ— Invalid device category: {device_category}") + return False + + # Validate issues structure + if 6 in sfr_data: + issues = sfr_data[6] + if isinstance(issues, list): + print(f"โœ“ Issues properly structured as list with {len(issues)} items") + + # Validate first issue structure + if len(issues) > 0: + first_issue = issues[0] + required_issue_fields = [0, 1, 2, 3, 4] # title, cvss-score, cvss-vector, cwe, description + for field in required_issue_fields: + if field in first_issue: + print(f"โœ“ Issue field {field} present") + else: + print(f"โœ— Issue field {field} missing") + return False + else: + print("โœ— Issues not properly structured as list") + return False + + print("โœ“ All CDDL schema requirements satisfied") + + except Exception as e: + print(f"โœ— Error validating COMID content: {e}") + return False + + print_section("5. CoRIM Signing Test") + try: + # Generate test key for signing + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.backends import default_backend + + private_key = ec.generate_private_key(ec.SECP521R1(), default_backend()) + private_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + + if report.sign_corim_report_pem(private_pem, "ES512", "test-validation-key"): + signed_corim = report.get_signed_corim_report() + print(f"โœ“ CoRIM signing successful ({len(signed_corim)} bytes)") + print("โœ“ COSE-Sign1 format with cwt library") + else: + print("โœ— CoRIM signing failed") + return False + + except Exception as e: + print(f"โœ— Signing test failed: {e}") + return False + + print_section("6. Final Validation Summary") + print("โœ“ JSON to CoRIM conversion: PASSED") + print("โœ“ CBOR encoding: PASSED") + print("โœ“ CDDL schema compliance: PASSED") + print("โœ“ CoRIM tag structure: PASSED") + print("โœ“ COMID tag structure: PASSED") + print("โœ“ SFR extension mapping: PASSED") + print("โœ“ Date encoding (CBOR tag 1): PASSED") + print("โœ“ Device category mapping: PASSED") + print("โœ“ Issues structure: PASSED") + print("โœ“ COSE-Sign1 signing: PASSED") + + print(f"\n๐ŸŽ‰ CoRIM implementation is fully CDDL compliant!") + print(f"๐Ÿ“Š Generated CoRIM size: {len(corim_cbor)} bytes") + print(f"๐Ÿ” Signed CoRIM size: {len(signed_corim)} bytes") + + return True + + except Exception as e: + print(f"โœ— Error during validation: {e}") + import traceback + traceback.print_exc() + return False + +def main(): + """Main validation function.""" + print("CoRIM CDDL Compliance Validation") + print("OCP Security SAFE Framework") + print(f"Validation Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + success = validate_corim_compliance() + + if success: + print_section("VALIDATION RESULT: SUCCESS โœ…") + print("The CoRIM implementation successfully complies with the CDDL schema.") + print("Security Review Providers can now generate SFRs in CoRIM format.") + return 0 + else: + print_section("VALIDATION RESULT: FAILED โŒ") + print("The CoRIM implementation has compliance issues that need to be addressed.") + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/shortform_report-main/tests/json_to_corim_converter.py b/shortform_report-main/tests/json_to_corim_converter.py new file mode 100644 index 0000000..cd8964d --- /dev/null +++ b/shortform_report-main/tests/json_to_corim_converter.py @@ -0,0 +1,318 @@ +""" +JSON to CoRIM Conversion Utility + +This utility converts existing OCP SAFE SFR JSON reports to the new CoRIM format. +It handles the schema differences and provides migration capabilities for existing reports. + +Author: Extended from Jeremy Boone's original OcpReportLib.py +Date : January 2025 +""" + +import json +import sys +import os +import argparse +from typing import Dict, Any, Optional +from datetime import datetime + +# Add parent directory to path to import OcpReportLib +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from OcpReportLib import ShortFormReport + +class JsonToCorimConverter: + """Converter class for migrating JSON SFR reports to CoRIM format.""" + + def __init__(self): + self.warnings = [] + self.errors = [] + + def convert_json_file(self, json_file_path: str, output_cbor_path: Optional[str] = None) -> bool: + """Convert a JSON SFR file to CoRIM format. + + Args: + json_file_path: Path to the input JSON file + output_cbor_path: Optional path for output CBOR file + + Returns: + True if conversion successful, False otherwise + """ + try: + # Load JSON report + with open(json_file_path, 'r') as f: + json_data = json.load(f) + + # Convert to CoRIM + corim_data = self.convert_json_data(json_data) + + if not corim_data: + return False + + # Generate output filename if not provided + if output_cbor_path is None: + base_name = os.path.splitext(json_file_path)[0] + output_cbor_path = f"{base_name}_converted.cbor" + + # Save CBOR file + with open(output_cbor_path, 'wb') as f: + f.write(corim_data) + + print(f"โœ“ Converted {json_file_path} to {output_cbor_path}") + return True + + except Exception as e: + self.errors.append(f"Failed to convert {json_file_path}: {e}") + return False + + def convert_json_data(self, json_data: Dict[str, Any]) -> Optional[bytes]: + """Convert JSON data structure to CoRIM CBOR bytes. + + Args: + json_data: The JSON report data as a dictionary + + Returns: + CBOR bytes if successful, None if failed + """ + try: + # Validate JSON structure + if not self._validate_json_structure(json_data): + return None + + # Create ShortFormReport and populate it + rep = ShortFormReport( + framework_ver=json_data.get("review_framework_version", "1.1") + ) + + # Extract device information + device = json_data.get("device", {}) + rep.add_device( + vendor=device.get("vendor", "Unknown Vendor"), + product=device.get("product", "Unknown Product"), + category=device.get("category", "storage"), + repo_tag=device.get("repo_tag", ""), + fw_ver=device.get("fw_version", ""), + fw_hash_sha384=device.get("fw_hash_sha2_384", ""), + fw_hash_sha512=device.get("fw_hash_sha2_512", ""), + manifest=device.get("manifest") + ) + + # Extract audit information + audit = json_data.get("audit", {}) + rep.add_audit( + srp=audit.get("srp", "Unknown SRP"), + methodology=audit.get("methodology", "unknown"), + date=audit.get("completion_date", "1970-01-01"), + report_ver=audit.get("report_version", "1.0"), + scope_number=audit.get("scope_number", 1), + cvss_ver=audit.get("cvss_version", "3.1") + ) + + # Extract issues + issues = audit.get("issues", []) + for issue in issues: + rep.add_issue( + title=issue.get("title", "Unknown Issue"), + cvss_score=issue.get("cvss_score", "0.0"), + cvss_vec=issue.get("cvss_vector", ""), + cwe=issue.get("cwe", "CWE-000"), + description=issue.get("description", "No description"), + cve=issue.get("cve") + ) + + # Generate CoRIM CBOR + return rep.get_report_as_corim_cbor() + + except Exception as e: + self.errors.append(f"Conversion failed: {e}") + return None + + def _validate_json_structure(self, json_data: Dict[str, Any]) -> bool: + """Validate that the JSON has the expected SFR structure.""" + required_fields = ["device", "audit"] + missing_fields = [] + + for field in required_fields: + if field not in json_data: + missing_fields.append(field) + + if missing_fields: + self.errors.append(f"Missing required fields: {missing_fields}") + return False + + # Check device fields + device = json_data["device"] + device_warnings = [] + + if not device.get("vendor"): + device_warnings.append("Missing device vendor") + if not device.get("product"): + device_warnings.append("Missing device product") + if not device.get("fw_hash_sha2_384") and not device.get("fw_hash_sha2_512"): + device_warnings.append("Missing firmware hashes") + + # Check audit fields + audit = json_data["audit"] + audit_warnings = [] + + if not audit.get("srp"): + audit_warnings.append("Missing SRP name") + if not audit.get("completion_date"): + audit_warnings.append("Missing completion date") + if not audit.get("scope_number"): + audit_warnings.append("Missing scope number") + + # Add warnings + self.warnings.extend(device_warnings + audit_warnings) + + return True + + def convert_directory(self, input_dir: str, output_dir: Optional[str] = None) -> Dict[str, Any]: + """Convert all JSON files in a directory to CoRIM format. + + Args: + input_dir: Directory containing JSON files + output_dir: Optional output directory for CBOR files + + Returns: + Dictionary with conversion statistics + """ + if output_dir is None: + output_dir = input_dir + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + stats = { + "total_files": 0, + "converted": 0, + "failed": 0, + "skipped": 0 + } + + # Find all JSON files + json_files = [] + for root, dirs, files in os.walk(input_dir): + for file in files: + if file.endswith('.json') and not file.endswith('_converted.json'): + json_files.append(os.path.join(root, file)) + + stats["total_files"] = len(json_files) + + print(f"Found {len(json_files)} JSON files to convert...") + + for json_file in json_files: + try: + # Calculate relative path for output + rel_path = os.path.relpath(json_file, input_dir) + output_path = os.path.join(output_dir, rel_path) + output_path = os.path.splitext(output_path)[0] + "_converted.cbor" + + # Ensure output subdirectory exists + os.makedirs(os.path.dirname(output_path), exist_ok=True) + + # Convert file + if self.convert_json_file(json_file, output_path): + stats["converted"] += 1 + else: + stats["failed"] += 1 + + except Exception as e: + print(f"Error processing {json_file}: {e}") + stats["failed"] += 1 + + return stats + + def print_summary(self): + """Print conversion warnings and errors.""" + if self.warnings: + print("\nโš ๏ธ Warnings:") + for warning in self.warnings: + print(f" - {warning}") + + if self.errors: + print("\nโŒ Errors:") + for error in self.errors: + print(f" - {error}") + + +def main(): + parser = argparse.ArgumentParser( + description="Convert OCP SAFE SFR JSON reports to CoRIM format" + ) + parser.add_argument( + "input", + help="Input JSON file or directory containing JSON files" + ) + parser.add_argument( + "-o", "--output", + help="Output CBOR file or directory (default: same as input with _converted suffix)" + ) + parser.add_argument( + "-r", "--recursive", + action="store_true", + help="Process directories recursively" + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be converted without actually converting" + ) + + args = parser.parse_args() + + converter = JsonToCorimConverter() + + if os.path.isfile(args.input): + # Single file conversion + if args.input.endswith('.json'): + print(f"Converting single file: {args.input}") + + if args.dry_run: + print(f"Would convert {args.input} to CoRIM format") + return + + success = converter.convert_json_file(args.input, args.output) + converter.print_summary() + + if success: + print("โœ… Conversion completed successfully") + else: + print("โŒ Conversion failed") + sys.exit(1) + else: + print("Error: Input file must be a JSON file") + sys.exit(1) + + elif os.path.isdir(args.input): + # Directory conversion + print(f"Converting directory: {args.input}") + + if args.dry_run: + # Count JSON files + json_count = 0 + for root, dirs, files in os.walk(args.input): + json_count += len([f for f in files if f.endswith('.json')]) + print(f"Would convert {json_count} JSON files to CoRIM format") + return + + stats = converter.convert_directory(args.input, args.output) + converter.print_summary() + + print(f"\n๐Ÿ“Š Conversion Statistics:") + print(f" Total files found: {stats['total_files']}") + print(f" Successfully converted: {stats['converted']}") + print(f" Failed: {stats['failed']}") + print(f" Skipped: {stats['skipped']}") + + if stats['failed'] > 0: + print("โš ๏ธ Some conversions failed. Check errors above.") + sys.exit(1) + else: + print("โœ… All conversions completed successfully") + + else: + print(f"Error: Input path '{args.input}' does not exist") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/shortform_report-main/tests/test_cddl_validation.py b/shortform_report-main/tests/test_cddl_validation.py new file mode 100644 index 0000000..99c7c28 --- /dev/null +++ b/shortform_report-main/tests/test_cddl_validation.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +""" +Test script to validate CoRIM generation against CDDL schema. +This script generates a CoRIM and outputs diagnostic information to help debug CDDL compliance. +""" + +import sys +import os +import cbor2 +import json +from datetime import datetime + +# Add parent directory to path to import OcpReportLib +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from OcpReportLib import ShortFormReport + +def cbor_to_diagnostic(cbor_data): + """Convert CBOR data to a more readable diagnostic format.""" + try: + # Decode CBOR and pretty print the structure + decoded = cbor2.loads(cbor_data) + return json.dumps(decoded, indent=2, default=str) + except Exception as e: + return f"Error decoding CBOR: {e}" + +def test_corim_generation(): + """Test CoRIM generation and output diagnostic information.""" + print("=== Testing CoRIM Generation ===\n") + + # Create a test report + report = ShortFormReport("1.1") + + # Add device information + report.add_device( + vendor="Test Vendor", + product="Test Product", + category="cpu", + repo_tag="v1.0.0", + fw_ver="1.2.3", + fw_hash_sha384="a" * 96, # 48 bytes = 96 hex chars + fw_hash_sha512="b" * 128, # 64 bytes = 128 hex chars + ) + + # Add audit information + report.add_audit( + srp="Test SRP", + methodology="whitebox", + date="2025-01-02", + report_ver="1.0", + scope_number=2, + cvss_ver="3.1" + ) + + # Add a test issue + report.add_issue( + title="Test Issue", + cvss_score="7.5", + cvss_vec="CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", + cwe="CWE-200", + description="Test vulnerability description", + cve="CVE-2025-0001" + ) + + print("1. Generated JSON report structure:") + print(json.dumps(report.get_report_as_dict(), indent=2)) + print("\n" + "="*80 + "\n") + + print("2. Converting to CoRIM structure...") + try: + corim_dict = report.get_report_as_corim_dict() + print("CoRIM dictionary structure:") + print(json.dumps(corim_dict, indent=2, default=str)) + print("\n" + "="*80 + "\n") + + print("3. Generating CBOR...") + corim_cbor = report.get_report_as_corim_cbor() + print(f"Generated CBOR length: {len(corim_cbor)} bytes") + print(f"CBOR hex: {corim_cbor[:100].hex()}..." if len(corim_cbor) > 100 else f"CBOR hex: {corim_cbor.hex()}") + print("\n" + "="*80 + "\n") + + print("4. CBOR diagnostic representation:") + diagnostic = cbor_to_diagnostic(corim_cbor) + print(diagnostic) + print("\n" + "="*80 + "\n") + + # Save the CBOR for external validation + with open("test_corim_output.cbor", "wb") as f: + f.write(corim_cbor) + print("5. Saved CBOR to 'test_corim_output.cbor' for external validation") + + # Test signing (optional) + print("\n6. Testing CoRIM signing...") + try: + # Generate a test key for signing + from cryptography.hazmat.primitives import serialization + from cryptography.hazmat.primitives.asymmetric import ec + from cryptography.hazmat.backends import default_backend + + # Generate P-521 key for ES512 + private_key = ec.generate_private_key(ec.SECP521R1(), default_backend()) + private_pem = private_key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.PKCS8, + encryption_algorithm=serialization.NoEncryption() + ) + + # Try to sign + if report.sign_corim_report_pem(private_pem, "ES512", "test-key-001"): + signed_corim = report.get_signed_corim_report() + print(f"Successfully signed CoRIM! Signed length: {len(signed_corim)} bytes") + + # Save signed CoRIM + with open("test_corim_signed.cbor", "wb") as f: + f.write(signed_corim) + print("Saved signed CoRIM to 'test_corim_signed.cbor'") + else: + print("CoRIM signing failed, but generation succeeded") + + except Exception as e: + print(f"Signing test failed: {e}") + print("This is expected if cwt library is not available") + + return True + + except Exception as e: + print(f"Error generating CoRIM: {e}") + import traceback + traceback.print_exc() + return False + +def analyze_structure(): + """Analyze the generated structure against expected CDDL format.""" + print("\n=== Structure Analysis ===\n") + + try: + with open("test_corim_output.cbor", "rb") as f: + cbor_data = f.read() + + # Decode and analyze structure + decoded = cbor2.loads(cbor_data) + + print("Top-level structure analysis:") + if hasattr(decoded, 'tag') and decoded.tag == 501: + print("โœ“ Correct CoRIM CBOR tag (501) found") + corim_content = decoded.value + else: + print("โœ— Missing or incorrect CoRIM CBOR tag") + corim_content = decoded + + print(f"CoRIM content type: {type(corim_content)}") + + if isinstance(corim_content, dict): + print("CoRIM structure keys:", list(corim_content.keys())) + + # Check for required fields + required_fields = [0, 1, 5] # id, tags, entities + for field in required_fields: + if field in corim_content: + print(f"โœ“ Required field {field} present") + else: + print(f"โœ— Required field {field} missing") + + # Analyze tags structure + if 1 in corim_content: # tags + tags = corim_content[1] + print(f"Tags structure: {type(tags)}, length: {len(tags) if hasattr(tags, '__len__') else 'N/A'}") + + if isinstance(tags, list) and len(tags) > 0: + first_tag = tags[0] + if hasattr(first_tag, 'tag') and first_tag.tag == 506: + print("โœ“ Correct COMID CBOR tag (506) found in tags") + else: + print("โœ— Missing or incorrect COMID CBOR tag in tags") + + return True + + except Exception as e: + print(f"Error analyzing structure: {e}") + return False + +if __name__ == "__main__": + print("CoRIM CDDL Validation Test") + print("=" * 50) + + success = test_corim_generation() + if success: + analyze_structure() + print("\n=== Test Summary ===") + print("โœ“ CoRIM generation completed") + print("โœ“ CBOR output saved for validation") + print("\nNext steps:") + print("1. Use a CDDL validator tool to check 'test_corim_output.cbor' against the schema") + print("2. Compare structure with the example in ocp-safe-sfr-fw-example.diag") + print("3. Check for any remaining structural differences") + else: + print("\nโœ— CoRIM generation failed") + sys.exit(1) diff --git a/shortform_report-main/tests/test_corim_generation.py b/shortform_report-main/tests/test_corim_generation.py new file mode 100644 index 0000000..a9a1152 --- /dev/null +++ b/shortform_report-main/tests/test_corim_generation.py @@ -0,0 +1,363 @@ +""" +Test script for CoRIM SFR generation functionality. + +This script validates that the extended library correctly generates +CoRIM format reports that comply with the OCP SAFE SFR CDDL schema. + +Author: Extended from Jeremy Boone's original OcpReportLib.py +Date : January 2025 +""" + +import sys +import os +import json +import traceback + +# Add parent directory to path to import OcpReportLib +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +from OcpReportLib import ShortFormReport + +def test_basic_functionality(): + """Test basic report generation in both formats.""" + print("=== Testing Basic Functionality ===") + + try: + # Create report + rep = ShortFormReport(framework_ver="1.1") + + # Add test data + rep.add_device( + vendor="Test Vendor", + product="Test Device", + category="storage", + repo_tag="test_v1.0.0", + fw_ver="1.0.0", + fw_hash_sha384="a" * 96, # Valid length hex string + fw_hash_sha512="b" * 128 # Valid length hex string + ) + + rep.add_audit( + srp="Test SRP", + methodology="whitebox", + date="2023-01-01", + report_ver="1.0", + scope_number=1 + ) + + rep.add_issue( + title="Test Issue", + cvss_score="5.0", + cvss_vec="AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + cwe="CWE-000", + description="Test vulnerability description" + ) + + # Test JSON generation + json_dict = rep.get_report_as_dict() + json_str = rep.get_json_report_as_str() + + assert isinstance(json_dict, dict) + assert isinstance(json_str, str) + assert "device" in json_dict + assert "audit" in json_dict + + print("โœ“ JSON generation: PASS") + + # Test CoRIM generation + corim_dict = rep.get_report_as_corim_dict() + corim_cbor = rep.get_report_as_corim_cbor() + + assert isinstance(corim_dict, dict) + assert isinstance(corim_cbor, bytes) + assert len(corim_cbor) > 0 + + print("โœ“ CoRIM generation: PASS") + + return True + + except Exception as e: + print(f"โœ— Basic functionality test failed: {e}") + traceback.print_exc() + return False + +def test_schema_compliance(): + """Test that generated CoRIM complies with expected structure.""" + print("\n=== Testing Schema Compliance ===") + + try: + rep = ShortFormReport() + + rep.add_device( + vendor="ACME Corp", + product="Test Widget", + category="gpu", # Test different category + repo_tag="v2.1.0", + fw_ver="2.1.0", + fw_hash_sha384="c" * 96, + fw_hash_sha512="d" * 128 + ) + + rep.add_audit( + srp="Compliance Test SRP", + methodology="blackbox", + date="2023-12-31", + report_ver="2.0", + scope_number=2 + ) + + # Add multiple issues + rep.add_issue( + title="Critical Issue", + cvss_score="9.8", + cvss_vec="AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + cwe="CWE-787", + description="Critical buffer overflow", + cve="CVE-2023-12345" + ) + + rep.add_issue( + title="Medium Issue", + cvss_score="6.1", + cvss_vec="AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", + cwe="CWE-79", + description="Cross-site scripting vulnerability" + ) + + # Generate CoRIM and validate structure + corim_dict = rep.get_report_as_corim_dict() + + # Check top-level structure + assert 0 in corim_dict # id + assert 1 in corim_dict # tags + assert 5 in corim_dict # entities + + print("โœ“ Top-level CoRIM structure: PASS") + + # Validate SFR data structure + sfr_map = rep._convert_to_corim_structure() + + # Check required SFR fields + assert 0 in sfr_map # review-framework-version + assert 1 in sfr_map # report-version + assert 2 in sfr_map # completion-date + assert 3 in sfr_map # scope-number + assert 4 in sfr_map # fw-identifiers + + # Check optional fields + assert 5 in sfr_map # device-category (should be 2 for GPU) + assert sfr_map[5] == 2 # GPU category + assert 6 in sfr_map # issues + assert len(sfr_map[6]) == 2 # Two issues added + + print("โœ“ SFR structure compliance: PASS") + + # Check fw-identifiers structure + fw_ids = sfr_map[4] + assert isinstance(fw_ids, list) + assert len(fw_ids) > 0 + + fw_id = fw_ids[0] + assert 0 in fw_id # fw-version + assert 1 in fw_id # fw-file-digests + assert 2 in fw_id # repo-tag + + print("โœ“ Firmware identifier structure: PASS") + + # Check issues structure + issues = sfr_map[6] + for issue in issues: + assert 0 in issue # title + assert 1 in issue # cvss-score + assert 2 in issue # cvss-vector + assert 3 in issue # cwe + assert 4 in issue # description + + print("โœ“ Issues structure: PASS") + + return True + + except Exception as e: + print(f"โœ— Schema compliance test failed: {e}") + traceback.print_exc() + return False + +def test_device_categories(): + """Test device category mapping.""" + print("\n=== Testing Device Categories ===") + + categories = [ + ("storage", 0), + ("network", 1), + ("gpu", 2), + ("cpu", 3), + ("apu", 4), + ("bmc", 5) + ] + + try: + for category_str, expected_int in categories: + rep = ShortFormReport() + + rep.add_device( + vendor="Test", + product="Test", + category=category_str, + repo_tag="test", + fw_ver="1.0", + fw_hash_sha384="a" * 96, + fw_hash_sha512="b" * 128 + ) + + rep.add_audit( + srp="Test", + methodology="test", + date="2023-01-01", + report_ver="1.0", + scope_number=1 + ) + + sfr_map = rep._convert_to_corim_structure() + + if 5 in sfr_map: # device-category is optional + assert sfr_map[5] == expected_int, f"Category {category_str} should map to {expected_int}, got {sfr_map[5]}" + + print(f"โœ“ Category '{category_str}' โ†’ {expected_int}: PASS") + + return True + + except Exception as e: + print(f"โœ— Device category test failed: {e}") + traceback.print_exc() + return False + +def test_error_handling(): + """Test error handling for invalid inputs.""" + print("\n=== Testing Error Handling ===") + + try: + # Test missing required data + rep = ShortFormReport() + + try: + # Should fail - no device or audit data + corim_dict = rep.get_report_as_corim_dict() + print("โœ— Should have failed with missing data") + return False + except ValueError: + print("โœ“ Missing data validation: PASS") + + # Test invalid date format + rep.add_device("Test", "Test", "storage", "test", "1.0", "a"*96, "b"*128) + rep.add_audit("Test", "test", "invalid-date", "1.0", 1) + + try: + sfr_map = rep._convert_to_corim_structure() + print("โœ— Should have failed with invalid date") + return False + except ValueError: + print("โœ“ Invalid date validation: PASS") + + return True + + except Exception as e: + print(f"โœ— Error handling test failed: {e}") + traceback.print_exc() + return False + +def test_backward_compatibility(): + """Test that original API still works.""" + print("\n=== Testing Backward Compatibility ===") + + try: + # Import with original alias + from OcpReportLib import ShortFormReport + + # Use exactly like original library + rep = ShortFormReport() + + rep.add_device( + "Test Vendor", + "Test Product", + "storage", + "test_tag", + "1.0.0", + "a" * 96, + "b" * 128 + ) + + rep.add_audit( + "Test SRP", + "whitebox", + "2023-01-01", + "1.0", + 1 + ) + + rep.add_issue( + "Test Issue", + "5.0", + "AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L", + "CWE-000", + "Test description" + ) + + # Original methods should work + json_dict = rep.get_report_as_dict() + json_str = rep.get_json_report_as_str() + + assert isinstance(json_dict, dict) + assert isinstance(json_str, str) + + print("โœ“ Original API compatibility: PASS") + + # New methods should also work + corim_cbor = rep.get_report_as_corim_cbor() + assert isinstance(corim_cbor, bytes) + + print("โœ“ Extended API availability: PASS") + + return True + + except Exception as e: + print(f"โœ— Backward compatibility test failed: {e}") + traceback.print_exc() + return False + +def main(): + """Run all tests.""" + print("OCP SAFE SFR CoRIM Generation Test Suite") + print("=" * 50) + + tests = [ + test_basic_functionality, + test_schema_compliance, + test_device_categories, + test_error_handling, + test_backward_compatibility + ] + + passed = 0 + failed = 0 + + for test in tests: + try: + if test(): + passed += 1 + else: + failed += 1 + except Exception as e: + print(f"โœ— Test {test.__name__} crashed: {e}") + failed += 1 + + print("\n" + "=" * 50) + print(f"Test Results: {passed} passed, {failed} failed") + + if failed == 0: + print("๐ŸŽ‰ All tests passed! CoRIM generation is working correctly.") + return 0 + else: + print("โŒ Some tests failed. Please check the implementation.") + return 1 + +if __name__ == "__main__": + sys.exit(main()) From 4a5bc7a6635ae81fef84e8f67aa5b23537ccd80c Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Fri, 10 Oct 2025 13:57:23 -0700 Subject: [PATCH 2/8] Adding back GitHub actions Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- .github/workflows/validate-cddl.yml | 39 ++++ .github/workflows/validate-reports.yml | 243 +++++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100644 .github/workflows/validate-cddl.yml create mode 100644 .github/workflows/validate-reports.yml diff --git a/.github/workflows/validate-cddl.yml b/.github/workflows/validate-cddl.yml new file mode 100644 index 0000000..af78829 --- /dev/null +++ b/.github/workflows/validate-cddl.yml @@ -0,0 +1,39 @@ +name: Validate CDDL and Example + +on: + workflow_dispatch: + pull_request: + paths: + - 'Documentation/corim_profile/*.cddl' + - 'Documentation/corim_profile/examples/*.diag' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Ruby cddl and cbor-diag gems + run: | + sudo apt-get update + sudo apt-get install -y ruby + sudo gem install cddl + sudo gem install cddlc + sudo gem install cbor-diag + + - name: Convert DIAG to CBOR + run: | + diag2cbor.rb Documentation/corim_profile/examples/ocp-safe-sfr-fw-example.diag > example.cbor + + - name: Fetch latest upstream CoRIM CDDL + run: | + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + + - name: Concatenate the CDDLs + run: | + cddlc -t cddl corim-base-upstream.cddl Documentation/corim_profile/ocp-safe-sfr-profile.cddl > combined.cddl + + - name: Validate CBOR against CDDL + run: | + cddl combined.cddl validate example.cbor diff --git a/.github/workflows/validate-reports.yml b/.github/workflows/validate-reports.yml new file mode 100644 index 0000000..973c3ba --- /dev/null +++ b/.github/workflows/validate-reports.yml @@ -0,0 +1,243 @@ +name: Validate SFR Reports + +on: + workflow_dispatch: + pull_request: + paths: + - 'Reports/**/*.json' + - 'Reports/**/*.cbor' + - 'shortform_report-main/**' + - 'Documentation/corim_profile/*.cddl' + - 'Documentation/corim_profile/examples/*.diag' + push: + paths: + - 'Reports/**/*.json' + - 'Reports/**/*.cbor' + - 'shortform_report-main/**' + - 'Documentation/corim_profile/*.cddl' + - 'Documentation/corim_profile/examples/*.diag' + +jobs: + validate-cbor-reports: + runs-on: ubuntu-latest + name: Validate CBOR CoRIM Reports + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install Ruby and CDDL tools + run: | + sudo apt-get update + sudo apt-get install -y ruby + sudo gem install cddl + sudo gem install cddlc + sudo gem install cbor-diag + + - name: Install Python dependencies + run: | + cd shortform_report-main + pip install -r requirements.txt + + - name: Find CBOR reports + id: find-cbor + run: | + # Find all CBOR files in Reports directory + cbor_files=$(find Reports/ -name "*.cbor" 2>/dev/null || echo "") + if [ -n "$cbor_files" ]; then + echo "found=true" >> $GITHUB_OUTPUT + echo "files<> $GITHUB_OUTPUT + echo "$cbor_files" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "Found CBOR files:" + echo "$cbor_files" + else + echo "found=false" >> $GITHUB_OUTPUT + echo "No CBOR files found in Reports directory" + fi + + - name: Prepare CDDL schema + run: | + # Fetch latest upstream CoRIM CDDL + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + + # Concatenate the CDDLs + cddlc -t cddl corim-base-upstream.cddl Documentation/corim_profile/ocp-safe-sfr-profile.cddl > combined.cddl + + echo "โœ… CDDL schema prepared" + + - name: Validate CBOR against CDDL schema + if: steps.find-cbor.outputs.found == 'true' + run: | + echo "Validating CBOR files against CDDL schema..." + + validation_failed=false + + echo "${{ steps.find-cbor.outputs.files }}" | while read -r file; do + if [ -n "$file" ] && [ -f "$file" ]; then + echo "Validating $file..." + if cddl combined.cddl validate "$file"; then + echo "โœ… $file: Valid CBOR structure" + else + echo "โŒ $file: CDDL validation failed" + validation_failed=true + fi + fi + done + + if [ "$validation_failed" = true ]; then + echo "โŒ Some CBOR files failed CDDL validation" + exit 1 + else + echo "๐ŸŽ‰ All CBOR files passed CDDL validation!" + fi + + - name: Test CoRIM generation + run: | + cd shortform_report-main + echo "Testing CoRIM generation functionality..." + + # Run the comprehensive test suite + python tests/test_corim_generation.py + + # Run CDDL validation test + python tests/test_cddl_validation.py + + echo "โœ… CoRIM generation tests completed" + + validate-cddl-schema: + runs-on: ubuntu-latest + name: Validate CDDL Schema and Examples + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Ruby and CDDL tools + run: | + sudo apt-get update + sudo apt-get install -y ruby + sudo gem install cddl + sudo gem install cddlc + sudo gem install cbor-diag + + - name: Convert DIAG to CBOR + run: | + diag2cbor.rb Documentation/corim_profile/examples/ocp-safe-sfr-fw-example.diag > example.cbor + + - name: Fetch latest upstream CoRIM CDDL + run: | + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + + - name: Concatenate the CDDLs + run: | + cddlc -t cddl corim-base-upstream.cddl Documentation/corim_profile/ocp-safe-sfr-profile.cddl > combined.cddl + + - name: Validate CBOR against CDDL + run: | + cddl combined.cddl validate example.cbor + + integration-test: + runs-on: ubuntu-latest + name: Integration Test - JSON to CoRIM Conversion + needs: validate-cddl-schema + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.9' + + - name: Install Ruby and CDDL tools + run: | + sudo apt-get update + sudo apt-get install -y ruby + sudo gem install cddl + sudo gem install cddlc + sudo gem install cbor-diag + + - name: Install Python dependencies + run: | + cd shortform_report-main + pip install -r requirements.txt + + - name: Prepare CDDL schema + run: | + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + cddlc -t cddl corim-base-upstream.cddl Documentation/corim_profile/ocp-safe-sfr-profile.cddl > combined.cddl + + - name: Test JSON to CoRIM conversion + run: | + cd shortform_report-main + + # Find a sample JSON file to test conversion + sample_json=$(find ../Reports/ -name "*.json" -not -name "*_converted*" | head -1) + + if [ -n "$sample_json" ] && [ -f "$sample_json" ]; then + echo "Testing conversion of: $sample_json" + + # Convert JSON to CoRIM + python tests/json_to_corim_converter.py "$sample_json" -o test_converted.cbor + + # Validate the converted CBOR against CDDL + if [ -f "test_converted.cbor" ]; then + echo "Validating converted CBOR against CDDL..." + cd .. + if cddl combined.cddl validate shortform_report-main/test_converted.cbor; then + echo "โœ… JSON to CoRIM conversion successful and CDDL compliant!" + else + echo "โŒ Converted CBOR failed CDDL validation" + exit 1 + fi + else + echo "โŒ Conversion failed - no output file generated" + exit 1 + fi + else + echo "โ„น๏ธ No sample JSON files found for conversion testing" + echo "Running final validation summary instead..." + cd shortform_report-main + python tests/final_validation_summary.py + fi + + summary: + runs-on: ubuntu-latest + name: Validation Summary + needs: [validate-cbor-reports, validate-cddl-schema, integration-test] + if: always() + steps: + - name: Report Results + run: | + echo "## SFR Report Validation Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ needs.validate-cbor-reports.result }}" = "success" ]; then + echo "โœ… CBOR Reports: PASSED" >> $GITHUB_STEP_SUMMARY + else + echo "โŒ CBOR Reports: FAILED" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.validate-cddl-schema.result }}" = "success" ]; then + echo "โœ… CDDL Schema: PASSED" >> $GITHUB_STEP_SUMMARY + else + echo "โŒ CDDL Schema: FAILED" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ needs.integration-test.result }}" = "success" ]; then + echo "โœ… Integration Test: PASSED" >> $GITHUB_STEP_SUMMARY + else + echo "โŒ Integration Test: FAILED" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Validation Coverage" >> $GITHUB_STEP_SUMMARY + echo "- JSON SFR report structure validation" >> $GITHUB_STEP_SUMMARY + echo "- CBOR CoRIM CDDL schema compliance" >> $GITHUB_STEP_SUMMARY + echo "- CoRIM generation functionality" >> $GITHUB_STEP_SUMMARY + echo "- JSON to CoRIM conversion" >> $GITHUB_STEP_SUMMARY + echo "- End-to-end integration testing" >> $GITHUB_STEP_SUMMARY From bc0a6e1af4b547790dad6b6b7a72d5a4deecbf1d Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:22:27 -0700 Subject: [PATCH 3/8] Fixing syntax issue... Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- shortform_report-main/OcpReportLib.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shortform_report-main/OcpReportLib.py b/shortform_report-main/OcpReportLib.py index 6ec8cc7..ab36588 100644 --- a/shortform_report-main/OcpReportLib.py +++ b/shortform_report-main/OcpReportLib.py @@ -556,9 +556,7 @@ def sign_json_report_pem(self, priv_key: bytes, algo: str, kid: str) -> bool: if algo in ALLOWED_JWA_RSA_ALGOS: if pem.key_size not in ALLOWED_RSA_KEY_SIZES: print( - f"RSA key is too small: {pem.key_size}, must be one of: { - ALLOWED_RSA_KEY_SIZES - }" + f"RSA key is too small: {pem.key_size}, must be one of: {ALLOWED_RSA_KEY_SIZES}" ) return False From aa534f50b91b0dc289b7f44b223b9cbdb03e7e7f Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:29:04 -0700 Subject: [PATCH 4/8] Trying to fix syntax by using same Python version as dev env Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- .github/workflows/validate-reports.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-reports.yml b/.github/workflows/validate-reports.yml index 973c3ba..f6966b4 100644 --- a/.github/workflows/validate-reports.yml +++ b/.github/workflows/validate-reports.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.12' - name: Install Ruby and CDDL tools run: | @@ -151,7 +151,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.12' - name: Install Ruby and CDDL tools run: | From 7385e6fa2cf90882b364a2edb96d909437d03365 Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Tue, 4 Nov 2025 13:27:28 -0800 Subject: [PATCH 5/8] Enhancing human readable helper script + comments - Enhancing the human readable script to handle signed CBOR and display all fields in human readable form. - Addressing fix from Rob Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- shortform_report-main/OcpReportLib.py | 2 +- shortform_report-main/cbor_human_inspector.py | 752 +++++++++++++++--- 2 files changed, 639 insertions(+), 115 deletions(-) diff --git a/shortform_report-main/OcpReportLib.py b/shortform_report-main/OcpReportLib.py index ab36588..c5cfc16 100644 --- a/shortform_report-main/OcpReportLib.py +++ b/shortform_report-main/OcpReportLib.py @@ -510,7 +510,7 @@ def get_signed_json_report(self) -> bytes: """Returns the signed short form report (a JWS) as a bytes object. May return a 'None' object if the report hasn't been signed yet. """ - return self.signed_json_report + return self.signed_json_report.encode('utf-8') def sign_json_report_pem(self, priv_key: bytes, algo: str, kid: str) -> bool: """Sign the JSON object to make a JSON Web Signature. Refer to RFC7515 diff --git a/shortform_report-main/cbor_human_inspector.py b/shortform_report-main/cbor_human_inspector.py index ad4ad86..6716e1d 100644 --- a/shortform_report-main/cbor_human_inspector.py +++ b/shortform_report-main/cbor_human_inspector.py @@ -28,12 +28,14 @@ def print_header(title, level=1): print(f" {title}") print(f"{'ยท'*40}") -def format_bytes_display(data, max_length=32): +def format_bytes_display(data, max_length=16): """Format bytes for human-readable display.""" if len(data) <= max_length: return data.hex() else: - return f"{data[:max_length//2].hex()}...{data[-max_length//2:].hex()} ({len(data)} bytes total)" + start_bytes = max_length // 2 + end_bytes = max_length // 2 + return f"{data[:start_bytes].hex()}...{data[-end_bytes:].hex()} ({len(data)} bytes total)" def format_oid_display(oid_bytes): """Convert OID bytes to human-readable dotted notation.""" @@ -48,10 +50,170 @@ def format_oid_display(oid_bytes): except: return f"Invalid OID: {oid_bytes.hex()}" +def explain_environment_field(field_num): + """Explain environment fields in identity claims.""" + env_fields = { + 0: "Class ID - Identifies the class/type of the component", + 1: "Instance ID - Unique identifier for this specific instance", + 2: "Group ID - Group identifier for related components" + } + return env_fields.get(field_num, f"Environment field {field_num}") + +def explain_class_id_field(field_num): + """Explain class-id fields.""" + class_fields = { + 0: "Type - Type identifier for the class", + 1: "Vendor - Vendor name or identifier", + 2: "Model/Product - Product or model name" + } + return class_fields.get(field_num, f"Class field {field_num}") + +def explain_measurement_field(field_num): + """Explain measurement fields.""" + measurement_fields = { + 0: "Measurement Key - Key identifying the measurement type", + 1: "Measurement Values - The actual measurement data", + 2: "Measurement Metadata - Additional metadata about the measurement" + } + return measurement_fields.get(field_num, f"Measurement field {field_num}") + +def format_identity_claim_human_readable(claim, indent=""): + """Format identity claims in a more human-readable way.""" + if isinstance(claim, list) and len(claim) == 2: + # This looks like [environment, measurements] structure + env, measurements = claim + result = f"Identity Claim Structure:\n" + + # Format environment + result += f"{indent} Environment: " + if isinstance(env, dict): + result += "{\n" + for env_key, env_value in env.items(): + result += f"{indent} {explain_environment_field(env_key)}: " + if env_key == 0 and isinstance(env_value, dict): # class-id + result += "{\n" + for cid_key, cid_value in env_value.items(): + result += f"{indent} {explain_class_id_field(cid_key)}: " + if isinstance(cid_value, cbor2.CBORTag): + result += f"CBOR Tag {cid_value.tag}: {cid_value.value}\n" + elif isinstance(cid_value, bytes): + result += f"{format_bytes_display(cid_value)}\n" + else: + result += f"{cid_value}\n" + result += f"{indent} }}\n" + elif isinstance(env_value, bytes): + result += f"{format_bytes_display(env_value)}\n" + else: + result += f"{env_value}\n" + result += f"{indent} }}\n" + else: + result += f"{env}\n" + + # Format measurements/claims + result += f"{indent} Claims/Measurements: " + if isinstance(measurements, list): + result += f"[\n" + for i, measurement in enumerate(measurements): + if isinstance(measurement, dict): + result += f"{indent} Measurement #{i+1}: {{\n" + for m_key, m_value in measurement.items(): + result += f"{indent} {explain_measurement_field(m_key)}: " + if m_key == 1 and isinstance(m_value, dict): # measurement-values + result += "{\n" + for mv_key, mv_value in m_value.items(): + if mv_key == 1029: # SFR extension key + result += f"{indent} SFR Extension (1029): {{\n" + if isinstance(mv_value, dict): + for sfr_key, sfr_value in mv_value.items(): + result += f"{indent} {explain_sfr_field(sfr_key)}: " + if sfr_key == 2 and isinstance(sfr_value, datetime): + result += f"{sfr_value.strftime('%Y-%m-%d %H:%M:%S UTC')}\n" + elif sfr_key == 5: + result += f"{sfr_value} ({explain_device_category(sfr_value)})\n" + elif sfr_key == 6 and isinstance(sfr_value, list): + result += f"{len(sfr_value)} security issue(s)\n" + elif sfr_key == 4: + # Format firmware identifiers with enhanced byte display + if isinstance(sfr_value, list): + result += f"[{len(sfr_value)} firmware item(s)]\n" + for fw_idx, fw_item in enumerate(sfr_value): + if isinstance(fw_item, dict): + result += f"{indent} Firmware #{fw_idx+1}:\n" + for fw_key, fw_value in fw_item.items(): + if fw_key == 1 and isinstance(fw_value, list): # hash arrays + result += f"{indent} Hash Arrays:\n" + for hash_idx, hash_item in enumerate(fw_value): + if isinstance(hash_item, list) and len(hash_item) == 2: + hash_type, hash_value = hash_item + if isinstance(hash_value, bytes): + hash_type_name = "SHA384" if hash_type == 7 else "SHA512" if hash_type == 8 else f"Type {hash_type}" + result += f"{indent} {hash_type_name}: {format_bytes_display(hash_value)}\n" + else: + result += f"{indent} {fw_key}: {fw_value}\n" + else: + result += f"{sfr_value}\n" + else: + result += f"{sfr_value}\n" + result += f"{indent} }}\n" + else: + result += f"{indent} Extension {mv_key}: {mv_value}\n" + result += f"{indent} }}\n" + else: + result += f"{format_value_recursively(m_value, indent + ' ')}\n" + result += f"{indent} }}\n" + else: + result += f"{indent} Item #{i+1}: {measurement}\n" + result += f"{indent} ]\n" + else: + result += f"{measurements}\n" + + return result.rstrip() + else: + return format_value_recursively(claim, indent) + +def format_value_recursively(value, indent="", max_depth=5, current_depth=0): + """Recursively format CBOR values to show actual leaf data.""" + if current_depth >= max_depth: + return f"{type(value).__name__} (max depth reached)" + + if isinstance(value, dict): + if len(value) == 0: + return "{} (empty dict)" + + result_lines = [] + for k, v in value.items(): + formatted_value = format_value_recursively(v, indent + " ", max_depth, current_depth + 1) + result_lines.append(f"{indent} {k}: {formatted_value}") + return "{\n" + "\n".join(result_lines) + f"\n{indent}}}" + + elif isinstance(value, list): + if len(value) == 0: + return "[] (empty list)" + + result_lines = [] + for i, item in enumerate(value): + formatted_item = format_value_recursively(item, indent + " ", max_depth, current_depth + 1) + result_lines.append(f"{indent} [{i}]: {formatted_item}") + return "[\n" + "\n".join(result_lines) + f"\n{indent}]" + + elif isinstance(value, bytes): + return format_bytes_display(value) + + elif isinstance(value, cbor2.CBORTag): + return f"CBOR Tag {value.tag}: {format_value_recursively(value.value, indent, max_depth, current_depth + 1)}" + + elif isinstance(value, datetime): + return f"{value.strftime('%Y-%m-%d %H:%M:%S UTC')} (datetime)" + + else: + return str(value) + def explain_cbor_tag(tag_num): """Provide human-readable explanations for CBOR tags.""" tag_explanations = { 1: "POSIX timestamp (seconds since epoch)", + 18: "COSE-Sign (Multi-Signer COSE Signature)", + 61: "COSE-Sign1 (Single Signer COSE Signature)", 111: "Object Identifier (OID)", 501: "CoRIM (CBOR Object Representation of Information Model)", 506: "COMID (Concise Module Identifier)", @@ -82,6 +244,16 @@ def explain_comid_field(field_num): } return comid_fields.get(field_num, f"Unknown COMID field {field_num}") +def explain_triple_type(triple_num): + """Explain different types of triples in COMID.""" + triple_types = { + 0: "Reference Triples - Reference values for comparison", + 1: "Endorsed Triples - Endorsed/attested values", + 9: "Identity Triples - Identity claims and attestations", + 10: "Conditional Endorsement Triples - Conditional endorsements with conditions" + } + return triple_types.get(triple_num, f"Unknown Triple Type {triple_num}") + def explain_sfr_field(field_num): """Explain SFR extension fields.""" sfr_fields = { @@ -109,6 +281,256 @@ def explain_device_category(category_num): } return categories.get(category_num, f"Unknown category {category_num}") +def extract_corim_from_cose_sign1(cose_sign1_value): + """Extract CoRIM payload from COSE-Sign1 structure.""" + try: + # Handle case where COSE-Sign1 value is wrapped in another CBOR tag + actual_value = cose_sign1_value + if isinstance(cose_sign1_value, cbor2.CBORTag): + print(f" ๐Ÿ” COSE-Sign1 value is wrapped in CBOR tag {cose_sign1_value.tag}") + actual_value = cose_sign1_value.value + + # COSE-Sign1 structure: [protected, unprotected, payload, signature] + if isinstance(actual_value, list) and len(actual_value) >= 3: + payload = actual_value[2] # The payload is the third element + print(f" ๐Ÿ” Extracted payload from COSE-Sign1, type: {type(payload)}") + + if isinstance(payload, bytes): + # The payload should contain a CBOR-encoded structure with CoRIM data + # According to the cwt library usage in OcpReportLib, the CoRIM is in a claims structure + try: + claims = cbor2.loads(payload) + print(f" ๐Ÿ” Decoded claims from payload, type: {type(claims)}") + # Look for the CoRIM data in the claims (custom claim -65537 based on OcpReportLib) + if isinstance(claims, dict) and -65537 in claims: + corim_cbor = claims[-65537] + print(f" โœ… Found CoRIM data in custom claim -65537") + if isinstance(corim_cbor, bytes): + # Decode the CoRIM CBOR + corim_decoded = cbor2.loads(corim_cbor) + if isinstance(corim_decoded, cbor2.CBORTag) and corim_decoded.tag == 501: + return corim_decoded.value + except Exception as e: + print(f" โš ๏ธ Error decoding claims from payload: {e}") + + # Fallback: try to decode payload directly as CoRIM + try: + corim_decoded = cbor2.loads(payload) + print(f" ๐Ÿ” Direct payload decode result: {type(corim_decoded)}") + if isinstance(corim_decoded, cbor2.CBORTag) and corim_decoded.tag == 501: + print(f" โœ… Found CoRIM tag (501) directly in payload") + return corim_decoded.value + elif isinstance(corim_decoded, dict): + # Might be the CoRIM content directly + print(f" โœ… Found CoRIM content directly in payload") + return corim_decoded + except Exception as e: + print(f" โš ๏ธ Error decoding payload directly: {e}") + + print(f" โš ๏ธ Payload type: {type(payload)}, length: {len(payload) if hasattr(payload, '__len__') else 'N/A'}") + return None + else: + print(f" โŒ Invalid COSE-Sign1 structure: expected list with โ‰ฅ3 elements, got {type(actual_value)}") + if isinstance(actual_value, list): + print(f" ๐Ÿ” List has {len(actual_value)} elements") + return None + except Exception as e: + print(f" โŒ Error extracting CoRIM from COSE-Sign1: {e}") + import traceback + traceback.print_exc() + return None + +def extract_corim_from_cose_sign(cose_sign_value): + """Extract CoRIM payload from COSE-Sign structure (multi-signer).""" + try: + # Handle case where COSE-Sign value is wrapped in another CBOR tag + actual_value = cose_sign_value + if isinstance(cose_sign_value, cbor2.CBORTag): + print(f" ๐Ÿ” COSE-Sign value is wrapped in CBOR tag {cose_sign_value.tag}") + actual_value = cose_sign_value.value + + # COSE-Sign structure: [protected, unprotected, payload, signatures] + if isinstance(actual_value, list) and len(actual_value) >= 3: + payload = actual_value[2] # The payload is the third element + print(f" ๐Ÿ” Extracted payload from COSE-Sign, type: {type(payload)}") + + if len(actual_value) >= 4: + signatures = actual_value[3] + if isinstance(signatures, list): + print(f" ๐Ÿ” Found {len(signatures)} signature(s) in COSE-Sign") + + if isinstance(payload, bytes): + # The payload should contain a CBOR-encoded structure with CoRIM data + # According to the cwt library usage in OcpReportLib, the CoRIM is in a claims structure + try: + claims = cbor2.loads(payload) + print(f" ๐Ÿ” Decoded claims from payload, type: {type(claims)}") + # Look for the CoRIM data in the claims (custom claim -65537 based on OcpReportLib) + if isinstance(claims, dict) and -65537 in claims: + corim_cbor = claims[-65537] + print(f" โœ… Found CoRIM data in custom claim -65537") + if isinstance(corim_cbor, bytes): + # Decode the CoRIM CBOR + corim_decoded = cbor2.loads(corim_cbor) + if isinstance(corim_decoded, cbor2.CBORTag) and corim_decoded.tag == 501: + return corim_decoded.value + except Exception as e: + print(f" โš ๏ธ Error decoding claims from payload: {e}") + + # Fallback: try to decode payload directly as CoRIM + try: + corim_decoded = cbor2.loads(payload) + print(f" ๐Ÿ” Direct payload decode result: {type(corim_decoded)}") + if isinstance(corim_decoded, cbor2.CBORTag) and corim_decoded.tag == 501: + print(f" โœ… Found CoRIM tag (501) directly in payload") + return corim_decoded.value + elif isinstance(corim_decoded, dict): + # Might be the CoRIM content directly + print(f" โœ… Found CoRIM content directly in payload") + return corim_decoded + except Exception as e: + print(f" โš ๏ธ Error decoding payload directly: {e}") + + print(f" โš ๏ธ Payload type: {type(payload)}, length: {len(payload) if hasattr(payload, '__len__') else 'N/A'}") + return None + else: + print(f" โŒ Invalid COSE-Sign structure: expected list with โ‰ฅ3 elements, got {type(actual_value)}") + if isinstance(actual_value, list): + print(f" ๐Ÿ” List has {len(actual_value)} elements") + return None + except Exception as e: + print(f" โŒ Error extracting CoRIM from COSE-Sign: {e}") + import traceback + traceback.print_exc() + return None + +def inspect_corim_content_details(corim_content): + """Inspect the detailed content of a CoRIM structure.""" + if isinstance(corim_content, dict): + print(f"โœ… CoRIM contains {len(corim_content)} top-level fields") + + # First, validate required fields + print_header("Required Fields Validation", 3) + required_fields = { + 0: "CoRIM ID", + 1: "Tags", + 3: "Profile", + 5: "Entities" + } + + validation_passed = True + for field_num, field_name in required_fields.items(): + if field_num in corim_content: + print(f"โœ… Required field {field_num} ({field_name}) is present") + else: + print(f"โŒ MISSING REQUIRED FIELD {field_num} ({field_name})") + validation_passed = False + + if not validation_passed: + print(f"\nโš ๏ธ WARNING: This CoRIM is missing required fields and may not be valid!") + + print_header("CoRIM Fields Breakdown", 3) + + # Check for profile field specifically if missing + if 3 not in corim_content: + print(f"\n๐Ÿ”ธ Field 3: {explain_corim_field(3)}") + print(f" โŒ CRITICAL: Profile field is MISSING!") + print(f" โŒ CoRIM MUST include profile OID 1.3.6.1.4.1.42623.1.1 for OCP SAFE SFR") + print(f" โŒ This CoRIM will not validate against the OCP SAFE SFR profile") + + for field_num in sorted(corim_content.keys()): + field_value = corim_content[field_num] + print(f"\n๐Ÿ”ธ Field {field_num}: {explain_corim_field(field_num)}") + + if field_num == 0: # CoRIM ID + if isinstance(field_value, bytes): + print(f" Value: {format_bytes_display(field_value)}") + else: + print(f" Value: {field_value}") + + elif field_num == 1: # Tags + if isinstance(field_value, list): + print(f" ๐Ÿ“ Contains {len(field_value)} COMID tag(s)") + + for i, tag in enumerate(field_value): + print(f"\n ๐Ÿ“‹ COMID Tag #{i+1}:") + if isinstance(tag, cbor2.CBORTag) and tag.tag == 506: + print(f" โœ… Proper COMID tag (506)") + + try: + comid_content = cbor2.loads(tag.value) + inspect_comid_content(comid_content, indent=" ") + except Exception as e: + print(f" โŒ Error decoding COMID: {e}") + elif isinstance(tag, bytes): + print(f" ๐Ÿ” COMID stored as raw bytes, attempting to decode...") + + try: + # Try to decode the bytes as CBOR + decoded_tag = cbor2.loads(tag) + if isinstance(decoded_tag, cbor2.CBORTag) and decoded_tag.tag == 506: + print(f" โœ… Found COMID tag (506) after decoding bytes") + # The value might already be decoded or might need further decoding + if isinstance(decoded_tag.value, bytes): + comid_content = cbor2.loads(decoded_tag.value) + else: + comid_content = decoded_tag.value + inspect_comid_content(comid_content, indent=" ") + elif isinstance(decoded_tag, dict): + print(f" โœ… Found COMID content directly in bytes") + inspect_comid_content(decoded_tag, indent=" ") + else: + print(f" โŒ Decoded bytes but found unexpected structure: {type(decoded_tag)}") + except Exception as e: + print(f" โŒ Error decoding COMID bytes: {e}") + else: + print(f" โŒ Invalid COMID tag structure: {type(tag)}") + else: + print(f" โŒ Tags field should be a list, found: {type(field_value)}") + + elif field_num == 3: # Profile + if isinstance(field_value, cbor2.CBORTag) and field_value.tag == 111: + print(f" โœ… Proper OID tag (111)") + oid_display = format_oid_display(field_value.value) + print(f" ๐Ÿ†” Profile: {oid_display}") + else: + print(f" โŒ Profile should be OID tag (111), found: {type(field_value)}") + + elif field_num == 5: # Entities + if isinstance(field_value, list): + print(f" ๐Ÿ‘ฅ Contains {len(field_value)} entity/entities") + for i, entity in enumerate(field_value): + if isinstance(entity, dict): + print(f" Entity #{i+1}:") + if 0 in entity: # entity-name + print(f" Name: {entity[0]}") + if 1 in entity: # reg-id + print(f" Registration ID: {entity[1]}") + if 2 in entity: # role + roles = entity[2] + if isinstance(roles, list): + role_names = [] + for role in roles: + if role == 0: + role_names.append("tag-creator") + elif role == 1: + role_names.append("tag-maintainer") + else: + role_names.append(f"role-{role}") + print(f" Roles: {', '.join(role_names)}") + else: + print(f" โŒ Entities should be a list, found: {type(field_value)}") + + else: + print(f" Value type: {type(field_value)}") + if isinstance(field_value, (str, int, float)): + print(f" Value: {field_value}") + elif isinstance(field_value, bytes): + print(f" Value: {format_bytes_display(field_value)}") + + else: + print(f"โŒ CoRIM content should be a dictionary, found: {type(corim_content)}") + def inspect_corim_structure(cbor_data, show_raw_data=False): """Provide human-readable inspection of CoRIM structure.""" @@ -130,113 +552,33 @@ def inspect_corim_structure(cbor_data, show_raw_data=False): if decoded.tag == 501: # CoRIM tag print("โœ… This is a valid CoRIM structure") - corim_content = decoded.value + inspect_corim_content_details(decoded.value) - if isinstance(corim_content, dict): - print(f"โœ… CoRIM contains {len(corim_content)} top-level fields") - - # First, validate required fields - print_header("Required Fields Validation", 3) - required_fields = { - 0: "CoRIM ID", - 1: "Tags", - 3: "Profile", - 5: "Entities" - } - - validation_passed = True - for field_num, field_name in required_fields.items(): - if field_num in corim_content: - print(f"โœ… Required field {field_num} ({field_name}) is present") - else: - print(f"โŒ MISSING REQUIRED FIELD {field_num} ({field_name})") - validation_passed = False - - if not validation_passed: - print(f"\nโš ๏ธ WARNING: This CoRIM is missing required fields and may not be valid!") - - print_header("CoRIM Fields Breakdown", 3) - - # Check for profile field specifically if missing - if 3 not in corim_content: - print(f"\n๐Ÿ”ธ Field 3: {explain_corim_field(3)}") - print(f" โŒ CRITICAL: Profile field is MISSING!") - print(f" โŒ CoRIM MUST include profile OID 1.3.6.1.4.1.42623.1.1 for OCP SAFE SFR") - print(f" โŒ This CoRIM will not validate against the OCP SAFE SFR profile") + elif decoded.tag == 61: # COSE-Sign1 tag + print("โœ… This is a signed CoRIM with COSE-Sign1 signature") + print("๐Ÿ”“ Extracting CoRIM payload from COSE signature...") + + # Extract CoRIM from COSE-Sign1 structure + corim_payload = extract_corim_from_cose_sign1(decoded.value) + if corim_payload: + print("โœ… Successfully extracted CoRIM payload from signature") + inspect_corim_content_details(corim_payload) + else: + print("โŒ Failed to extract CoRIM payload from COSE-Sign1 structure") - for field_num in sorted(corim_content.keys()): - field_value = corim_content[field_num] - print(f"\n๐Ÿ”ธ Field {field_num}: {explain_corim_field(field_num)}") - - if field_num == 0: # CoRIM ID - if isinstance(field_value, bytes): - print(f" Value: {format_bytes_display(field_value)}") - else: - print(f" Value: {field_value}") - - elif field_num == 1: # Tags - if isinstance(field_value, list): - print(f" ๐Ÿ“ Contains {len(field_value)} COMID tag(s)") - - for i, tag in enumerate(field_value): - print(f"\n ๐Ÿ“‹ COMID Tag #{i+1}:") - if isinstance(tag, cbor2.CBORTag) and tag.tag == 506: - print(f" โœ… Proper COMID tag (506)") - - try: - comid_content = cbor2.loads(tag.value) - inspect_comid_content(comid_content, indent=" ") - except Exception as e: - print(f" โŒ Error decoding COMID: {e}") - else: - print(f" โŒ Invalid COMID tag structure") - else: - print(f" โŒ Tags field should be a list, found: {type(field_value)}") - - elif field_num == 3: # Profile - if isinstance(field_value, cbor2.CBORTag) and field_value.tag == 111: - print(f" โœ… Proper OID tag (111)") - oid_display = format_oid_display(field_value.value) - print(f" ๐Ÿ†” Profile: {oid_display}") - else: - print(f" โŒ Profile should be OID tag (111), found: {type(field_value)}") - - elif field_num == 5: # Entities - if isinstance(field_value, list): - print(f" ๐Ÿ‘ฅ Contains {len(field_value)} entity/entities") - for i, entity in enumerate(field_value): - if isinstance(entity, dict): - print(f" Entity #{i+1}:") - if 0 in entity: # entity-name - print(f" Name: {entity[0]}") - if 1 in entity: # reg-id - print(f" Registration ID: {entity[1]}") - if 2 in entity: # role - roles = entity[2] - if isinstance(roles, list): - role_names = [] - for role in roles: - if role == 0: - role_names.append("tag-creator") - elif role == 1: - role_names.append("tag-maintainer") - else: - role_names.append(f"role-{role}") - print(f" Roles: {', '.join(role_names)}") - else: - print(f" โŒ Entities should be a list, found: {type(field_value)}") - - else: - print(f" Value type: {type(field_value)}") - if isinstance(field_value, (str, int, float)): - print(f" Value: {field_value}") - elif isinstance(field_value, bytes): - print(f" Value: {format_bytes_display(field_value)}") + elif decoded.tag == 18: # COSE-Sign tag + print("โœ… This is a signed CoRIM with COSE-Sign signature (multi-signer)") + print("๐Ÿ”“ Extracting CoRIM payload from COSE signature...") + # Extract CoRIM from COSE-Sign structure + corim_payload = extract_corim_from_cose_sign(decoded.value) + if corim_payload: + print("โœ… Successfully extracted CoRIM payload from signature") + inspect_corim_content_details(corim_payload) else: - print(f"โŒ CoRIM content should be a dictionary, found: {type(corim_content)}") + print("โŒ Failed to extract CoRIM payload from COSE-Sign structure") else: - print(f"โŒ Expected CoRIM tag (501), found tag {decoded.tag}") + print(f"โŒ Expected CoRIM tag (501), COSE-Sign1 tag (61), or COSE-Sign tag (18), found tag {decoded.tag}") else: print(f"โŒ Expected CBOR tag at top level, found: {type(decoded)}") @@ -269,11 +611,97 @@ def inspect_comid_content(comid_content, indent=""): elif field_num == 4: # Triples if isinstance(field_value, dict): print(f"{indent} ๐Ÿ“Š Triples structure:") + print(f"{indent} ๐Ÿ“‹ Found {len(field_value)} triple type(s)") + + # Check for different types of triples + triples_found = False + + # Check for reference-triples (field 0) + if 0 in field_value: + ref_triples = field_value[0] + if isinstance(ref_triples, list): + print(f"{indent} ๐Ÿ“š Reference Triples: {len(ref_triples)} item(s)") + triples_found = True + for i, triple in enumerate(ref_triples): + print(f"{indent} ๐Ÿ“– Reference #{i+1}: {type(triple).__name__}") + + # Check for endorsed-triples (field 1) + if 1 in field_value: + end_triples = field_value[1] + if isinstance(end_triples, list): + print(f"{indent} โœ… Endorsed Triples: {len(end_triples)} item(s)") + triples_found = True + for i, triple in enumerate(end_triples): + print(f"{indent} โœ… Endorsement #{i+1}: {type(triple).__name__}") + + # Check for identity-triples (field 9) + if 9 in field_value: + identity_triples = field_value[9] + if isinstance(identity_triples, list): + print(f"{indent} ๐Ÿ†” Identity Triples: {len(identity_triples)} item(s)") + triples_found = True + + for i, identity_triple in enumerate(identity_triples): + if isinstance(identity_triple, list) and len(identity_triple) == 2: + env, claims = identity_triple + print(f"\n{indent} ๐Ÿ†” Identity #{i+1}:") + print(f"{indent} Environment: {type(env).__name__}") + + if isinstance(claims, list): + print(f"{indent} ๐Ÿท๏ธ {len(claims)} identity claim(s):") + + for j, claim in enumerate(claims): + if isinstance(claim, dict): + print(f"\n{indent} ๐Ÿ“‹ Identity Claim #{j+1}:") + + # Check for common identity claim fields + if 0 in claim: # class-id + class_id = claim[0] + if isinstance(class_id, dict): + print(f"{indent} ๐Ÿท๏ธ Class ID:") + for cid_key, cid_value in class_id.items(): + if cid_key == 0: # type + print(f"{indent} Type: {cid_value}") + elif cid_key == 1: # value + if isinstance(cid_value, bytes): + print(f"{indent} Value: {format_bytes_display(cid_value)}") + else: + print(f"{indent} Value: {cid_value}") + else: + print(f"{indent} Field {cid_key}: {cid_value}") + else: + print(f"{indent} ๐Ÿท๏ธ Class ID: {class_id}") + + if 1 in claim: # instance-id + instance_id = claim[1] + if isinstance(instance_id, bytes): + print(f"{indent} ๐Ÿ†” Instance ID: {format_bytes_display(instance_id)}") + else: + print(f"{indent} ๐Ÿ†” Instance ID: {instance_id}") + + # Check for other claim fields + for claim_key in claim.keys(): + if claim_key not in [0, 1]: + claim_value = claim[claim_key] + print(f"{indent} Field {claim_key}: {format_value_recursively(claim_value, indent + ' ')}") + elif isinstance(claim, list): + print(f"\n{indent} ๐Ÿ“‹ Identity Claim #{j+1} (List with {len(claim)} items):") + # Use the enhanced identity claim formatter + formatted_claim = format_identity_claim_human_readable(claim, indent + " ") + print(f"{indent} {formatted_claim}") + else: + print(f"{indent} ๐Ÿ“‹ Identity Claim #{j+1}: {format_value_recursively(claim, indent + ' ')}") + else: + print(f"{indent} ๐Ÿท๏ธ Claims: {type(claims).__name__}") + else: + print(f"{indent} ๐Ÿ†” Identity #{i+1}: Invalid structure - {type(identity_triple).__name__}") - if 10 in field_value: # conditional-endorsement-triples + # Check for conditional-endorsement-triples (field 10) + if 10 in field_value: cond_endorsements = field_value[10] if isinstance(cond_endorsements, list): - print(f"{indent} ๐Ÿ”„ {len(cond_endorsements)} conditional endorsement(s)") + print(f"{indent} ๐Ÿ”„ Conditional Endorsement Triples: {len(cond_endorsements)} item(s)") + triples_found = True for i, cond_endorsement in enumerate(cond_endorsements): if isinstance(cond_endorsement, list) and len(cond_endorsement) == 2: @@ -305,6 +733,29 @@ def inspect_comid_content(comid_content, indent=""): inspect_sfr_data(ext_value, indent + " ") else: print(f"{indent} Extension {ext_key}: {type(ext_value).__name__}") + else: + print(f"{indent} ๐Ÿ“ Measurements: {type(measurements).__name__}") + else: + print(f"{indent} ๐ŸŽฏ Endorsement #{j+1}: Invalid structure - {type(endorsement).__name__}") + else: + print(f"{indent} Endorsements: {type(endorsements).__name__}") + else: + print(f"{indent} ๐Ÿ“‹ Conditional Endorsement #{i+1}: Invalid structure - {type(cond_endorsement).__name__}") + + # Check for any other triple types + for triple_key in field_value.keys(): + if triple_key not in [0, 1, 9, 10]: + triple_data = field_value[triple_key] + print(f"{indent} ๐Ÿ” {explain_triple_type(triple_key)}: {type(triple_data).__name__}") + if isinstance(triple_data, list): + print(f"{indent} Contains {len(triple_data)} item(s)") + triples_found = True + + if not triples_found: + print(f"{indent} โš ๏ธ No recognized triple types found in triples structure") + print(f"{indent} ๐Ÿ” Available keys: {list(field_value.keys())}") + else: + print(f"{indent} โŒ Triples field should be a dictionary, found: {type(field_value)}") def inspect_sfr_data(sfr_data, indent=""): """Inspect SFR extension data in detail.""" @@ -351,6 +802,49 @@ def inspect_sfr_data(sfr_data, indent=""): print(f"{indent} SHA512: {format_bytes_display(fw_value)}") else: print(f"{indent} Field {fw_key}: {fw_value}") + elif isinstance(field_value, list): + print(f"{indent} ๐Ÿ“ฆ Firmware Info ({len(field_value)} items):") + for i, fw_item in enumerate(field_value): + print(f"{indent} Firmware #{i+1}:") + if isinstance(fw_item, dict): + for fw_key, fw_value in fw_item.items(): + if fw_key == 0: # vendor + if isinstance(fw_value, dict): + print(f"{indent} Vendor Info:") + for v_key, v_value in fw_value.items(): + if v_key == 0: + print(f"{indent} Version: {v_value}") + else: + print(f"{indent} Field {v_key}: {v_value}") + else: + print(f"{indent} Vendor: {fw_value}") + elif fw_key == 1: # product - often contains hash arrays + if isinstance(fw_value, list): + print(f"{indent} Product Hashes ({len(fw_value)} items):") + for j, hash_item in enumerate(fw_value): + if isinstance(hash_item, list) and len(hash_item) == 2: + hash_type, hash_value = hash_item + if isinstance(hash_value, bytes): + hash_type_name = "SHA384" if hash_type == 7 else "SHA512" if hash_type == 8 else f"Hash Type {hash_type}" + print(f"{indent} {hash_type_name}: {format_bytes_display(hash_value)}") + else: + print(f"{indent} Hash #{j+1}: Type {hash_type}, Value: {hash_value}") + else: + print(f"{indent} Hash #{j+1}: {hash_item}") + else: + print(f"{indent} Product: {fw_value}") + elif fw_key == 2: # version + print(f"{indent} Version: {fw_value}") + elif fw_key == 3: # hash-sha384 + print(f"{indent} SHA384: {format_bytes_display(fw_value)}") + elif fw_key == 4: # hash-sha512 + print(f"{indent} SHA512: {format_bytes_display(fw_value)}") + else: + print(f"{indent} Field {fw_key}: {fw_value}") + else: + print(f"{indent} {format_value_recursively(fw_item, indent + ' ')}") + else: + print(f"{indent} ๐Ÿ“ฆ Firmware Info: {format_value_recursively(field_value, indent + ' ')}") elif field_num == 5: # Device category if isinstance(field_value, int): @@ -392,17 +886,47 @@ def inspect_sfr_data(sfr_data, indent=""): else: print(f"{indent} Value: {field_value} (Type: {type(field_value).__name__})") +def show_help(): + """Display help information.""" + print("CBOR CoRIM Human-Readable Inspector") + print("=" * 50) + print("\nDESCRIPTION:") + print(" This tool provides human-readable inspection of CBOR CoRIM files.") + print(" Perfect for auditors who need to visually verify CoRIM contents.") + print(" Supports both unsigned CoRIMs and signed CoRIMs (COSE-Sign1/COSE-Sign).") + print("\nUSAGE:") + print(" python cbor_human_inspector.py [options]") + print("\nARGUMENTS:") + print(" cbor_file Path to the CBOR CoRIM file to inspect") + print("\nOPTIONS:") + print(" --show-raw Show raw CBOR data in addition to decoded structure") + print(" --help, -h Show this help message and exit") + print("\nEXAMPLES:") + print(" python cbor_human_inspector.py my_corim.cbor") + print(" python cbor_human_inspector.py my_corim.cbor --show-raw") + print(" python cbor_human_inspector.py signed_corim.jws") + print("\nSUPPORTED FORMATS:") + print(" โ€ข Unsigned CoRIMs (CBOR tag 501)") + print(" โ€ข COSE-Sign1 signed CoRIMs (CBOR tag 61)") + print(" โ€ข COSE-Sign multi-signer CoRIMs (CBOR tag 18)") + print("\nOUTPUT:") + print(" The tool provides detailed analysis including:") + print(" โ€ข CoRIM structure validation") + print(" โ€ข COMID tag inspection") + print(" โ€ข Identity claims and measurements") + print(" โ€ข SFR extension data (if present)") + print(" โ€ข Human-readable field explanations") + def main(): """Main function for command-line usage.""" + # Check for help flags first + if "--help" in sys.argv or "-h" in sys.argv: + show_help() + return 0 + if len(sys.argv) < 2: - print("Usage: python cbor_human_inspector.py [--show-raw]") - print("\nThis tool provides human-readable inspection of CBOR CoRIM files.") - print("Perfect for auditors who need to visually verify CoRIM contents.") - print("\nOptions:") - print(" --show-raw Show raw CBOR data in addition to decoded structure") - print("\nExample:") - print(" python cbor_human_inspector.py my_corim.cbor") - print(" python cbor_human_inspector.py my_corim.cbor --show-raw") + print("Usage: python cbor_human_inspector.py [options]") + print("Use --help for detailed usage information.") return 1 cbor_file = sys.argv[1] From 051c0c538bd59e1c0f36035c38d686df8244e630 Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Tue, 4 Nov 2025 14:16:27 -0800 Subject: [PATCH 6/8] Pinning Upstream Corim dependency to draft-08 Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- .github/workflows/validate-cddl.yml | 2 +- .github/workflows/validate-reports.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/validate-cddl.yml b/.github/workflows/validate-cddl.yml index af78829..5f35798 100644 --- a/.github/workflows/validate-cddl.yml +++ b/.github/workflows/validate-cddl.yml @@ -28,7 +28,7 @@ jobs: - name: Fetch latest upstream CoRIM CDDL run: | - curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/download/cddl-draft-ietf-rats-corim-08/corim-autogen.cddl - name: Concatenate the CDDLs run: | diff --git a/.github/workflows/validate-reports.yml b/.github/workflows/validate-reports.yml index f6966b4..23dfa44 100644 --- a/.github/workflows/validate-reports.yml +++ b/.github/workflows/validate-reports.yml @@ -63,7 +63,7 @@ jobs: - name: Prepare CDDL schema run: | # Fetch latest upstream CoRIM CDDL - curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/download/cddl-draft-ietf-rats-corim-08/corim-autogen.cddl # Concatenate the CDDLs cddlc -t cddl corim-base-upstream.cddl Documentation/corim_profile/ocp-safe-sfr-profile.cddl > combined.cddl @@ -130,7 +130,7 @@ jobs: - name: Fetch latest upstream CoRIM CDDL run: | - curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/download/cddl-draft-ietf-rats-corim-08/corim-autogen.cddl - name: Concatenate the CDDLs run: | @@ -168,7 +168,7 @@ jobs: - name: Prepare CDDL schema run: | - curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/latest/download/corim-autogen.cddl + curl -L -o corim-base-upstream.cddl https://github.com/ietf-rats-wg/draft-ietf-rats-corim/releases/download/cddl-draft-ietf-rats-corim-08/corim-autogen.cddl cddlc -t cddl corim-base-upstream.cddl Documentation/corim_profile/ocp-safe-sfr-profile.cddl > combined.cddl - name: Test JSON to CoRIM conversion From be7283a3e7a0d8dd9fc67c5938e9d54974d84b2a Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:13:21 -0800 Subject: [PATCH 7/8] Addressing comments/issues Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- .github/workflows/validate-reports.yml | 9 +++--- .../tests/final_validation_summary.py | 31 ++++++++++++++++++- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validate-reports.yml b/.github/workflows/validate-reports.yml index 23dfa44..049567b 100644 --- a/.github/workflows/validate-reports.yml +++ b/.github/workflows/validate-reports.yml @@ -175,10 +175,10 @@ jobs: run: | cd shortform_report-main - # Find a sample JSON file to test conversion - sample_json=$(find ../Reports/ -name "*.json" -not -name "*_converted*" | head -1) + # Use a specific JSON file for testing conversion + sample_json="../Reports/CHIPS_Alliance/2024/Caliptra/caliptra_fw_report.json" - if [ -n "$sample_json" ] && [ -f "$sample_json" ]; then + if [ -f "$sample_json" ]; then echo "Testing conversion of: $sample_json" # Convert JSON to CoRIM @@ -199,9 +199,8 @@ jobs: exit 1 fi else - echo "โ„น๏ธ No sample JSON files found for conversion testing" + echo "โ„น๏ธ Test JSON file not found: $sample_json" echo "Running final validation summary instead..." - cd shortform_report-main python tests/final_validation_summary.py fi diff --git a/shortform_report-main/tests/final_validation_summary.py b/shortform_report-main/tests/final_validation_summary.py index d93125b..0a0c0ec 100644 --- a/shortform_report-main/tests/final_validation_summary.py +++ b/shortform_report-main/tests/final_validation_summary.py @@ -275,10 +275,39 @@ def validate_corim_compliance(): encryption_algorithm=serialization.NoEncryption() ) - if report.sign_corim_report_pem(private_pem, "ES512", "test-validation-key"): + # Sign the CoRIM report + signing_result = report.sign_corim_report_pem(private_pem, "ES512", "test-validation-key") + if signing_result: signed_corim = report.get_signed_corim_report() print(f"โœ“ CoRIM signing successful ({len(signed_corim)} bytes)") print("โœ“ COSE-Sign1 format with cwt library") + + # Validate the signature is actually valid + try: + import cwt + from cwt import COSEKey + + # Extract public key from private key for verification + public_key = private_key.public_key() + public_pem = public_key.public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo + ) + + # Create COSE key for verification with proper key ID + cose_key = COSEKey.from_pem(public_pem, kid="test-validation-key") + + # Verify the signature + verified_payload = cwt.decode(signed_corim, cose_key) + if verified_payload: + print("โœ“ Signature verification successful") + else: + print("โœ— Signature verification failed - invalid signature") + return False + + except Exception as verify_error: + print(f"โœ— Signature verification failed: {verify_error}") + return False else: print("โœ— CoRIM signing failed") return False From 981cbffb807b882a95734369959346f57d97288e Mon Sep 17 00:00:00 2001 From: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> Date: Thu, 6 Nov 2025 09:25:01 -0800 Subject: [PATCH 8/8] Trying to fix the failure from integration Signed-off-by: Alex Tzonkov <4975715+attzonko@users.noreply.github.com> --- .github/workflows/validate-reports.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-reports.yml b/.github/workflows/validate-reports.yml index 049567b..bc8b58f 100644 --- a/.github/workflows/validate-reports.yml +++ b/.github/workflows/validate-reports.yml @@ -176,7 +176,7 @@ jobs: cd shortform_report-main # Use a specific JSON file for testing conversion - sample_json="../Reports/CHIPS_Alliance/2024/Caliptra/caliptra_fw_report.json" + sample_json="../Reports/AMD/2024/MI300X/OCP_SAFE_-_amd_asp_-_BL_-_Boot_Access_Module.json" if [ -f "$sample_json" ]; then echo "Testing conversion of: $sample_json"