Skip to content

Commit 9763f86

Browse files
committed
fee: document the synchronous CEK-absorption guarantee (FIL-569)
Encrypt/Decrypt wipe their CEK with defer zero(cek) as soon as encryptStream / openStream return — before the returned reader is read. Document that guarantee where it's provided, not just where it's relied upon: encryptStream wraps the CEK to recipients and calls aesstream.NewWriter (which internalizes the key into a GCM AEAD) synchronously before returning; openStream calls aesstream.NewReader likewise. So neither retains the cek slice past its own return — the background encryption goroutine and the lazy decrypt reads both work from the internalized key, never the slice — and a caller may wipe cek immediately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fyx4RZx2j88mTQyeavbs79
1 parent 1214a14 commit 9763f86

1 file changed

Lines changed: 15 additions & 3 deletions

File tree

fee/fee.go

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,14 @@ func EncryptWithCEK(plaintext io.Reader, cek []byte, recipients []Recipient, opt
231231
// encryptStream is the shared core of Encrypt and EncryptWithCEK: it seals
232232
// plaintext under cek and returns a streaming reader over envelope||ciphertext.
233233
// With no recipients it emits a COSE_Encrypt0 (tag 16); otherwise a COSE_Encrypt
234-
// (tag 96). It does not retain, modify, or wipe cek.
234+
// (tag 96). It does not modify or wipe cek.
235+
//
236+
// It also does not retain cek past its own return: the recipient wraps and the
237+
// aesstream.NewWriter that internalizes the CEK (into a GCM AEAD) both run
238+
// synchronously before encryptStream returns, so a caller may wipe cek as soon
239+
// as it returns — even though the returned reader has not been read and its
240+
// background encryption goroutine is still running. That goroutine works from
241+
// the writer's internalized key, never from the cek slice.
235242
func encryptStream(plaintext io.Reader, cek []byte, recipients []Recipient, opts ...EncryptOption) (io.ReadCloser, error) {
236243
if plaintext == nil {
237244
return nil, errors.New("fee: nil plaintext reader")
@@ -437,8 +444,13 @@ func DecryptWithCEK(src io.Reader, cek []byte) (io.Reader, error) {
437444

438445
// openStream is the shared core of Decrypt and DecryptWithCEK: given a decoded
439446
// envelope, its detached ciphertext stream, and the content-encryption key, it
440-
// validates the body parameters and returns the streaming plaintext reader. It
441-
// copies cek into the body cipher and does not retain it.
447+
// validates the body parameters and returns the streaming plaintext reader.
448+
//
449+
// It does not retain cek past its own return: aesstream.NewReader internalizes
450+
// the CEK (into a GCM AEAD) synchronously before openStream returns, so a caller
451+
// may wipe cek as soon as it returns — even though the returned reader decrypts
452+
// lazily on later reads, which work from the internalized key, never the cek
453+
// slice.
442454
func openStream(env *cose.Envelope, ciphertext io.Reader, cek []byte) (io.Reader, error) {
443455
alg, ok := env.Headers.Protected.Int(cose.HeaderLabelAlg)
444456
if !ok {

0 commit comments

Comments
 (0)