@@ -518,55 +518,56 @@ func DecryptWithCEK(src io.Reader, cek []byte) (io.Reader, error) {
518518// lazily on later reads, which work from the internalized key, never the cek
519519// slice.
520520func openStream (env * cose.Envelope , ciphertext io.Reader , cek []byte ) (io.Reader , error ) {
521- body , err := validateBody (env )
521+ body , aad , err := buildBodyParamsWithAAD (env )
522522 if err != nil {
523523 return nil , err
524524 }
525- r , err := aesstream .NewReader (ciphertext , body .streamConfig (cek ))
525+ r , err := aesstream .NewReader (ciphertext , body .streamConfig (cek , aad ))
526526 if err != nil {
527527 return nil , fmt .Errorf ("fee: initializing body cipher: %w" , err )
528528 }
529529 return r , nil
530530}
531531
532532// bodyParams is the validated STREAM configuration a FEE envelope's body header
533- // carries: everything fee/aesstream needs to decrypt the detached ciphertext
534- // apart from the content-encryption key.
533+ // carries: everything fee/aesstream needs to size and locate the detached
534+ // ciphertext apart from the content-encryption key and Enc_structure AAD .
535535//
536- // [BodyDescriptor] is the same parameters plus the envelope's encoded length —
536+ // [buildBodyParamsWithAAD] rebuilds that AAD alongside the validated params, and
537+ // [BodyDescriptor] caches the same inputs plus the envelope's encoded length —
537538// where the ciphertext starts within a stored blob. That is what a caller caches
538539// and what a range read needs; a whole-object read has neither the number (the
539- // streaming decoder does not report it) nor a use for it, so the two shapes stay
540- // distinct and [BodyDescriptor.body] converts one way.
540+ // streaming decoder does not report it) nor a use for it, so the shapes stay
541+ // distinct and [BodyDescriptor.bodyParams] / [BodyDescriptor.aad] convert one
542+ // way.
541543type bodyParams struct {
542544 baseNonce []byte
543545 chunkSize int
544- aad []byte
545546}
546547
547548// streamConfig returns the fee/aesstream configuration for decrypting a body
548- // with these parameters under cek. It is the only place FEE body parameters
549- // become a stream configuration, so the whole-object reader ([openStream]) and
550- // the range reader ([spanRangeReader ]) cannot drift apart in how they configure
551- // the cipher.
552- func (b bodyParams ) streamConfig (cek []byte ) aesstream.Config {
549+ // with these parameters under cek, authenticating aad as the envelope
550+ // Enc_structure. It is the only place FEE body parameters become a stream
551+ // configuration, so the whole-object reader ([openStream ]) and the range reader
552+ // ([spanRangeReader]) cannot drift apart in how they configure the cipher.
553+ func (b bodyParams ) streamConfig (cek , aad []byte ) aesstream.Config {
553554 return aesstream.Config {
554555 Key : cek ,
555556 BaseNonce : b .baseNonce ,
556- AAD : b . aad ,
557+ AAD : aad ,
557558 ChunkSize : b .chunkSize ,
558559 }
559560}
560561
561- // validateBody checks a decoded envelope's FEE body headers — the algorithm is
562- // the chunked AES-256-GCM-STREAM cipher, the base nonce (iv) is present, and the
563- // self-describing chunk size is in range — and rebuilds the Enc_structure AAD
564- // that the encoder bound into every chunk.
562+ // validateBodyParams checks a decoded envelope's FEE body headers — the
563+ // algorithm is the chunked AES-256-GCM-STREAM cipher, the base nonce (iv) is
564+ // present, and the self-describing chunk size is in range.
565565//
566- // It is shared by the whole-object path ([openStream]) and the range path
567- // ([newRangeReader]), so both accept exactly the same envelopes and report the
568- // same errors for a body header they cannot honour.
569- func validateBody (env * cose.Envelope ) (bodyParams , error ) {
566+ // It is shared by the whole-object path's AAD-building resolution
567+ // ([buildBodyParamsWithAAD]) and the header-only sizing path ([PlaintextSize]),
568+ // so both accept exactly the same envelopes and report the same errors for a
569+ // body header they cannot honour.
570+ func validateBodyParams (env * cose.Envelope ) (bodyParams , error ) {
570571 alg , ok := env .Headers .Protected .Int (cose .HeaderLabelAlg )
571572 if ! ok {
572573 return bodyParams {}, fmt .Errorf ("%w: body algorithm header missing or not an integer" , ErrUnsupportedBodyAlg )
@@ -597,15 +598,26 @@ func validateBody(env *cose.Envelope) (bodyParams, error) {
597598 ErrMalformedEnvelope , chunkSize , aesstream .MinChunkSize , aesstream .MaxChunkSize )
598599 }
599600
601+ return bodyParams {baseNonce : baseNonce , chunkSize : int (chunkSize )}, nil
602+ }
603+
604+ // buildBodyParamsWithAAD validates the body headers and rebuilds the
605+ // Enc_structure AAD that the encoder bound into every chunk.
606+ func buildBodyParamsWithAAD (env * cose.Envelope ) (bodyParams , []byte , error ) {
607+ body , err := validateBodyParams (env )
608+ if err != nil {
609+ return bodyParams {}, nil , err
610+ }
611+
600612 // The decrypt-side AAD is rebuilt from the decoded envelope, using the
601613 // Enc_structure context that matches its tag — byte-identical to the value
602614 // the encoder bound into every chunk.
603615 aad , err := env .EncStructure (nil )
604616 if err != nil {
605- return bodyParams {}, fmt .Errorf ("fee: building envelope AAD: %w" , err )
617+ return bodyParams {}, nil , fmt .Errorf ("fee: building envelope AAD: %w" , err )
606618 }
607619
608- return bodyParams { baseNonce : baseNonce , chunkSize : int ( chunkSize ), aad : aad } , nil
620+ return body , aad , nil
609621}
610622
611623// checkCEK reports whether a caller-provided content-encryption key is the right
0 commit comments