feat(postbox): expose configurable protection contracts
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
# Postbox End-To-End Encryption Architecture
|
# Postbox End-To-End Encryption Architecture
|
||||||
|
|
||||||
This document records the strategic encryption target for GovOPlaN postboxes.
|
This document records the encryption boundary for GovOPlaN postboxes. Postbox
|
||||||
It does not require the first postbox implementation to ship full E2EE, but it
|
now implements the server-side contracts for three selectable profiles:
|
||||||
defines the architecture so early data models and APIs do not make the stronger
|
unencrypted content, institution-managed server envelopes, and externally
|
||||||
model impossible.
|
produced E2EE envelopes. The E2EE contract is operational—the server rejects
|
||||||
|
plaintext and retains ciphertext, signed manifests, wrapped keys, and digest
|
||||||
|
evidence—but a reviewed browser/device client and private-key custody provider
|
||||||
|
remain separately deployed responsibilities.
|
||||||
|
|
||||||
The core principle is that a postbox can become a trusted administrative
|
The core principle is that a postbox can become a trusted administrative
|
||||||
communication channel without requiring the server to see plaintext content.
|
communication channel without requiring the server to see plaintext content.
|
||||||
@@ -35,6 +38,54 @@ Algorithm choices should remain replaceable behind a crypto profile. The first
|
|||||||
profile should prefer standard, reviewed primitives such as HPKE for key
|
profile should prefer standard, reviewed primitives such as HPKE for key
|
||||||
wrapping and AEAD encryption for content.
|
wrapping and AEAD encryption for content.
|
||||||
|
|
||||||
|
## Product Profiles And Default
|
||||||
|
|
||||||
|
The content-protection policy is configurable per exact Postbox or immutable
|
||||||
|
template revision:
|
||||||
|
|
||||||
|
- `server_envelope_v1` is the recommended default. An institution-selected
|
||||||
|
Encryption vault controls server-readable envelopes and their migration
|
||||||
|
evidence. It is not end-to-end encryption.
|
||||||
|
- `external_e2ee_v1` is server-blind. An approved client or producer supplies
|
||||||
|
the ciphertext reference, signed manifest, wrapped recipient keys, key epoch,
|
||||||
|
and SHA-256 plaintext digest. GovOPlaN has no private key that can decrypt it.
|
||||||
|
- `plaintext_v1` stores clear content for institutions that explicitly choose
|
||||||
|
that boundary.
|
||||||
|
|
||||||
|
Operational metadata—including subject, routing, participants,
|
||||||
|
classifications, timestamps, attachment references, receipts, and retention
|
||||||
|
state—remains visible under every profile. Administrators therefore choose a
|
||||||
|
content-protection boundary, not a metadata-anonymity profile.
|
||||||
|
|
||||||
|
The standard policy grants new incumbents history since assignment, uses key
|
||||||
|
rewrapping for ordinary rotation and content re-encryption after compromise,
|
||||||
|
requires two-person institutional recovery and dual-control hand-over,
|
||||||
|
emergency, export, and destruction, requires strong external identity, and
|
||||||
|
limits vacancy escalation to metadata. Deployments may select other policy
|
||||||
|
values rather than inheriting a decision from GovOPlaN.
|
||||||
|
|
||||||
|
## Governed Profile Changes
|
||||||
|
|
||||||
|
A profile transition applies to new messages immediately and increments the
|
||||||
|
Postbox key epoch. Retained history can remain under the previous profile or be
|
||||||
|
migrated. The transition ledger records source and target profiles/vaults,
|
||||||
|
authority route, consent and key-holder evidence, quorum, reason, immutable
|
||||||
|
configuration snapshot, per-message source and target digest, and outcome.
|
||||||
|
|
||||||
|
Plaintext and managed-envelope migrations can use the server-side Encryption
|
||||||
|
capability. Managed decrypt, export, and re-encryption operations also create
|
||||||
|
Encryption migration records so old envelopes are disposed of through the
|
||||||
|
governed provider contract. Any transition to or from E2EE pauses each retained
|
||||||
|
message for an approved client transform. The client must return plaintext or
|
||||||
|
ciphertext as appropriate, plus evidence and the original content digest;
|
||||||
|
Postbox verifies digest continuity before changing the stored representation.
|
||||||
|
Leaving E2EE requires user-consent evidence, while changing managed history
|
||||||
|
requires institutional key-holder evidence. Dual control can require both.
|
||||||
|
|
||||||
|
This transition mechanism cannot revoke plaintext already decrypted, copied,
|
||||||
|
printed, or exported. Administrators must explicitly acknowledge that residual
|
||||||
|
disclosure before a transition is accepted.
|
||||||
|
|
||||||
## Identity And Device Keys
|
## Identity And Device Keys
|
||||||
|
|
||||||
The platform should distinguish:
|
The platform should distinguish:
|
||||||
|
|||||||
@@ -172,6 +172,10 @@ class PostboxDirectoryEntryRef:
|
|||||||
template_revision_id: str | None = None
|
template_revision_id: str | None = None
|
||||||
holder_count: int = 0
|
holder_count: int = 0
|
||||||
vacant: bool = True
|
vacant: bool = True
|
||||||
|
encryption_profile: str = "plaintext_v1"
|
||||||
|
key_epoch: int = 1
|
||||||
|
encryption_vault_id: str | None = None
|
||||||
|
protection_policy: Mapping[str, object] = field(default_factory=dict)
|
||||||
access: PostboxAccessDecisionRef | None = None
|
access: PostboxAccessDecisionRef | None = None
|
||||||
resource_revision: int = 1
|
resource_revision: int = 1
|
||||||
etag: str | None = None
|
etag: str | None = None
|
||||||
@@ -260,6 +264,9 @@ class PostboxMessageAuthoringRequest:
|
|||||||
idempotency_key: str
|
idempotency_key: str
|
||||||
subject: str
|
subject: str
|
||||||
body_text: str | None = None
|
body_text: str | None = None
|
||||||
|
ciphertext_ref: str | None = None
|
||||||
|
signed_manifest_ref: str | None = None
|
||||||
|
wrapped_keys: tuple[PostboxWrappedKeyRef, ...] = ()
|
||||||
classification: str = "internal"
|
classification: str = "internal"
|
||||||
participants: tuple[PostboxParticipantRef, ...] = ()
|
participants: tuple[PostboxParticipantRef, ...] = ()
|
||||||
attachments: tuple[PostboxAttachmentRef, ...] = ()
|
attachments: tuple[PostboxAttachmentRef, ...] = ()
|
||||||
|
|||||||
Reference in New Issue
Block a user