Source code for aeat.adapters.persistence.storage.master_key._dek_wrap
"""AES-256-GCM wrap and unwrap of the per-bucket data-encryption key.The substrate wraps a freshly-generated 32-byte data-encryption key(DEK) under a passphrase-derived 32-byte key-encryption key (KEK) usingAES-256-GCM. The wrap binds to the bucket idthrough AEAD additional-authenticated-data (AAD), so the wrapped DEKfrom one bucket cannot be silently swapped under another bucket'smanifest at unlock.The on-wire shape is the typed `WrappedDek` record carrying:- `nonce` 12 random bytes produced afresh at every wrap.- `ciphertext` 32 bytes of AES-256-GCM ciphertext.- `tag` 16 bytes of AES-256-GCM authentication tag.`unwrap_dek` raises a typed storage `DecryptionError` on AEAD failure."""from__future__importannotationsimportsecretsfromcryptography.exceptionsimportInvalidTagfromcryptography.hazmat.primitives.ciphers.aeadimportAESGCMfrompydanticimportBaseModel,Fieldfrom.....coreimportSTRICT_FROZEN_CONFIGas_STRICT_FROZENfrom.....core.external_constantsimportUTF_8_ENCODINGas_UTF_8_ENCODINGfrom..errorsimportDecryptionError,EncryptionError_NONCE_BYTES=12_TAG_BYTES=16_DEK_BYTES=32_KEK_BYTES=32_STORAGE_DECRYPTION_MESSAGE_KEY="errors.integrity.integrity_storage_decryption"_STORAGE_ENCRYPTION_MESSAGE_KEY="errors.integrity.integrity_storage_encryption"
[docs]classWrappedDek(BaseModel):"""Frozen AES-256-GCM envelope around one bucket's data-encryption key."""model_config=_STRICT_FROZENnonce:bytes=Field(min_length=_NONCE_BYTES,max_length=_NONCE_BYTES)ciphertext:bytes=Field(min_length=_DEK_BYTES,max_length=_DEK_BYTES)tag:bytes=Field(min_length=_TAG_BYTES,max_length=_TAG_BYTES)
def_associated_data(bucket_id:str)->bytes:"""Compose the AEAD additional-authenticated-data for one bucket."""ifnotbucket_id:raise_encryption_error("bucket_id must be non-empty")returnf"aeat.dek-wrap.v1:{bucket_id}".encode(_UTF_8_ENCODING)def_encryption_error(message:str)->EncryptionError:returnEncryptionError(message,translated_message=_STORAGE_ENCRYPTION_MESSAGE_KEY)def_decryption_error(message:str)->DecryptionError:returnDecryptionError(message,translated_message=_STORAGE_DECRYPTION_MESSAGE_KEY)
[docs]defwrap_dek(*,kek:bytes,dek:bytes,bucket_id:str)->WrappedDek:"""Wrap `dek` under `kek` using AES-256-GCM keyed to `bucket_id`. Args: kek: 32-byte key-encryption key derived from the operator's passphrase via Argon2id. dek: 32-byte data-encryption key minted afresh at enrollment. bucket_id: Non-empty bucket identifier; bound into the AEAD AAD so the wrapped DEK cannot be re-mounted under a different bucket. Returns: A frozen :class:`WrappedDek` record carrying nonce, ciphertext, and tag. Raises: EncryptionError: If `kek` or `dek` is not 32 bytes, or `bucket_id` is empty. """iflen(kek)!=_KEK_BYTES:raise_encryption_error(f"kek must be exactly {_KEK_BYTES} bytes")iflen(dek)!=_DEK_BYTES:raise_encryption_error(f"dek must be exactly {_DEK_BYTES} bytes")nonce=secrets.token_bytes(_NONCE_BYTES)aad=_associated_data(bucket_id)try:cipher_with_tag=AESGCM(kek).encrypt(nonce,dek,aad)except(TypeError,ValueError)asexc:raise_encryption_error("DEK wrap failed")fromexcciphertext,tag=cipher_with_tag[:_DEK_BYTES],cipher_with_tag[_DEK_BYTES:]returnWrappedDek(nonce=nonce,ciphertext=ciphertext,tag=tag)
[docs]defunwrap_dek(*,kek:bytes,wrapped:WrappedDek,bucket_id:str)->bytes:"""Recover the 32-byte DEK from `wrapped` under `kek` and `bucket_id`. Args: kek: 32-byte key-encryption key. wrapped: Typed envelope produced by `wrap_dek`. bucket_id: Non-empty bucket identifier; must match the value bound at wrap time. Returns: The 32-byte data-encryption key. Raises: EncryptionError: When ``kek`` is not 32 bytes or ``bucket_id`` is empty. DecryptionError: When AEAD tag verification fails. """iflen(kek)!=_KEK_BYTES:raise_encryption_error(f"kek must be exactly {_KEK_BYTES} bytes")aad=_associated_data(bucket_id)cipher_with_tag=wrapped.ciphertext+wrapped.tagtry:returnAESGCM(kek).decrypt(wrapped.nonce,cipher_with_tag,aad)exceptInvalidTagasexc:raise_decryption_error("DEK unwrap tag verification failed")fromexcexcept(TypeError,ValueError)asexc:raise_decryption_error("DEK unwrap failed")fromexc