aeat.adapters.persistence.storage.blob_store._blob_store module¶
Encrypted, classification-aware blob store.
Blobs are persisted under a content-addressed layout rooted at
Settings.aeat_blob_store_dir. Each blob carries a sidecar
BlobManifest Envelope that pins the sensitivity
classification, the wire-format SHA-256 (plaintext for CORPUS; ciphertext for
every other class), the optional wrapped data-encryption key, the AEAD nonce,
the original size, and the content type.
Two layouts are supported:
Plaintext (
SensitivityClass.CORPUSonly): blob bytes are written verbatim underblobs/<hex[:2]>/<hex>where<hex>is the plaintext SHA-256. The manifest recordsencryption=None.Ciphertext (every other class): the substrate mints a 32-byte data-encryption key (DEK), encrypts the blob with AES-256-GCM keyed by the DEK, wraps the DEK using the
MasterKeyProvider(also AES-256-GCM), and writes the ciphertext under the plaintext digest pathblobs/<hex[:2]>/<hex>.enc. The manifest records the ciphertext SHA-256, the wrapped DEK, and the AEAD nonces; the master key never touches disk.
The repository’s public read API returns plaintext bytes; the
sensitivity class drives whether decryption is performed under the
hood. Manifests are Envelope[BlobManifest] instances stored
under <hex>.manifest.json.
Attempting to write a blob whose declared classification is incompatible
with its layout (e.g. tagging an arbitrary blob as CORPUS to skip
encryption) raises ClassificationError. Missing blobs raise
BlobNotFoundError. SHA-256 disagreement between the
on-disk file and the manifest raises BlobIntegrityError.
- class BlobManifest(**data)[source]¶
Bases:
BaseModelFrozen manifest record for one blob in the encrypted blob store.
- Variables:
sha256_plaintext_hex – Lowercase hex digest of the original plaintext bytes. Always present.
sha256_ciphertext_hex – Lowercase hex digest of the ciphertext on disk.
Nonefor plaintext (CORPUS) blobs.size_plaintext – Size of the original plaintext in bytes.
content_type – Free-form MIME type or descriptive label (e.g.
application/pdf,application/json,application/octet-stream). The substrate does not parse or validate the value; it is recorded for forensic clarity only. Consumers that need a stricter classification should choose the appropriateSensitivityClassrather than relying on the content-type label.classification – The
SensitivityClassof the payload.wrapped_dek – Per-blob data-encryption key, AES-256-GCM-wrapped with the master key, expressed as JSON-friendly
EncryptionMetadata.Nonefor plaintext blobs.payload_metadata – AEAD metadata for the payload itself (nonce + algorithm). The ciphertext lives on disk; this field carries only the surrounding metadata.
Nonefor plaintext blobs.
- Parameters:
sha256_plaintext_hex (str)
sha256_ciphertext_hex (str | None)
size_plaintext (int)
content_type (str)
classification (SensitivityClass)
wrapped_dek (EncryptionMetadata | None)
payload_metadata (EncryptionMetadata | None)
- sha256_plaintext_hex: str¶
- sha256_ciphertext_hex: str | None¶
- size_plaintext: int¶
- content_type: str¶
- classification: SensitivityClass¶
- wrapped_dek: EncryptionMetadata | None¶
- payload_metadata: EncryptionMetadata | None¶
- class BlobReference(**data)[source]¶
Bases:
BaseModelFrozen public handle for one blob.
- Variables:
sha256_plaintext_hex – Lowercase hex digest of the plaintext. This is the natural key for retrieval; the consumer computes it from the plaintext at write time.
classification – The classification used at write time. Required because the on-disk layout differs by class (plaintext vs ciphertext); the get path uses this to find the file.
- Parameters:
sha256_plaintext_hex (str)
classification (SensitivityClass)
- sha256_plaintext_hex: str¶
- classification: SensitivityClass¶
- class EncryptedBlobStore(*, root_dir, master_key_provider=None)[source]¶
Bases:
objectRepository for the at-rest, classification-aware blob store.
- Parameters:
root_dir (Path)
master_key_provider (MasterKeyProvider | None)
- put(plaintext, *, classification, content_type='application/octet-stream')[source]¶
Persist
plaintextand return a reference for retrieval.The on-disk layout is dictated by
classification: CORPUS-class blobs are written verbatim; every other class is encrypted with a fresh per-blob DEK that is then wrapped with the master key.- Parameters:
plaintext (
bytes) – Bytes to persist.classification (
SensitivityClass) –SensitivityClasscontrolling the at-rest treatment (plaintext for CORPUS, ciphertext for all other classes).content_type (
str) – Stable MIME-type-style label stored in the manifest.
- Return type:
- Returns:
A
BlobReferencekeyed by the plaintext SHA-256 and the classification.- Raises:
ClassificationError – If a non-CORPUS class is requested but the policy table forbids ciphertext for it (defensive check; the default table never trips this).
- get(reference)[source]¶
Return the plaintext bytes for
reference.- Parameters:
reference (
BlobReference) – The blob reference identifying the stored object.- Return type:
- Returns:
Decrypted plaintext bytes for the referenced blob.
- Raises:
BlobNotFoundError – When the manifest or payload file is missing.
- delete(reference)[source]¶
Remove the blob and its manifest.
Order: payload bytes (plaintext or ciphertext) are unlinked first, then the manifest. If the payload unlink fails, the manifest is left in place so a subsequent
getsurfaces aBlobIntegrityErrorrather than a silentBlobNotFoundError(sec-M-2).- Return type:
- Parameters:
reference (BlobReference)
- iter_manifests()[source]¶
Yield the
BlobManifestof every blob currently persisted.The walk is shallow: only the canonical
blobs/<hex[:2]>/<hex>.manifest.jsonfiles are visited. Each manifest is loaded through a single-read + inline version-gate path; corrupted or unparseable manifests fail closed so corruption does not disappear from audit flows.- Return type:
- rotate_master_key(*, old_master_key_provider, new_master_key_provider)[source]¶
Re-wrap every blob’s per-record DEK under the new master key.
The blob store wraps each blob’s DEK directly under the master key (
encrypt_record(dek, key=master_key, associated_data=_DEK_AAD)). When the master key rotates, every wrapped DEK must be re-wrapped under the new master key or the blob becomes unrecoverable.Resume-idempotent: re-running on an already-rotated store decrypts the wrapped DEK under the new master key first; on success the manifest is skipped.
- Parameters:
old_master_key_provider (
MasterKeyProvider) –MasterKeyProviderreturning the master key that was in use when the blobs were last persisted.new_master_key_provider (
MasterKeyProvider) –MasterKeyProviderreturning the new master key.
- Return type:
- Returns:
A
(rotated, skipped, errors)triple.