From 8fef7620b60b417298ca54d651ab679683790cc8 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Mahawar <114311884+HarshvMahawar@users.noreply.github.com> Date: Tue, 4 Mar 2025 17:52:25 +0530 Subject: [PATCH 1/5] Enhance README.md with detailed technical overview of the project Signed-off-by: HarshvMahawar --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 537468d..1407903 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,68 @@ -# python-ear +# **python-ear** -A python implementation of [draft-fv-rats-ear](https://datatracker.ietf.org/doc/draft-fv-rats-ear/). +A Python library that implements the EAT Attestation Result (EAR) data format, as specified in [draft-fv-rats-ear](https://datatracker.ietf.org/doc/draft-fv-rats-ear/). This library provides implementations for both CBOR-based and JSON-based serialisations. -# Proposal +--- -Following are the tools that will be used in the development of this library +## **Overview** -## CWT and JWT creation +The goal of this project is to standardize attestation results by defining a shared information and data model, enabling seamless integration with other components of the RATS architecture. This focuses specifically on harmonizing attestation results to facilitate interoperability between various verifiers and relying parties. -1. [python-cwt](https://python-cwt.readthedocs.io/en/stable/) -2. [python-jwt](https://pypi.org/project/python-jose/) +This implementation was initiated as part of the **Veraison Mentorship** under the Linux Foundation Mentorship Program (**LFX Mentorship**), focusing on the following capabilities: -## Code formatting and styling +- **Populating EAR Claims-Sets:** Define and populate claims that represent evidence and attestation results. +- **Signing EAR Claims-Sets:** Support signing using private keys, ensuring data integrity and authenticity. +- **Encoding and Decoding:** + - Encode signed EAR claims as **CWT** (Concise Binary Object Representation Web Tokens) or **JWT** (JSON Web Tokens). + - Decode signed EARs from CWT or JWT formats, enabling interoperability between different systems. +- **Signature Verification:** Verify signatures using public keys to ensure the authenticity of claims. +- **Accessing Claims:** Provide interfaces to access and manage EAR claims efficiently. -1. [black](https://pypi.org/project/black/) -2. [isort](https://pypi.org/project/isort/) +This library is developed in Python and makes use of existing packages for CWT and JWT management, static code analysis, and testing. -## Linting and static analysis +--- -1. [flake8](https://pypi.org/project/flake8/) -2. [mypy](https://pypi.org/project/mypy/) +## **Key Features** -## Testing +1. **Standards Compliance:** + Implements draft-fv-rats-ear as per IETF specifications to ensure compatibility with the RATS architecture. -1. [pytest](https://pypi.org/project/pytest/) \ No newline at end of file +2. **Token Management:** + - **CWT Support:** Utilizes [python-cwt](https://python-cwt.readthedocs.io/en/stable/) for handling CBOR Web Tokens. + - **JWT Support:** Uses [python-jose](https://pypi.org/project/python-jose/) for JSON Web Tokens management. + +3. **Security:** + - Supports signing of EAR claims with private keys and verification with public keys. + - Adopts secure cryptographic practices for token creation and verification. + +4. **Static Analysis and Code Quality:** + - Ensures code quality using linters and static analysis tools. + - Maintains type safety and code consistency. + +5. **Testing:** + - Comprehensive unit tests using `pytest` to validate all functionalities. + +--- + +## **Technical Stack** + +### **Token Creation and Management** + +- **CWT:** [python-cwt](https://python-cwt.readthedocs.io/en/stable/) +- **JWT:** [python-jose](https://pypi.org/project/python-jose/) + +### **Code Formatting and Styling** + +- **black:** Ensures consistent code formatting. +- **isort:** Manages import statements. + +### **Linting and Static Analysis** + +- **flake8:** For PEP 8 compliance and linting. +- **mypy:** Static type checking. +- **pyright:** Advanced type checking for Python. +- **pylint:** Code analysis for error detection and enforcing coding standards. + +### **Testing** + +- **pytest:** Framework for writing and executing tests. \ No newline at end of file From 1ceccbdeb7282a07d55b21e75d112219f7cabca2 Mon Sep 17 00:00:00 2001 From: HarshvMahawar Date: Thu, 6 Mar 2025 05:03:35 +0530 Subject: [PATCH 2/5] Add GitHub Actions workflow for Tox Signed-off-by: HarshvMahawar --- .github/workflows/tox.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/tox.yml diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml new file mode 100644 index 0000000..d907ca5 --- /dev/null +++ b/.github/workflows/tox.yml @@ -0,0 +1,34 @@ +name: Run Tox on PR + +on: + pull_request: + branches: + - main + - '**' # Run on all branches for PRs + +jobs: + tox-tests: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.11] # Test against multiple Python versions + + steps: + # Checkout the code + - name: Checkout code + uses: actions/checkout@v3 + + # Setup Python + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + # Install tox + - name: Install tox + run: pip install tox + + # Run tox + - name: Run tox + run: tox \ No newline at end of file From c30cd5acb58547c105a3c9343c7bc7e665496aa6 Mon Sep 17 00:00:00 2001 From: HarshvMahawar Date: Thu, 6 Mar 2025 17:05:13 +0530 Subject: [PATCH 3/5] Add support for Python 3.9 in GitHub Actions workflow for Tox Signed-off-by: HarshvMahawar --- .github/workflows/tox.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index d907ca5..f68e652 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - python-version: [3.11] # Test against multiple Python versions + python-version: [3.9, 3.11] # Test against multiple Python versions steps: # Checkout the code From ab8fd223ff769cc69fa17838998a141b1e947115 Mon Sep 17 00:00:00 2001 From: Harsh Vardhan Mahawar <114311884+HarshvMahawar@users.noreply.github.com> Date: Wed, 12 Mar 2025 15:19:18 +0530 Subject: [PATCH 4/5] Add pytest in tox lint environment --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 2a75fe9..0d461b8 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ deps = mypy==1.5.1 pylint==2.17.5 pyright==1.1.325 + pytest==7.4.2 commands = isort . --check --diff black . --check --diff From 9cd1ff57ef8bb6e1b5fd785385bd68941367f376 Mon Sep 17 00:00:00 2001 From: HarshvMahawar Date: Wed, 12 Mar 2025 15:27:47 +0530 Subject: [PATCH 5/5] Add verifierID and its unit tests Signed-off-by: HarshvMahawar --- src/base.py | 28 +++++++++++++++++++++++++++ src/verifier_id.py | 36 +++++++++++++++++++++++++++++++++++ tests/test_verifier_id.py | 40 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+) create mode 100644 src/base.py create mode 100644 src/verifier_id.py create mode 100644 tests/test_verifier_id.py diff --git a/src/base.py b/src/base.py new file mode 100644 index 0000000..c7bc1fb --- /dev/null +++ b/src/base.py @@ -0,0 +1,28 @@ +import json +from abc import ABC, abstractmethod +from typing import Any, Dict + + +class BaseJCSerializable(ABC): + JC_map: Dict[str, int] + + @abstractmethod + def to_dict(self) -> Dict[str, Any]: + pass + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + + @abstractmethod + def to_cbor(self) -> Dict[int, Any]: + pass + + @classmethod + @abstractmethod + def from_dict(cls, data: Dict[str, Any]): + pass + + @classmethod + @abstractmethod + def from_cbor(cls, data: Dict[int, Any]): + pass diff --git a/src/verifier_id.py b/src/verifier_id.py new file mode 100644 index 0000000..f3d9ca3 --- /dev/null +++ b/src/verifier_id.py @@ -0,0 +1,36 @@ +from dataclasses import asdict, dataclass +from typing import Any, Dict + +from src.base import BaseJCSerializable + + +@dataclass +class VerifierID(BaseJCSerializable): + developer: str + build: str + # https://www.ietf.org/archive/id/draft-ietf-rats-ar4si-08.html#section-3.3 + JC_map = { + "developer": 0, # JC<"developer", 0> + "build": 1, # JC<"build", 1> + } + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + # Convert to a dict with integer keys (for CBOR) + def to_cbor(self) -> Dict[int, str]: + return { + index: getattr(self, field) for field, index in self.JC_map.items() + } # noqa: E501 + + # Create an instance from a dict with string keys + @classmethod + def from_dict(cls, data: Dict[str, str]): + return cls(**data) + + # Create an instance from a CBOR-like dict (integer keys) + @classmethod + def from_cbor(cls, data: Dict[int, str]): + reverse_map = {v: k for k, v in cls.JC_map.items()} + kwargs = {reverse_map[index]: value for index, value in data.items()} + return cls(**kwargs) diff --git a/tests/test_verifier_id.py b/tests/test_verifier_id.py new file mode 100644 index 0000000..372c6c5 --- /dev/null +++ b/tests/test_verifier_id.py @@ -0,0 +1,40 @@ +import json + +import pytest + +from src.verifier_id import VerifierID + + +@pytest.fixture +def verifier(): + # Sample VerifierID object for testing + return VerifierID(developer="Acme Inc.", build="v1.0.0") + + +def test_to_dict(verifier): # pylint: disable=redefined-outer-name + expected = {"developer": "Acme Inc.", "build": "v1.0.0"} + assert verifier.to_dict() == expected + + +def test_to_json(verifier): # pylint: disable=redefined-outer-name + expected = json.dumps({"developer": "Acme Inc.", "build": "v1.0.0"}) + assert verifier.to_json() == expected + + +def test_to_cbor(verifier): # pylint: disable=redefined-outer-name + expected = {0: "Acme Inc.", 1: "v1.0.0"} + assert verifier.to_cbor() == expected + + +def test_from_dict(): + data = {"developer": "Acme Inc.", "build": "v1.0.0"} + sample_verifier = VerifierID.from_dict(data) + assert sample_verifier.developer == "Acme Inc." + assert sample_verifier.build == "v1.0.0" + + +def test_from_cbor(): + cbor_data = {0: "Acme Inc.", 1: "v1.0.0"} + sample_verifier = VerifierID.from_cbor(cbor_data) + assert sample_verifier.developer == "Acme Inc." + assert sample_verifier.build == "v1.0.0"