Hi @jmj, I generate the presigned url on my server with the following parameters:
AccessKey: tid_<>
SecretKey: tsec_<>
Endpoint: see below
Region: auto
Bucket: test.sessions.pinggg.cloud
Method: PUT
Expiration: 15
When I use fly.storage.tigris.dev
as endpoint then everything works fine, but when I use t3.storage.dev
then I get the message:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
<Resource>/dFGR75OhGeGGRdNwyjIV3rOFMuxeBaNu8gus4mqSvGy/shares/1.png</Resource>
<RequestId>1750939451656676602</RequestId>
<Key>dFGR75OhGeGGRdNwyjIV3rOFMuxeBaNu8gus4mqSvGy/shares/1.png</Key>
<BucketName>test.sessions.pinggg.cloud</BucketName>
</Error>
Below you find the parts that generate the presigned url in Golang. It’s the method PresignObject(...)
that is called to generate the signature:
//---------------------------------------------------------------------------
const (
algorithm = "AWS4-HMAC-SHA256"
service = "s3"
request = "aws4_request"
)
//---------------------------------------------------------------------------
type Presigner struct {
AccessKey string
SecretKey string
Region string
Endpoint string
Bucket string
Method string
Expiration time.Duration
}
//---------------------------------------------------------------------------
// Creates a presigned GET/PUT date and signature
func (p *Presigner) PresignObject(objectName string) (string, []byte) {
dateOnly, dateTime := presigner_Stamp()
signature := p.PresignObjectWithDate(objectName, dateOnly, dateTime)
return dateTime, signature
}
//---------------------------------------------------------------------------
// Creates a presigned GET/PUT signature
func (p *Presigner) PresignObjectWithDate(objectName string, dateOnly string, dateTime string) []byte {
mime := presigner_ExtractMimeType(objectName)
// Build canonical URI
var canonicalURI strings.Builder
canonicalURI.WriteByte('/')
canonicalURI.WriteString(p.Bucket)
canonicalURI.WriteByte('/')
canonicalURI.WriteString(objectName)
// Build credential string
var credential strings.Builder
// These '/'s should be escaped
credential.WriteString(p.AccessKey)
credential.WriteString("%2F")
credential.WriteString(dateOnly)
credential.WriteString("%2F")
credential.WriteString(p.Region)
credential.WriteString("%2F")
credential.WriteString(service)
credential.WriteString("%2F")
credential.WriteString(request)
// Build query parameters
var canonicalQueryString strings.Builder
// Add parameters in the correct order
canonicalQueryString.WriteString(fmt.Sprintf("X-Amz-Algorithm=%s", algorithm))
canonicalQueryString.WriteString(fmt.Sprintf("&X-Amz-Credential=%s", credential.String()))
canonicalQueryString.WriteString(fmt.Sprintf("&X-Amz-Date=%s", dateTime))
canonicalQueryString.WriteString(fmt.Sprintf("&X-Amz-Expires=%d", int(p.Expiration.Seconds())))
canonicalQueryString.WriteString("&X-Amz-SignedHeaders=host")
// Build host and canonical headers
var canonicalHeaders strings.Builder
canonicalHeaders.WriteString(fmt.Sprintf("host:%s", p.Endpoint))
canonicalHeaders.WriteByte('\n')
// Build canonical request
var canonicalRequest strings.Builder
canonicalRequest.WriteString(p.Method)
canonicalRequest.WriteByte('\n')
canonicalRequest.WriteString(canonicalURI.String())
canonicalRequest.WriteByte('\n')
canonicalRequest.WriteString(canonicalQueryString.String())
canonicalRequest.WriteByte('\n')
canonicalRequest.WriteString(canonicalHeaders.String())
canonicalRequest.WriteByte('\n')
canonicalRequest.WriteString("host")
canonicalRequest.WriteByte('\n')
canonicalRequest.WriteString("UNSIGNED-PAYLOAD")
// Build string to sign
var scope strings.Builder
scope.WriteString(dateOnly)
scope.WriteByte('/')
scope.WriteString(p.Region)
scope.WriteByte('/')
scope.WriteString(service)
scope.WriteByte('/')
scope.WriteString(request)
var stringToSign strings.Builder
stringToSign.WriteString(algorithm)
stringToSign.WriteByte('\n')
stringToSign.WriteString(dateTime)
stringToSign.WriteByte('\n')
stringToSign.WriteString(scope.String())
stringToSign.WriteByte('\n')
stringToSign.WriteString(hex.EncodeToString(presigner_Hash([]byte(canonicalRequest.String()))))
// Calculate signature
signingKey := presigner_SignKey(p.SecretKey, dateOnly, p.Region, service)
signature := presigner_Hmac(signingKey, []byte(stringToSign.String()))
return signature
}
//---------------------------------------------------------------------------
func presigner_ExtractMimeType(objectName string) string {
ext := filepath.Ext(objectName)
return mime.TypeByExtension(ext)
}
//---------------------------------------------------------------------------
// Creates a date only string and a date with time string
func presigner_Stamp() (string, string) {
datetime := time.Now().UTC()
return datetime.Format("20060102"), datetime.Format("20060102T150405Z")
}
//---------------------------------------------------------------------------
func presigner_Hash(data []byte) []byte {
h := sha256.New()
h.Write(data)
return h.Sum(nil)
}
//---------------------------------------------------------------------------
func presigner_Hmac(key []byte, data []byte) []byte {
h := hmac.New(sha256.New, key)
h.Write(data)
return h.Sum(nil)
}
//---------------------------------------------------------------------------
func presigner_SignKey(secretKey string, date string, region string, service string) []byte {
kDate := presigner_Hmac([]byte("AWS4"+secretKey), []byte(date))
kRegion := presigner_Hmac(kDate, []byte(region))
kService := presigner_Hmac(kRegion, []byte(service))
return presigner_Hmac(kService, []byte(request))
}
//---------------------------------------------------------------------------
The signature and date are send to the client and assembled into:
https://test.sessions.pinggg.cloud/dFGR75OhGeGGRdNwyjIV3rOFMuxeBaNu8gus4mqSvGy/shares/1.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=tid_mNpmYFuMLHfstgsbJEnjDXQLjRSgqXlznLCJdlY_ZIhereWHco%2F20250626%2Fauto%2Fs3%2Faws4_request&X-Amz-Date=20250626T120406Z&X-Amz-Expires=15&X-Amz-SignedHeaders=host&X-Amz-Signature=625a62854449727f57935b10caf1e9895c8ba0c7d424296c771b5e066f8b13bb
The client then uploads the file and gets the error listed above.
Could this be the result of me using a CNAME bucket mapped onto test.sessions.pinggg.cloud.fly.storage.tigris.dev
?
Thanks in advance.