diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000..38ccd40 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,16 @@ +name: "CodeQL config" + +# Paths to exclude from analysis +paths-ignore: + - '**/tests/**' + - '**/test_*.py' + - 'tests/**' + - '.pytest_cache/**' + +# Optionally specify paths to include (if you want to be explicit) +# paths: +# - trustpoint + +# Query suites to use +# queries: +# - uses: security-extended diff --git a/.github/workflows/check-licenses.yml b/.github/workflows/check-licenses.yml new file mode 100644 index 0000000..987d825 --- /dev/null +++ b/.github/workflows/check-licenses.yml @@ -0,0 +1,85 @@ +name: Code Quality / License Check + +on: + pull_request: + types: + - opened + - synchronize + paths: + # Self + - ".github/workflows/check-licenses.yml" + # Python Ecosystem + - "**/pyproject.toml" + - "**/setup.py" + - "**/requirements*.txt" + - "**/Pipfile.lock" + - "**/uv.lock" + +jobs: + check-licenses: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout Code + uses: actions/checkout@v7 + + - name: Setup uv environment + uses: ./.github/actions/setup-uv-action + + - name: Install pip-licenses + run: uv tool install pip-licenses + + - name: Generate license report + run: uv tool run pip-licenses --format=markdown --order=license > licenses.md + continue-on-error: true + + - name: Check for incompatible licenses + run: | + # Check for GPL licenses (incompatible with MIT for distribution) + if uv tool run pip-licenses | grep -iE "GPL-[0-9]|GPLv[0-9]" | grep -v "LGPL"; then + echo "::error::Found GPL-licensed dependencies which may have compatibility issues with MIT license" + echo "GPL licenses found:" + uv tool run pip-licenses | grep -iE "GPL-[0-9]|GPLv[0-9]" | grep -v "LGPL" + exit 1 + fi + + # Check for proprietary licenses + if uv tool run pip-licenses | grep -iE "Proprietary|Commercial"; then + echo "::error::Found proprietary/commercial licenses that need review" + echo "Proprietary licenses found:" + uv tool run pip-licenses | grep -iE "Proprietary|Commercial" + exit 1 + fi + + echo "✅ No incompatible licenses detected" + + - name: Comment license report on PR + if: always() && github.event.pull_request.head.repo.full_name == github.repository + uses: actions/github-script@v9 + with: + script: | + const fs = require('fs'); + let report = ''; + try { + report = fs.readFileSync('licenses.md', 'utf8'); + } catch (e) { + report = 'âš ī¸ License report generation failed'; + } + + const comment = `## 📋 License Check Report + + ${report} + + --- + **Note:** This project is MIT-licensed. LGPL and most permissive licenses are compatible. GPL and proprietary licenses require review. + `; + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: comment + }); diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..eeadeb4 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,91 @@ +name: "Security / CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '44 7 * * 2' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v7 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + config-file: ./.github/codeql/codeql-config.yml + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index a7db8ef..125084e 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -1,4 +1,4 @@ -name: MyPy +name: Code Quality / MyPy # Trigger this workflow on pull requests, ensuring type checks are performed before merging. on: [pull_request] diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 9088fad..b3775ee 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,4 +1,4 @@ -name: Pytest +name: Tests / Pytest # Trigger this workflow on pull requests to ensure tests are executed before merging. on: diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index a575406..9462a94 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -1,4 +1,4 @@ -name: Publish Python Package to PyPi +name: CI/CD / Publish Python Package to PyPi on: push: diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 2556eee..08b8b90 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -1,4 +1,4 @@ -name: Ruff +name: Code Quality / Ruff # Trigger this workflow on pull requests to ensure tests are executed before merging. on: [pull_request] diff --git a/pyproject.toml b/pyproject.toml index 058e5d6..00d053d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [project] name = "trustpoint-core" -version = "0.3.1" +version = "0.3.2" description = "Trustpoint core functionalities used by several other Trustpoint repositories." requires-python = ">=3.12" dependencies = [ - "cryptography>=46.0.1", + "cryptography>=49.0.0", "hatchling>=1.27.0", ] diff --git a/tests/test_mldsa_integration.py b/tests/test_mldsa_integration.py new file mode 100644 index 0000000..4eeb453 --- /dev/null +++ b/tests/test_mldsa_integration.py @@ -0,0 +1,437 @@ +"""This module contains tests for ML-DSA (Module-Lattice-Based Digital Signature Algorithm) integration.""" + +import pytest +from cryptography.hazmat.primitives.asymmetric import mldsa + +from trustpoint_core.oid import ( + AlgorithmIdentifier, + KeyPairGenerator, + PublicKeyAlgorithmOid, + PublicKeyInfo, +) +from trustpoint_core.serializer import PrivateKeyReference, PrivateKeySerializer + +# ruff: noqa: SLF001 + + +# Fixtures for ML-DSA key generation +@pytest.fixture +def generate_mldsa44_private_key() -> mldsa.MLDSA44PrivateKey: + """Fixture to generate ML-DSA-44 private key for testing. + + Returns: + MLDSA44PrivateKey + """ + return mldsa.MLDSA44PrivateKey.generate() + + +@pytest.fixture +def generate_mldsa65_private_key() -> mldsa.MLDSA65PrivateKey: + """Fixture to generate ML-DSA-65 private key for testing. + + Returns: + MLDSA65PrivateKey + """ + return mldsa.MLDSA65PrivateKey.generate() + + +@pytest.fixture +def generate_mldsa87_private_key() -> mldsa.MLDSA87PrivateKey: + """Fixture to generate ML-DSA-87 private key for testing. + + Returns: + MLDSA87PrivateKey + """ + return mldsa.MLDSA87PrivateKey.generate() + + +# Tests for PublicKeyAlgorithmOid with ML-DSA keys +def test_public_key_algorithm_oid_from_mldsa44_public_key( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test PublicKeyAlgorithmOid detection from ML-DSA-44 public key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + public_key = generate_mldsa44_private_key.public_key() + algo_oid = PublicKeyAlgorithmOid.from_public_key(public_key) + + assert algo_oid == PublicKeyAlgorithmOid.ML_DSA_44 + assert algo_oid.verbose_name == 'ML-DSA-44' + assert algo_oid.dotted_string == '2.16.840.1.101.3.4.3.17' + + +def test_public_key_algorithm_oid_from_mldsa65_public_key( + generate_mldsa65_private_key: mldsa.MLDSA65PrivateKey, +) -> None: + """Test PublicKeyAlgorithmOid detection from ML-DSA-65 public key. + + Args: + generate_mldsa65_private_key: ML-DSA-65 private key fixture. + """ + public_key = generate_mldsa65_private_key.public_key() + algo_oid = PublicKeyAlgorithmOid.from_public_key(public_key) + + assert algo_oid == PublicKeyAlgorithmOid.ML_DSA_65 + assert algo_oid.verbose_name == 'ML-DSA-65' + assert algo_oid.dotted_string == '2.16.840.1.101.3.4.3.18' + + +def test_public_key_algorithm_oid_from_mldsa87_public_key( + generate_mldsa87_private_key: mldsa.MLDSA87PrivateKey, +) -> None: + """Test PublicKeyAlgorithmOid detection from ML-DSA-87 public key. + + Args: + generate_mldsa87_private_key: ML-DSA-87 private key fixture. + """ + public_key = generate_mldsa87_private_key.public_key() + algo_oid = PublicKeyAlgorithmOid.from_public_key(public_key) + + assert algo_oid == PublicKeyAlgorithmOid.ML_DSA_87 + assert algo_oid.verbose_name == 'ML-DSA-87' + assert algo_oid.dotted_string == '2.16.840.1.101.3.4.3.19' + + +# Tests for PublicKeyInfo with ML-DSA keys +def test_public_key_info_from_mldsa44_public_key( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test PublicKeyInfo creation from ML-DSA-44 public key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + public_key = generate_mldsa44_private_key.public_key() + pub_key_info = PublicKeyInfo.from_public_key(public_key) + + assert pub_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_44 + assert str(pub_key_info) == 'ML-DSA-44' + assert pub_key_info.named_curve is None + + +def test_public_key_info_from_mldsa65_public_key( + generate_mldsa65_private_key: mldsa.MLDSA65PrivateKey, +) -> None: + """Test PublicKeyInfo creation from ML-DSA-65 public key. + + Args: + generate_mldsa65_private_key: ML-DSA-65 private key fixture. + """ + public_key = generate_mldsa65_private_key.public_key() + pub_key_info = PublicKeyInfo.from_public_key(public_key) + + assert pub_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_65 + assert str(pub_key_info) == 'ML-DSA-65' + assert pub_key_info.named_curve is None + + +def test_public_key_info_from_mldsa87_public_key( + generate_mldsa87_private_key: mldsa.MLDSA87PrivateKey, +) -> None: + """Test PublicKeyInfo creation from ML-DSA-87 public key. + + Args: + generate_mldsa87_private_key: ML-DSA-87 private key fixture. + """ + public_key = generate_mldsa87_private_key.public_key() + pub_key_info = PublicKeyInfo.from_public_key(public_key) + + assert pub_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_87 + assert str(pub_key_info) == 'ML-DSA-87' + assert pub_key_info.named_curve is None + + +def test_public_key_info_from_mldsa44_private_key( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test PublicKeyInfo creation from ML-DSA-44 private key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + pub_key_info = PublicKeyInfo.from_private_key(generate_mldsa44_private_key) + + assert pub_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_44 + assert str(pub_key_info) == 'ML-DSA-44' + + +# Tests for KeyPairGenerator with ML-DSA keys +def test_key_pair_generator_for_mldsa44_public_key( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test KeyPairGenerator with ML-DSA-44 public key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + public_key = generate_mldsa44_private_key.public_key() + generated_key = KeyPairGenerator.generate_key_pair_for_public_key(public_key) + + assert isinstance(generated_key, mldsa.MLDSA44PrivateKey) + assert isinstance(generated_key.public_key(), mldsa.MLDSA44PublicKey) + + +def test_key_pair_generator_for_mldsa65_public_key( + generate_mldsa65_private_key: mldsa.MLDSA65PrivateKey, +) -> None: + """Test KeyPairGenerator with ML-DSA-65 public key. + + Args: + generate_mldsa65_private_key: ML-DSA-65 private key fixture. + """ + public_key = generate_mldsa65_private_key.public_key() + generated_key = KeyPairGenerator.generate_key_pair_for_public_key(public_key) + + assert isinstance(generated_key, mldsa.MLDSA65PrivateKey) + assert isinstance(generated_key.public_key(), mldsa.MLDSA65PublicKey) + + +def test_key_pair_generator_for_mldsa87_public_key( + generate_mldsa87_private_key: mldsa.MLDSA87PrivateKey, +) -> None: + """Test KeyPairGenerator with ML-DSA-87 public key. + + Args: + generate_mldsa87_private_key: ML-DSA-87 private key fixture. + """ + public_key = generate_mldsa87_private_key.public_key() + generated_key = KeyPairGenerator.generate_key_pair_for_public_key(public_key) + + assert isinstance(generated_key, mldsa.MLDSA87PrivateKey) + assert isinstance(generated_key.public_key(), mldsa.MLDSA87PublicKey) + + +def test_key_pair_generator_for_mldsa44_public_key_info() -> None: + """Test KeyPairGenerator with ML-DSA-44 PublicKeyInfo.""" + pub_key_info = PublicKeyInfo(public_key_algorithm_oid=PublicKeyAlgorithmOid.ML_DSA_44) + generated_key = KeyPairGenerator.generate_key_pair_for_public_key_info(pub_key_info) + + assert isinstance(generated_key, mldsa.MLDSA44PrivateKey) + + +def test_key_pair_generator_for_mldsa65_public_key_info() -> None: + """Test KeyPairGenerator with ML-DSA-65 PublicKeyInfo.""" + pub_key_info = PublicKeyInfo(public_key_algorithm_oid=PublicKeyAlgorithmOid.ML_DSA_65) + generated_key = KeyPairGenerator.generate_key_pair_for_public_key_info(pub_key_info) + + assert isinstance(generated_key, mldsa.MLDSA65PrivateKey) + + +def test_key_pair_generator_for_mldsa87_public_key_info() -> None: + """Test KeyPairGenerator with ML-DSA-87 PublicKeyInfo.""" + pub_key_info = PublicKeyInfo(public_key_algorithm_oid=PublicKeyAlgorithmOid.ML_DSA_87) + generated_key = KeyPairGenerator.generate_key_pair_for_public_key_info(pub_key_info) + + assert isinstance(generated_key, mldsa.MLDSA87PrivateKey) + + +def test_key_pair_generator_for_mldsa44_private_key( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test KeyPairGenerator with ML-DSA-44 private key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + generated_key = KeyPairGenerator.generate_key_pair_for_private_key(generate_mldsa44_private_key) + + assert isinstance(generated_key, mldsa.MLDSA44PrivateKey) + + +# Tests for AlgorithmIdentifier with ML-DSA +def test_algorithm_identifier_mldsa44() -> None: + """Test AlgorithmIdentifier for ML-DSA-44.""" + algo_id = AlgorithmIdentifier.ML_DSA_44 + + assert algo_id.verbose_name == 'ML-DSA-44' + assert algo_id.dotted_string == '2.16.840.1.101.3.4.3.17' + assert algo_id.public_key_algo_oid == PublicKeyAlgorithmOid.ML_DSA_44 + assert algo_id.hash_algorithm is None + assert algo_id.padding_scheme is None + + +def test_algorithm_identifier_mldsa65() -> None: + """Test AlgorithmIdentifier for ML-DSA-65.""" + algo_id = AlgorithmIdentifier.ML_DSA_65 + + assert algo_id.verbose_name == 'ML-DSA-65' + assert algo_id.dotted_string == '2.16.840.1.101.3.4.3.18' + assert algo_id.public_key_algo_oid == PublicKeyAlgorithmOid.ML_DSA_65 + assert algo_id.hash_algorithm is None + assert algo_id.padding_scheme is None + + +def test_algorithm_identifier_mldsa87() -> None: + """Test AlgorithmIdentifier for ML-DSA-87.""" + algo_id = AlgorithmIdentifier.ML_DSA_87 + + assert algo_id.verbose_name == 'ML-DSA-87' + assert algo_id.dotted_string == '2.16.840.1.101.3.4.3.19' + assert algo_id.public_key_algo_oid == PublicKeyAlgorithmOid.ML_DSA_87 + assert algo_id.hash_algorithm is None + assert algo_id.padding_scheme is None + + +def test_algorithm_identifier_from_dotted_string_mldsa44() -> None: + """Test AlgorithmIdentifier.from_dotted_string for ML-DSA-44.""" + algo_id = AlgorithmIdentifier.from_dotted_string('2.16.840.1.101.3.4.3.17') + + assert algo_id == AlgorithmIdentifier.ML_DSA_44 + assert algo_id.verbose_name == 'ML-DSA-44' + + +def test_algorithm_identifier_from_verbose_name_mldsa65() -> None: + """Test AlgorithmIdentifier.from_verbose_name for ML-DSA-65.""" + algo_id = AlgorithmIdentifier.from_verbose_name('ML-DSA-65') + + assert algo_id == AlgorithmIdentifier.ML_DSA_65 + assert algo_id.dotted_string == '2.16.840.1.101.3.4.3.18' + + +# Tests for PrivateKeyReference with ML-DSA keys +def test_private_key_reference_from_mldsa44_private_key( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test PrivateKeyReference creation from ML-DSA-44 private key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + key_ref = PrivateKeyReference.from_private_key(generate_mldsa44_private_key) + + assert key_ref.key_type is type(generate_mldsa44_private_key) + assert key_ref.is_software() + assert not key_ref.is_hsm_key() + + +def test_private_key_reference_from_mldsa65_private_key( + generate_mldsa65_private_key: mldsa.MLDSA65PrivateKey, +) -> None: + """Test PrivateKeyReference creation from ML-DSA-65 private key. + + Args: + generate_mldsa65_private_key: ML-DSA-65 private key fixture. + """ + key_ref = PrivateKeyReference.from_private_key(generate_mldsa65_private_key) + + assert key_ref.key_type is type(generate_mldsa65_private_key) + assert key_ref.is_software() + + +def test_private_key_reference_from_mldsa87_private_key( + generate_mldsa87_private_key: mldsa.MLDSA87PrivateKey, +) -> None: + """Test PrivateKeyReference creation from ML-DSA-87 private key. + + Args: + generate_mldsa87_private_key: ML-DSA-87 private key fixture. + """ + key_ref = PrivateKeyReference.from_private_key(generate_mldsa87_private_key) + + assert key_ref.key_type is type(generate_mldsa87_private_key) + assert key_ref.is_software() + + +# Tests for PrivateKeySerializer with ML-DSA keys +def test_private_key_serializer_init_mldsa44( + generate_mldsa44_private_key: mldsa.MLDSA44PrivateKey, +) -> None: + """Test PrivateKeySerializer initialization with ML-DSA-44 key. + + Args: + generate_mldsa44_private_key: ML-DSA-44 private key fixture. + """ + serializer = PrivateKeySerializer(generate_mldsa44_private_key) + assert serializer.as_crypto() == generate_mldsa44_private_key + + +def test_private_key_serializer_init_mldsa65( + generate_mldsa65_private_key: mldsa.MLDSA65PrivateKey, +) -> None: + """Test PrivateKeySerializer initialization with ML-DSA-65 key. + + Args: + generate_mldsa65_private_key: ML-DSA-65 private key fixture. + """ + serializer = PrivateKeySerializer(generate_mldsa65_private_key) + assert serializer.as_crypto() == generate_mldsa65_private_key + + +def test_private_key_serializer_init_mldsa87( + generate_mldsa87_private_key: mldsa.MLDSA87PrivateKey, +) -> None: + """Test PrivateKeySerializer initialization with ML-DSA-87 key. + + Args: + generate_mldsa87_private_key: ML-DSA-87 private key fixture. + """ + serializer = PrivateKeySerializer(generate_mldsa87_private_key) + assert serializer.as_crypto() == generate_mldsa87_private_key + + +# Integration tests +def test_mldsa_full_workflow_44() -> None: + """Test complete ML-DSA-44 workflow from generation to serialization.""" + # Generate key pair + private_key = mldsa.MLDSA44PrivateKey.generate() + public_key = private_key.public_key() + + # Test OID detection + algo_oid = PublicKeyAlgorithmOid.from_public_key(public_key) + assert algo_oid == PublicKeyAlgorithmOid.ML_DSA_44 + + # Test PublicKeyInfo + pub_key_info = PublicKeyInfo.from_public_key(public_key) + assert str(pub_key_info) == 'ML-DSA-44' + + # Test key generation from PublicKeyInfo + new_private_key = KeyPairGenerator.generate_key_pair_for_public_key_info(pub_key_info) + assert isinstance(new_private_key, mldsa.MLDSA44PrivateKey) + + # Test serialization + serializer = PrivateKeySerializer(private_key) + assert serializer.as_crypto() == private_key + + # Test PrivateKeyReference + key_ref = PrivateKeyReference.from_private_key(private_key) + assert key_ref.is_software() + + +def test_mldsa_full_workflow_65() -> None: + """Test complete ML-DSA-65 workflow from generation to serialization.""" + # Generate key pair + private_key = mldsa.MLDSA65PrivateKey.generate() + public_key = private_key.public_key() + + # Test OID detection + algo_oid = PublicKeyAlgorithmOid.from_public_key(public_key) + assert algo_oid == PublicKeyAlgorithmOid.ML_DSA_65 + + # Test PublicKeyInfo + pub_key_info = PublicKeyInfo.from_public_key(public_key) + assert str(pub_key_info) == 'ML-DSA-65' + + # Test AlgorithmIdentifier + algo_id = AlgorithmIdentifier.ML_DSA_65 + assert algo_id.public_key_algo_oid == PublicKeyAlgorithmOid.ML_DSA_65 + + +def test_mldsa_full_workflow_87() -> None: + """Test complete ML-DSA-87 workflow from generation to serialization.""" + # Generate key pair + private_key = mldsa.MLDSA87PrivateKey.generate() + public_key = private_key.public_key() + + # Test OID detection + algo_oid = PublicKeyAlgorithmOid.from_public_key(public_key) + assert algo_oid == PublicKeyAlgorithmOid.ML_DSA_87 + + # Test PublicKeyInfo + pub_key_info = PublicKeyInfo.from_public_key(public_key) + assert str(pub_key_info) == 'ML-DSA-87' + + # Test AlgorithmIdentifier + algo_id = AlgorithmIdentifier.ML_DSA_87 + assert algo_id.public_key_algo_oid == PublicKeyAlgorithmOid.ML_DSA_87 diff --git a/trustpoint_core/crypto_types.py b/trustpoint_core/crypto_types.py index 525a896..5bee9aa 100644 --- a/trustpoint_core/crypto_types.py +++ b/trustpoint_core/crypto_types.py @@ -3,10 +3,26 @@ from __future__ import annotations from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa +from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, mldsa, rsa -PublicKey = rsa.RSAPublicKey | ec.EllipticCurvePublicKey | ed25519.Ed25519PublicKey | ed448.Ed448PublicKey -PrivateKey = rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey | ed25519.Ed25519PrivateKey | ed448.Ed448PrivateKey +PublicKey = ( + rsa.RSAPublicKey + | ec.EllipticCurvePublicKey + | ed25519.Ed25519PublicKey + | ed448.Ed448PublicKey + | mldsa.MLDSA44PublicKey + | mldsa.MLDSA65PublicKey + | mldsa.MLDSA87PublicKey +) +PrivateKey = ( + rsa.RSAPrivateKey + | ec.EllipticCurvePrivateKey + | ed25519.Ed25519PrivateKey + | ed448.Ed448PrivateKey + | mldsa.MLDSA44PrivateKey + | mldsa.MLDSA65PrivateKey + | mldsa.MLDSA87PrivateKey +) AllowedCertSignHashAlgos = ( hashes.SHA224 | hashes.SHA256 diff --git a/trustpoint_core/oid.py b/trustpoint_core/oid.py index b191e97..ecd9146 100644 --- a/trustpoint_core/oid.py +++ b/trustpoint_core/oid.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, mldsa, rsa from trustpoint_core.crypto_types import PrivateKey, PublicKey @@ -21,6 +21,10 @@ RSA_MIN_KEY_SIZE = 2048 EC_MIN_KEY_SIZE = 128 +ML_DSA_44_PUBLIC_KEY_SIZE = 1312 +ML_DSA_65_PUBLIC_KEY_SIZE = 1952 +ML_DSA_87_PUBLIC_KEY_SIZE = 2592 + @dataclass(frozen=True) class NameOidData: @@ -517,6 +521,9 @@ class PublicKeyAlgorithmOid(enum.Enum): ECC = PublicKeyAlgorithmOidData('1.2.840.10045.2.1', 'ECC') RSA = PublicKeyAlgorithmOidData('1.2.840.113549.1.1.1', 'RSA') + ML_DSA_44 = PublicKeyAlgorithmOidData('2.16.840.1.101.3.4.3.17', 'ML-DSA-44') + ML_DSA_65 = PublicKeyAlgorithmOidData('2.16.840.1.101.3.4.3.18', 'ML-DSA-65') + ML_DSA_87 = PublicKeyAlgorithmOidData('2.16.840.1.101.3.4.3.19', 'ML-DSA-87') @property def dotted_string(self) -> str: @@ -600,7 +607,14 @@ def from_public_key(cls, public_key: PublicKey) -> PublicKeyAlgorithmOid: return cls.RSA if isinstance(public_key, ec.EllipticCurvePublicKey): return cls.ECC - err_msg = 'Unsupported key type, expected RSA or ECC key.' + if isinstance(public_key, (mldsa.MLDSA44PublicKey, mldsa.MLDSA65PublicKey, mldsa.MLDSA87PublicKey)): + if isinstance(public_key, mldsa.MLDSA44PublicKey): + return cls.ML_DSA_44 + if isinstance(public_key, mldsa.MLDSA65PublicKey): + return cls.ML_DSA_65 + if isinstance(public_key, mldsa.MLDSA87PublicKey): + return cls.ML_DSA_87 + err_msg = 'Unsupported key type, expected RSA, ECC, or ML-DSA key.' raise TypeError(err_msg) @@ -854,6 +868,29 @@ class AlgorithmIdentifier(enum.Enum): None, HashAlgorithm.SHA3_512, ) + + ML_DSA_44 = AlgorithmIdentifierData( + '2.16.840.1.101.3.4.3.17', + 'ML-DSA-44', + PublicKeyAlgorithmOid.ML_DSA_44, + None, + None, + ) + ML_DSA_65 = AlgorithmIdentifierData( + '2.16.840.1.101.3.4.3.18', + 'ML-DSA-65', + PublicKeyAlgorithmOid.ML_DSA_65, + None, + None, + ) + ML_DSA_87 = AlgorithmIdentifierData( + '2.16.840.1.101.3.4.3.19', + 'ML-DSA-87', + PublicKeyAlgorithmOid.ML_DSA_87, + None, + None, + ) + PASSWORD_BASED_MAC = AlgorithmIdentifierData( '1.2.840.113533.7.66.13', 'Password Based MAC', @@ -1009,7 +1046,7 @@ class PublicKeyInfo: _key_size: None | int = None _named_curve: None | NamedCurve = None - def __init__( + def __init__( # noqa: C901 self, public_key_algorithm_oid: PublicKeyAlgorithmOid, key_size: None | int = None, @@ -1040,6 +1077,20 @@ def __init__( raise ValueError(err_msg) self._key_size = named_curve.key_size self._named_curve = named_curve + elif self._public_key_algorithm_oid in ( + PublicKeyAlgorithmOid.ML_DSA_44, + PublicKeyAlgorithmOid.ML_DSA_65, + PublicKeyAlgorithmOid.ML_DSA_87, + ): + if self._public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_44: + self._key_size = ML_DSA_44_PUBLIC_KEY_SIZE + elif self._public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_65: + self._key_size = ML_DSA_65_PUBLIC_KEY_SIZE + elif self._public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_87: + self._key_size = ML_DSA_87_PUBLIC_KEY_SIZE + if named_curve is not None: + err_msg = 'ML-DSA keys cannot have a named curve associated with it.' + raise ValueError(err_msg) def __eq__(self, other: object) -> bool: """Defines the behaviour on use of the equality operator. @@ -1071,6 +1122,12 @@ def __str__(self) -> str: err_msg = 'Failed to determine named curve.' raise ValueError(err_msg) return f'ECC-{self.named_curve.verbose_name}' + if self.public_key_algorithm_oid in ( + PublicKeyAlgorithmOid.ML_DSA_44, + PublicKeyAlgorithmOid.ML_DSA_65, + PublicKeyAlgorithmOid.ML_DSA_87, + ): + return self.public_key_algorithm_oid.verbose_name return 'Invalid Signature Suite' @property @@ -1124,7 +1181,13 @@ def from_public_key(cls, public_key: PublicKey) -> PublicKeyInfo: key_size=public_key.key_size, named_curve=NamedCurve[public_key.curve.name.upper()], ) - err_msg = 'Unsupported public key type found. Must be RSA or ECC key.' + if isinstance(public_key, mldsa.MLDSA44PublicKey): + return cls(public_key_algorithm_oid=PublicKeyAlgorithmOid.ML_DSA_44) + if isinstance(public_key, mldsa.MLDSA65PublicKey): + return cls(public_key_algorithm_oid=PublicKeyAlgorithmOid.ML_DSA_65) + if isinstance(public_key, mldsa.MLDSA87PublicKey): + return cls(public_key_algorithm_oid=PublicKeyAlgorithmOid.ML_DSA_87) + err_msg = 'Unsupported public key type found. Must be RSA, ECC, or ML-DSA key.' raise TypeError(err_msg) @classmethod @@ -1344,6 +1407,12 @@ def generate_key_pair_for_public_key(public_key: PublicKey) -> PrivateKey: return rsa.generate_private_key(public_exponent=65537, key_size=public_key.key_size) if isinstance(public_key, ec.EllipticCurvePublicKey): return ec.generate_private_key(public_key.curve) + if isinstance(public_key, mldsa.MLDSA44PublicKey): + return mldsa.MLDSA44PrivateKey.generate() + if isinstance(public_key, mldsa.MLDSA65PublicKey): + return mldsa.MLDSA65PrivateKey.generate() + if isinstance(public_key, mldsa.MLDSA87PublicKey): + return mldsa.MLDSA87PrivateKey.generate() err_msg = 'Unsupported key type found.' raise TypeError(err_msg) @@ -1383,7 +1452,7 @@ def generate_key_pair_for_certificate(cls, certificate: x509.Certificate) -> Pri return cls.generate_key_pair_for_public_key(public_key) @staticmethod - def generate_key_pair_for_public_key_info( + def generate_key_pair_for_public_key_info( # noqa: C901 public_key_info: PublicKeyInfo, ) -> PrivateKey: """Generates a new key-pair of the type given by the PublicKeyInfo object. @@ -1419,6 +1488,12 @@ def generate_key_pair_for_public_key_info( err_msg = 'Curve not found.' raise ValueError(err_msg) return ec.generate_private_key(curve=public_key_info.named_curve.curve()) + if public_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_44: + return mldsa.MLDSA44PrivateKey.generate() + if public_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_65: + return mldsa.MLDSA65PrivateKey.generate() + if public_key_info.public_key_algorithm_oid == PublicKeyAlgorithmOid.ML_DSA_87: + return mldsa.MLDSA87PrivateKey.generate() err_msg = 'Unsupported key type found.' raise TypeError(err_msg) diff --git a/trustpoint_core/serializer.py b/trustpoint_core/serializer.py index 5a6a68b..0609649 100644 --- a/trustpoint_core/serializer.py +++ b/trustpoint_core/serializer.py @@ -8,7 +8,7 @@ from cryptography import exceptions as crypto_exceptions from cryptography import x509 from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric import ec, rsa +from cryptography.hazmat.primitives.asymmetric import ec, mldsa, rsa from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, pkcs7, pkcs12 from trustpoint_core.crypto_types import PrivateKey, PublicKey @@ -110,6 +110,10 @@ def set_key_details_from_private_key(self, private_key: PrivateKey) -> None: self.key_type = ec.EllipticCurvePrivateKey self.key_curve = NamedCurve.from_curve(type(private_key.curve)) self.key_size = None + elif isinstance(private_key, (mldsa.MLDSA44PrivateKey, mldsa.MLDSA65PrivateKey, mldsa.MLDSA87PrivateKey)): + self.key_type = type(private_key) + self.key_size = None + self.key_curve = None else: msg = f'Unsupported private key type: {type(private_key)}' raise TypeError(msg) @@ -703,9 +707,10 @@ def as_pkcs12(self, password: None | bytes = None, friendly_name: bytes = b'') - Returns: Bytes object that contains the private key in a PKCS#12 structure. """ + # Note: PKCS#12 doesn't yet support ML-DSA keys in cryptography library return pkcs12.serialize_key_and_certificates( name=friendly_name, - key=self._private_key, + key=self._private_key, # type: ignore[arg-type] cert=None, cas=None, encryption_algorithm=get_encryption_algorithm(password), @@ -1714,9 +1719,10 @@ def as_pkcs12(self, password: None | bytes = None, friendly_name: bytes = b'') - 'private key, certificate or certificate collection.' ) raise ValueError(err_msg) + # Note: PKCS#12 doesn't yet support ML-DSA keys in cryptography library return pkcs12.serialize_key_and_certificates( name=friendly_name, - key=self.private_key, + key=self.private_key, # type: ignore[arg-type] cert=self.certificate, cas=self.additional_certificates, encryption_algorithm=get_encryption_algorithm(password), diff --git a/uv.lock b/uv.lock index ebd8b1a..d24d195 100644 --- a/uv.lock +++ b/uv.lock @@ -79,55 +79,52 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.5" +version = "49.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, - { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, - { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, - { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, - { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, - { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, ] [[package]] @@ -490,7 +487,7 @@ wheels = [ [[package]] name = "trustpoint-core" -version = "0.3.1" +version = "0.3.2" source = { editable = "." } dependencies = [ { name = "cryptography" }, @@ -508,7 +505,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "cryptography", specifier = ">=46.0.1" }, + { name = "cryptography", specifier = ">=49.0.0" }, { name = "hatchling", specifier = ">=1.27.0" }, ]