aeat.core.corpus_manifest._bundle_signing module

Ed25519 authenticity signing for offline corpus bundles.

build_corpus_bundle() and verify_corpus_bundle() give a distributable corpus .zip an INTEGRITY guarantee: every archived file matches its embedded SHA-256 manifest record, and the manifest itself is self-attesting. That is deliberately silent on AUTHENTICITY – a bundle rebuilt by anyone from an unmodified corpus tree verifies exactly as clean as one built by the project’s maintainers, so an installer with only the checksum layer cannot tell “this bundle’s checksums are internally consistent” from “this bundle was actually published by the project”.

This module adds that authenticity layer on top, following the SAME pattern _review_package_signing established for review-package signing: sign the bundle’s self-attesting manifest_sha256 digest with an Ed25519 keypair (RFC 8032), so the signature transitively covers every archived member (a tampered member is already caught by verify_corpus_bundle() before the signature check is even attempted; see verify_corpus_bundle_signature()).

Key custody differs from the review-package case by necessity. A review package is signed by one profile’s operator and verified by a peer who receives the public key out of band; a corpus bundle is signed ONCE by the project’s maintainers (an offline, pre-distribution act, with no profile bucket in scope) and verified by every installer, including installers who have never provisioned a profile at all. This module therefore has no SecureObjectRepository dependency (this is a core module; core may not import adapters or application – see the core-not-outer import-linter contract) and persists a maintainer’s private key as a hex-encoded file on disk instead, hardened with restrict_file_permissions() (the same best-effort POSIX chmod 0o600 / Windows ACL hardening the AEAT-session-state writers use). The corresponding public key carries no secrecy requirement: it is meant to be embedded in the aeat distribution (or passed explicitly) so every installer can verify a downloaded bundle against a key they already trust, without contacting anyone.

See also

corpus_manifest

Builds and integrity-verifies the bundle this module signs.

_review_package_signing

The sibling implementation of the same signing primitive, scoped to one profile bucket’s review-package authenticity instead of a maintainer-published corpus bundle.

exception CorpusBundleSigningError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: AeatError

Base error for corpus-bundle signing/verification failures.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
exception CorpusBundleSigningKeyNotFoundError(message=None, *, context=None, suggestion=None, translated_message=None)[source]

Bases: CorpusBundleSigningError

Raised when no signing keypair file exists at the requested path.

Callers should mint one via generate_corpus_signing_keypair() before signing a bundle for the first time.

Parameters:
  • message (str | None)

  • context (Mapping[str, object] | None)

  • suggestion (str | None)

  • translated_message (str | None)

Return type:

None

code: ClassVar[ErrorCode]
class CorpusSigningKeypair(**data)[source]

Bases: BaseModel

A maintainer’s Ed25519 corpus-signing keypair, private key included.

This model is the PLAINTEXT in-memory shape used only transiently around generation, persistence, and signing; private_key() / public_key() reconstruct live cryptography key objects from the stored raw hex bytes. The caller (generate_corpus_signing_keypair() / load_corpus_signing_keypair()) is responsible for keeping the on-disk file permission-hardened; this model carries no persistence logic of its own.

Parameters:
private_key_hex: str
public_key_hex: str
created_at: datetime
private_key()[source]

Reconstruct the live Ed25519PrivateKey from stored raw bytes.

Return type:

Ed25519PrivateKey

public_key()[source]

Reconstruct the live Ed25519PublicKey from stored raw bytes.

Return type:

Ed25519PublicKey

class CorpusSigningPublicKey(**data)[source]

Bases: BaseModel

The exportable, non-secret half of a maintainer’s corpus-signing keypair.

Safe to embed in the aeat distribution, print, or transmit so every installer can verify a downloaded bundle’s signature independently. Carries no secrecy requirement – unlike CorpusSigningKeypair, this model is fine to write to a plaintext file.

Parameters:
public_key_hex: str
created_at: datetime
class SignedCorpusBundle(**data)[source]

Bases: BaseModel

Signature envelope binding a corpus bundle’s manifest digest to a signer.

manifest_sha256 is the bundle’s own self-attesting manifest_sha256 (recovered via verify_corpus_bundle()), NOT a re-derived hash of the archive bytes: signing the manifest digest transitively covers every archived member because the manifest digest already covers every per-file checksum record.

Parameters:
  • envelope_version (int)

  • corpus_root_name (str)

  • manifest_sha256 (str)

  • signature_hex (str)

  • public_key_hex (str)

  • signed_at (datetime)

envelope_version: int
corpus_root_name: str
manifest_sha256: str
signature_hex: str
public_key_hex: str
signed_at: datetime
generate_corpus_signing_keypair(*, private_key_path, generated_at=None)[source]

Mint a fresh Ed25519 keypair and persist it (private key included) to disk.

Writes the keypair as JSON to private_key_path, then hardens the file’s permissions via restrict_file_permissions() (best-effort chmod 0o600 on POSIX; an ACL-stripping icacls call on Windows). This is a maintainer-side, offline, one-time act: unlike the review-package keypair (minted per profile bucket, on demand), a corpus signing keypair is generated once by the project and its public half is then embedded in the distribution or handed to installers explicitly.

Parameters:
  • private_key_path (Path) – Destination path for the keypair JSON. Parent directories are created as needed. An existing file at this path is overwritten – callers who want key rotation safety should back up the existing key first.

  • generated_at (datetime | None) – Optional override for the keypair’s created_at timestamp (tests only); defaults to the current UTC time.

Return type:

CorpusSigningKeypair

Returns:

The freshly minted CorpusSigningKeypair.

load_corpus_signing_keypair(private_key_path)[source]

Load a maintainer’s existing Ed25519 corpus-signing keypair from disk.

Parameters:

private_key_path (Path) – Path to the keypair JSON written by generate_corpus_signing_keypair().

Raises:
Return type:

CorpusSigningKeypair

corpus_signing_public_key(keypair)[source]

Project the exportable public half out of a full keypair.

The projection never touches private_key_hex; the returned model is safe to embed in the distribution or hand to an installer.

Return type:

CorpusSigningPublicKey

Returns:

A CorpusSigningPublicKey carrying only the public verifier material and creation timestamp.

Parameters:

keypair (CorpusSigningKeypair)

sign_corpus_bundle(bundle_path, *, keypair, signed_at=None)[source]

Verify bundle_path’s checksum manifest, then sign its digest.

Delegates the integrity check entirely to assert_corpus_bundle_verifies() (no hashing logic is re-derived here): a bundle that is not checksum-clean raises before any signature is produced, so a signature can never be minted over a bundle this module itself cannot vouch is intact.

Parameters:
Raises:
Return type:

SignedCorpusBundle

verify_corpus_bundle_signature(bundle_path, signed_bundle, *, public_key_hex)[source]

Verify signed_bundle’s signature against public_key_hex.

Re-runs the checksum-manifest integrity check (a fresh verify_corpus_bundle() call, not a trust of signed_bundle.manifest_sha256) first. This matters because manifest_sha256 is a digest over the embedded manifest’s OWN recorded per-file hashes – it does NOT change if an archived member’s bytes are swapped without touching the manifest metadata; only the mismatched check (re-hashing every archived file against its manifest record) catches that tamper. So a bundle whose CURRENT bytes are not checksum-clean, or whose current manifest digest no longer matches the signed digest, fails here even before the Ed25519 check runs.

Parameters:
  • bundle_path (Path) – Path to the corpus bundle .zip to verify.

  • signed_bundle (SignedCorpusBundle) – The SignedCorpusBundle envelope produced by sign_corpus_bundle().

  • public_key_hex (str) – The signer’s raw public key, as 64 lowercase hex characters (see public_key_hex). Passed explicitly (never read off signed_bundle) so a verifier must supply the key it actually trusts, rather than trusting whatever key the envelope claims.

Return type:

bool

Returns:

True iff the bundle is currently checksum-clean, its manifest digest matches the signed digest, AND the Ed25519 signature verifies against public_key_hex. Returns False (never raises) on any mismatch or invalid-signature outcome – signature verification is an authenticity check, not an assertion; callers that want a raising assertion should call assert_corpus_bundle_signature_verifies().

assert_corpus_bundle_signature_verifies(bundle_path, signed_bundle, *, public_key_hex)[source]

Raise unless signed_bundle verifies clean against public_key_hex.

The install-time assertion: call this before extracting or trusting a downloaded/copied corpus bundle whose signature you hold. Raises CorpusBundleSigningError naming the bundle path on any failure – a checksum mismatch, a manifest-digest mismatch, or an invalid signature are not distinguished in the error message beyond naming the bundle, matching verify_corpus_bundle_signature()’s deliberately coarse boolean contract (a tampered bundle and a forged signature must both refuse installation identically).

Return type:

None

Parameters: