From c7f8bc0ab56dc6b651597aa98e51f027ac60688f Mon Sep 17 00:00:00 2001 From: Ben McClelland Date: Thu, 3 Sep 2026 08:56:04 -0700 Subject: [PATCH] fix: use constant-time comparisons for SigV4 signatures AWS SigV4 signatures are attacker-controlled inputs compared against server-computed HMAC values. Ordinary string comparison exits at the first differing byte, which can expose the length of the matching prefix through response timing and, in principle, enable signature forgery for a fixed request after many probes. Use the shared sigv4auth.SecureCompare helper for browser POST-policy signatures and streaming chunk and trailer signatures. The helper preserves the existing accept/reject behavior, including rejecting malformed or different-length signatures, while using crypto/subtle.ConstantTimeCompare for equal-length values. --- s3api/middlewares/object-post-auth.go | 2 +- s3api/utils/signed-chunk-reader.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/s3api/middlewares/object-post-auth.go b/s3api/middlewares/object-post-auth.go index 34494b00..aabc20ef 100644 --- a/s3api/middlewares/object-post-auth.go +++ b/s3api/middlewares/object-post-auth.go @@ -189,7 +189,7 @@ func AuthorizePostObject(root RootUserConfig, iam auth.IAMService, region string return err } - if expectedSig != signatureHex { + if !sigv4auth.SecureCompare(expectedSig, signatureHex) { debuglogger.Logf("POST object signature mismatch: expected %s got %s", expectedSig, signatureHex) // The String to sign for POST request is the base64 encoded policy // For POST incorrect signature no canonical request and canonical request bytes are returned diff --git a/s3api/utils/signed-chunk-reader.go b/s3api/utils/signed-chunk-reader.go index 26e63097..468fd117 100644 --- a/s3api/utils/signed-chunk-reader.go +++ b/s3api/utils/signed-chunk-reader.go @@ -32,6 +32,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/versity/versitygw/debuglogger" + "github.com/versity/versitygw/internal/sigv4auth" "github.com/versity/versitygw/s3err" ) @@ -235,7 +236,7 @@ func (cr *ChunkReader) verifyTrailerSignature() error { strToSign := cr.getTrailerChunkStringToSign() sig := hex.EncodeToString(hmac256(cr.signingKey, []byte(strToSign))) - if sig != cr.trailerSig { + if !sigv4auth.SecureCompare(sig, cr.trailerSig) { debuglogger.Logf("incorrect trailing signature: (calculated): %v, (got): %v", sig, cr.trailerSig) return s3err.GetSignatureDoesNotMatchErr(cr.accessKey, strToSign, cr.trailerSig, HexBytes(strToSign), cr.canonicalString, HexBytes(cr.canonicalString)) } @@ -262,7 +263,7 @@ func (cr *ChunkReader) checkSignature() error { cr.chunkHash.Reset() cr.prevSig = hex.EncodeToString(hmac256(cr.signingKey, []byte(sigstr))) - if cr.prevSig != cr.parsedSig { + if !sigv4auth.SecureCompare(cr.prevSig, cr.parsedSig) { debuglogger.Logf("incorrect signature: (calculated): %v, (got) %v", cr.prevSig, cr.parsedSig) return s3err.GetSignatureDoesNotMatchErr(cr.accessKey, sigstr, cr.parsedSig, HexBytes(sigstr), cr.canonicalString, HexBytes(cr.canonicalString)) }