Establish certifiable Voting provider boundary

This commit is contained in:
2026-08-04 14:01:26 +02:00
parent 0168c5ecd5
commit 8cfd6bfd48
8 changed files with 371 additions and 2 deletions
+104 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import UTC, datetime
from datetime import UTC, datetime, timedelta
import hashlib
from types import SimpleNamespace
import unittest
@@ -10,10 +10,13 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from govoplan_core.core.voting import (
VOTING_CERTIFICATION_CERTIFIED,
VOTING_CERTIFICATION_IN_EVALUATION,
VotingBallotCreateCommand,
VotingCastCommand,
VotingElector,
VotingOption,
VotingProviderAssuranceDeclaration,
voting_provider_capability,
)
from govoplan_core.core.encryption import (
@@ -51,6 +54,17 @@ class FakeRegistry:
return self.capabilities.get(name)
class FakeExternalVotingProvider:
def __init__(self, declaration: VotingProviderAssuranceDeclaration) -> None:
self.declaration = declaration
def assurance_declaration(self) -> VotingProviderAssuranceDeclaration:
return self.declaration
def finalize_ballot(self, session, principal, *, request):
raise AssertionError("finalization should not run in assurance gate tests")
class FakeKeyVault:
def __init__(self) -> None:
self.vaults: dict[tuple[str, str], object] = {}
@@ -290,6 +304,95 @@ class VotingTests(unittest.TestCase):
idempotency_key="open-secret",
)
def test_external_certified_provider_claim_is_current_and_frozen(self) -> None:
now = datetime.now(UTC)
candidate = FakeExternalVotingProvider(
VotingProviderAssuranceDeclaration(
provider_id="certified",
implementation_ref="vendor/adapter@1",
supported_assurance_profiles=("external_certified",),
certification_state=VOTING_CERTIFICATION_IN_EVALUATION,
protocol_ref="vendor:ballot",
protocol_version="3.0",
)
)
registry = FakeRegistry()
registry.capabilities[voting_provider_capability("certified")] = candidate
service = SqlVotingBallots(registry)
with self.Session() as session:
created = service.create_ballot(
session,
self.manager,
command=command(
assurance="external_certified",
provider_id="certified",
),
idempotency_key="create-certified-candidate",
)
with self.assertRaisesRegex(VotingStoreError, "currently valid"):
service.open_ballot(
session,
self.manager,
ballot_id=created.id,
expected_revision=created.revision,
idempotency_key="open-certified-candidate",
)
candidate.declaration = VotingProviderAssuranceDeclaration(
provider_id="certified",
implementation_ref="vendor/adapter@1",
supported_assurance_profiles=("external_certified",),
certification_state=VOTING_CERTIFICATION_CERTIFIED,
protocol_ref="vendor:ballot",
protocol_version="3.0",
certification_authority="Independent authority",
certification_reference="certificate-2026-1",
certification_evidence_ref="evidence://certificate-2026-1",
certification_valid_from=now - timedelta(days=1),
certification_valid_until=now + timedelta(days=1),
)
with self.Session() as session:
created = service.create_ballot(
session,
self.manager,
command=command(
assurance="external_certified",
provider_id="certified",
),
idempotency_key="create-certified",
)
opened = service.open_ballot(
session,
self.manager,
ballot_id=created.id,
expected_revision=created.revision,
idempotency_key="open-certified",
)
detail = service.get_ballot(
session,
self.manager,
ballot_id=created.id,
)
self.assertEqual(
"certificate-2026-1",
detail["provider_assurance"]["certification_reference"],
)
candidate.declaration = replace(
candidate.declaration,
certification_reference="certificate-2026-2",
certification_evidence_ref="evidence://certificate-2026-2",
)
with self.assertRaisesRegex(VotingStoreError, "changed after"):
service.close_ballot(
session,
self.manager,
ballot_id=opened.id,
expected_revision=opened.revision,
idempotency_key="close-certified",
)
def test_local_confidential_provider_encrypts_casts_and_returns_aggregates(
self,
) -> None: