content-length-range doesn't limit file upload

hello,

i was following browser-based upload guide and i included ["content-length-range", 1000, 1_000_000]in the policy. I uploaded an image using a PUT request over 1mb expecting an error but it just accepted the file and finished the upload.

Did i miss something or this is expected behavior?

thank you.

Hi @xanderjakeq

can you post the full post-policy JSON. Also the post-policy operations are supported with POST method and not PUT. Can you share how you exercised this post-policy upload?

Thanks!
Jigar

Sorry it was my mistake. I was still using the uri generated from aws_sdk_s3

        let presigned = self
            .client
            .put_object()
            .bucket(bucket)
            .key(file_path)
            .content_type("image/")
            .presigned(
                PresigningConfig::builder()
                    .expires_in(Duration::from_secs(60 * 5))
                    .build()
                    .expect("less than one week"),
            )
            .await
            .map_err(|err| {
                return StorageError::PresignPutError(err.to_string());
            })?;

I tried following the guide exactly but I’m now getting SignatureDoesNotMatch error.

Policy json string (excluded content-length-range for now):

"{\"conditions\":[{\"bucket\":\"empty-meadow-1162\"},[\"starts-with\",\"$key\",\"test/\"],{\"success_action_redirect\":\"http//localhost:8000/\"},[\"starts-with\",\"$Content-Type\",\"image/\"],{\"x-amz-meta-uuid\":\"8da4d7e3-0cc9-45b2-9095-8e89951c9dec\"},{\"x-amz-credential\":\"tid_gLhbNjKKAdmgroOtRZikcwRzIuQFLWrXdVnqNqgioxwxZEHDcY/20250702/auto/s3/aws4_request\"},{\"x-amz-algorithm\":\"AWS4-HMAC-SHA256\"},{\"x-amz-date\":\"20250702T212531Z\"}],\"expiration\":\"2025-07-02T21:25:31.579165529Z\"}"

This is my html form:

<form action="https://empty-meadow-1162.fly.storage.tigris.dev/" method="post" enctype="multipart/form-data">
  <input type="hidden" name="success_action_redirect" value="http//localhost:8000/">
  Key to upload:
  <input type="input" name="key" value="test/randommmm.png"><br>
  <input type="hidden" name="Content-Type" value="image/">
  <input type="hidden" name="x-amz-meta-uuid" value="8da4d7e3-0cc9-45b2-9095-8e89951c9dec">
  <input type="hidden" name="X-Amz-Credential" value="tid_gLhbNjKKAdmgroOtRZikcwRzIuQFLWrXdVnqNqgioxwxZEHDcY/20250702/auto/s3/aws4_request">
  <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256">
  <input type="hidden" name="X-Amz-Date" value="20250702T212531Z">
  <input type="hidden" name="Policy" value="eyJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJlbXB0eS1tZWFkb3ctMTE2MiJ9LFsic3RhcnRzLXdpdGgiLCIka2V5IiwidGVzdC8iXSx7InN1Y2Nlc3NfYWN0aW9uX3JlZGlyZWN0IjoiaHR0cC8vbG9jYWxob3N0OjgwMDAvIn0sWyJzdGFydHMtd2l0aCIsIiRDb250ZW50LVR5cGUiLCJpbWFnZS8iXSx7IngtYW16LW1ldGEtdXVpZCI6IjhkYTRkN2UzLTBjYzktNDViMi05MDk1LThlODk5NTFjOWRlYyJ9LHsieC1hbXotY3JlZGVudGlhbCI6InRpZF9nTGhiTmpLS0FkbWdyb090Ulppa2N3UnpJdVFGTFdyWGRWbnFOcWdpb3h3eFpFSERjWS8yMDI1MDcwMi9hdXRvL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotYWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsieC1hbXotZGF0ZSI6IjIwMjUwNzAyVDIxMjUzMVoifV0sImV4cGlyYXRpb24iOiIyMDI1LTA3LTAyVDIxOjI1OjMxLjU3OTE2NTUyOVoifQ==">
  <input type="hidden" name="X-Amz-Signature" value="0QI3yXk+3ekGl/7v0qQZUFpX8edl+Awb1ajafHZnz7Q=">
  File:
  <input type="file" name="file"> <br>
  <input type="submit" name="submit" value="Upload to Tigris">
</form>

And I’m getting the signature like this:

use base64::{engine::general_purpose::STANDARD, Engine as _};

{
    let policy_as_hex = STANDARD.encode(&aws_policy_string);
    let signature = STANDARD.encode(hmac_sha256(
        hmac_sha256(
            hmac_sha256(
                hmac_sha256(
                    hmac_sha256(format!("AWS4{}", secret_access_key), date.to_string()),
                    "auto",
                ),
                "s3",
            ),
            "aws4_request",
        ),
        &policy_as_hex,
    ));
}

where hmac_sha256:

use sha2::Sha256;
use hmac::{Hmac, Mac};

type HmacSha256 = Hmac<Sha256>;

fn hmac_sha256(key: impl AsRef<[u8]>, message: impl AsRef<[u8]>) -> impl AsRef<[u8]> {
    let mut mac = HmacSha256::new_from_slice(key.as_ref()).expect("HMAC can take key of any size.");
    mac.update(message.as_ref());
    let hmac = mac.finalize().into_bytes();

    return hmac;
}

I’m not sure where my mistake is. I tried using the policy json string and secret_access_key from the guide as inputs but I got a different signature string.

    #[test]
    fn signature_matches() {
        let test_policy_as_hex = STANDARD.encode("{\"expiration\":\"2024-03-30T12:00:00.000Z\",\"conditions\":[{\"bucket\":\"my-user-images\"},[\"starts-with\",\"$key\",\"images1/\"],{\"success_action_redirect\":\"https://your-website.com/success.html\"},[\"starts-with\",\"$Content-Type\",\"image/\"],{\"x-amz-meta-uuid\":\"465888667\"},{\"x-amz-credential\":\"tid_example_key_id/20240330/auto/s3/aws4_request\"},{\"x-amz-algorithm\":\"AWS4-HMAC-SHA256\"},{\"x-amz-date\":\"20240330T000000Z\"}]}");
        let test_signature = STANDARD.encode(hmac_sha256(
            hmac_sha256(
                hmac_sha256(
                    hmac_sha256(
                        hmac_sha256(format!("AWS4{}", "tsec_example_H3CYVqDGmFxdXGlruqb16mS22qj59Ag9H3CYVqDGmFxdXGlruqb16mS22qj59"), "20240330"),
                        "auto",
                    ),
                    "s3",
                ),
                "aws4_request",
            ),
            &test_policy_as_hex,
        ));

        assert_eq!(
            &test_signature,
            "5e1fd1320a7d0b001275718a680f7c6d74343b40aecf9af5c16fd6a532144584"
        );
    }

result:

Xh/RMgp9CwASdXGKaA98bXQ0O0Cuz5r1wW/WpTIURYQ=

Thank you.

Hi @xanderjakeq

I suspect the incorrect part is in this line

format!(“AWS4{}”, secret_access_key), date.to_string()

Here is the similar Python code that arrives at the signature correctly. Note that I am starting with base64 encoded policy here assumming that you arrived at it correctly as well.

import base64
import datetime
import hashlib
import hmac
import json

# Input values (replace with your actual values)
access_key = 'tid_example_key_id'
secret_key = 'tsec_example_H3CYVqDGmFxdXGlruqb16mS22qj59Ag9H3CYVqDGmFxdXGlruqb16mS22qj59'
region = 'auto'

policy_base64 = "eyJleHBpcmF0aW9uIjoiMjAyNC0wMy0zMFQxMjowMDowMC4wMDBaIiwiY29uZGl0aW9ucyI6W3siYnVja2V0IjoibXktdXNlci1pbWFnZXMifSxbInN0YXJ0cy13aXRoIiwiJGtleSIsImltYWdlczEvIl0seyJzdWNjZXNzX2FjdGlvbl9yZWRpcmVjdCI6Imh0dHBzOi8veW91ci13ZWJzaXRlLmNvbS9zdWNjZXNzLmh0bWwifSxbInN0YXJ0cy13aXRoIiwiJENvbnRlbnQtVHlwZSIsImltYWdlLyJdLHsieC1hbXotbWV0YS11dWlkIjoiNDY1ODg4NjY3In0seyJ4LWFtei1jcmVkZW50aWFsIjoidGlkX2V4YW1wbGVfa2V5X2lkLzIwMjQwMzMwL2F1dG8vczMvYXdzNF9yZXF1ZXN0In0seyJ4LWFtei1hbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0seyJ4LWFtei1kYXRlIjoiMjAyNDAzMzBUMDAwMDAwWiJ9XX0="

def sign(key, msg):
    return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()

date_stamp = "20240330"
credential_scope = f"{date_stamp}/{region}/s3/aws4_request"

k_date = sign(('AWS4' + secret_key).encode('utf-8'), date_stamp)
k_region = sign(k_date, region)
k_service = sign(k_region, 's3')
k_signing = sign(k_service, 'aws4_request')

signature = hmac.new(k_signing, policy_base64.encode('utf-8'), hashlib.sha256).hexdigest()

print("Policy (base64):", policy_base64)
print("Signature:", signature)
print("x-amz-algorithm: AWS4-HMAC-SHA256")
print("x-amz-credential:", f"{access_key}/{credential_scope}")
print("x-amz-date:", datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ'))

Thank you, I got it to work. (I as encoding the hmac hash in base64 instead of hex).

It uploads and exclude file sizes outside the range. But I’m getting this error.

<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
<Resource>/http//localhost:8000/</Resource>
<RequestId>1751755294738869943</RequestId>
<Key>http//localhost:8000/</Key>
<BucketName>empty-meadow-1162</BucketName>
</Error>

With http//localhost:8000/ is the value for success_action_redirect. It’s a typo and I forgot : after http. I replaced it with http://localhost:8000/ then I get the error below and it prevents the file upload.

<Error>
<Code>PostPolicyInvalidKeyName</Code>
<Message>
Invalid according to Policy: Policy Condition failed
</Message>
<Resource>/</Resource>
<RequestId>1751755895589340717</RequestId>
<BucketName>empty-meadow-1162</BucketName>
</Error>

Hi @xanderjakeq
Can you share your policy JSON that you signed and form values (stripping signature and masking the tid_<>)

Okay i found my silly mistake. I also had the same typo on the html form for some reason. It is working as expected.

Thank you for the help!

Great. Feel free to reach out if you have any other question.