etag: fix incorrect multipart detection (#14631)

This commit fixes a subtle bug in the ETag
`IsEncrypted` implementation.

An encrypted ETag may contain random bytes,
i.e. some randomness used for encryption.
This random value can contain a '-' byte
simple due to being randomly generated.

Before, the `IsEncrypted` implementation
incorrectly assumed that an encrypted ETag
cannot contain a '-' since it would be a
multipart ETag. Multipart ETags have a
16 byte value followed by a '-' and the part number.
For example:
```
059ba80b807c3c776fb3bcf3f33e11ae-2
```

However, the following encrypted ETag
```
20000f00db2d90a7b40782d4cff2b41a7799fc1e7ead25972db65150118dfbe2ba76a3c002da28f85c840cd2001a28a9
```
also contains a '-' byte but is not a multipart ETag.

This commit fixes the `IsEncrypted` implementation
simply by checking whether the ETag is at least 32
bytes long. A valid multipart ETag is never 32 bytes
long since a part number must be <= 10000.

However, an encrypted ETag must be at least 32 bytes
long. It contains the encrypted ETag bytes (16 bytes)
and the authentication tag added by the AEAD cipher (again
16 bytes).

Signed-off-by: Andreas Auernhammer <hi@aead.dev>
This commit is contained in:
Andreas Auernhammer
2022-03-26 02:21:01 +01:00
committed by GitHub
parent 5cfedcfe33
commit 062f3ea43a
2 changed files with 45 additions and 2 deletions
+17 -2
View File
@@ -143,7 +143,22 @@ func (e ETag) String() string {
// IsEncrypted reports whether the ETag is encrypted.
func (e ETag) IsEncrypted() bool {
return len(e) > 16 && !bytes.ContainsRune(e, '-')
// An encrypted ETag must be at least 32 bytes long.
// It contains the encrypted ETag value + an authentication
// code generated by the AEAD cipher.
//
// Here is an incorrect implementation of IsEncrypted:
//
// return len(e) > 16 && !bytes.ContainsRune(e, '-')
//
// An encrypted ETag may contain some random bytes - e.g.
// and nonce value. This nonce value may contain a '-'
// just by its nature of being randomly generated.
// The above implementation would incorrectly consider
// such an ETag (with a nonce value containing a '-')
// as non-encrypted.
return len(e) >= 32 // We consider all ETags longer than 32 bytes as encrypted
}
// IsMultipart reports whether the ETag belongs to an
@@ -151,7 +166,7 @@ func (e ETag) IsEncrypted() bool {
// API.
// An S3 multipart ETag has a -<part-number> suffix.
func (e ETag) IsMultipart() bool {
return len(e) > 16 && bytes.ContainsRune(e, '-')
return len(e) > 16 && !e.IsEncrypted() && bytes.ContainsRune(e, '-')
}
// Parts returns the number of object parts that are