The one place this package reads the engine's message format.
A ciphertext written by this package is an AWS Encryption SDK message, and its header carries in the clear - to anyone holding the bytes - the encryption context, the algorithm suite, and the provider and key name of every encrypted data key. This module parses that header and nothing else. It holds no state, reads no configuration, touches no vault and no provider, and needs no key material.
The dependency is deliberate and it is here
ADR-0002 open question 1 was reluctant to depend on the engine's message
layout at all. Three accepted decisions then required it: describe/1
(ADR-0004 decision 12), the vault-side reproduced-context value comparison
(ADR-0004 decision 6), and rekey/2's reproduction of the stored context
from the message (ADR-0004 decision 11). ADR-0005 open question 3 records
that a fourth caller - the rotation census - wants the same function.
Building it once, as a pure parse, is what makes the other three cheap. It
also means the dependency has a single address: if the engine's header
layout changes, or this package moves off aws_encryption_sdk, this module
is the file to read.
Nothing here authenticates anything
AwsEncryptionSdk.Format.Header.deserialize/1 reads the header's fields; it
does not check the header authentication tag, because checking that tag
requires the data key, which requires a keyring, a provider, and everything
this module exists to avoid needing. Every value this module returns is
therefore an unverified claim by whoever wrote the bytes. See
describe/1.
Records: ADR-0004 decision 12; ADR-0001 open question 1 as answered there; ADR-0005 open question 3.
Summary
Functions
Reads what a message says about itself, without a key and without verifying it.
Functions
@spec describe(binary()) :: {:ok, Encryptor.Message.Info.t()} | {:error, Encryptor.Error.t()}
Reads what a message says about itself, without a key and without verifying it.
The return is an unverified claim. The header authentication tag is not
checked - checking it needs the data key - so every field is what whoever
wrote the bytes says, not what this package has confirmed. Use it for
support tooling, for a migration that needs to know which key version wrote
a row, and for an operator holding a row they cannot explain. Never make
an authorization or routing decision on it. A host that reads
"tenant_ref" out of the context and shows the row to that tenant has built
an access check out of an attacker-editable field.
Offering this at all is an exception to ADR-0001 decision 10's collapse rule, and it is exempt for exactly one reason: it discloses nothing the ciphertext did not already disclose to its holder. It is not a decryption oracle, because it answers no question that depends on a key.
It is describe and not inspect so that a generated vault module defining
it does not shadow Kernel.inspect/1 inside its own body.
iex> {:error, error} = Encryptor.Message.describe("not an ESDK message")
iex> error.reason
:decrypt_failed
iex> error.engine
{:unsupported_version, 110}What "success" means here
A parse succeeds when the header is complete and well-formed. The message body is not read and not required, so a truncated message whose header survived describes itself perfectly well - which is the honest behaviour for a function whose whole contract is "unverified", and is what makes it usable on a corrupted row.
Records: ADR-0004 decision 12, ADR-0001 decision 10 and open question 1.