Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS S3 presigned urls with boto3 - Signature mismatch

I want to create a presigned url for the objects in my bucket. I use the following python code:

    client = boto3.client(
    's3',
    aws_access_key_id=os.environ['AWS_ACCESS_KEY'],
    aws_secret_access_key=os.environ['AWS_SECRETS_KEY'],
    config=botocore.client.Config(signature_version='s3v4'),
    region_name='eu-central-1'
)
url = client.generate_presigned_url(
    ClientMethod='get_object',
    ExpiresIn=60,
    Params={
        'Bucket': MYBUCKET,
        'Key': MYKEY
    })

I then send the generated URL to my frontend. On the client I will create an a tag with the generated link and use the click() method on it. This worked fine in other projects but here I only get the error:

The request signature we calculated does not match the signature you provided. Check your key and signing method.

Which is strange. The user should have all the necessary rights. Because listing all the files in my bucket works fine.

Can someone point me in the right direction why this isn't working?

EDIT

I'm using next.js on the frontend if this is of help.

like image 446
kidman01 Avatar asked May 07 '18 12:05

kidman01


2 Answers

Had the exact same problem. Studied the AWS docs and wrote the (signature v4) procedure myself. The below is based on

https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html

and works perfectly.

ENCODING = 'utf8'
SEVEN_DAYS = 604800
logger = logging.getLogger()
logger.setLevel(logging.INFO)


def sign(key, msg):
    return hmac.new(key, msg.encode(ENCODING), hashlib.sha256).digest()


def get_signature_key(key, dateStamp, regionName, serviceName):
    kDate = sign(('AWS4' + key).encode(ENCODING), dateStamp)
    kRegion = sign(kDate, regionName)
    kService = sign(kRegion, serviceName)
    kSigning = sign(kService, 'aws4_request')
    return kSigning


def generate_presigned_s3_get(bucket, object_key, region, expires_in, access_key, secret_key):
    METHOD = 'GET'
    SERVICE = 's3'
    host = bucket + '.s3.' + region + '.amazonaws.com'
    endpoint = 'https://' + host
    t = datetime.datetime.utcnow()
    amz_date = t.strftime('%Y%m%dT%H%M%SZ')
    datestamp = t.strftime('%Y%m%d')
    canonical_uri = '/' + object_key
    canonical_headers = 'host:' + host + '\n'
    signed_headers = 'host'
    algorithm = 'AWS4-HMAC-SHA256'
    credential_scope = datestamp + '/' + region + '/' + SERVICE + '/' + 'aws4_request'
    canonical_querystring = '?X-Amz-Algorithm=AWS4-HMAC-SHA256'
    canonical_querystring += '&X-Amz-Credential=' + urllib.parse.quote_plus(access_key + '/' + credential_scope)
    canonical_querystring += '&X-Amz-Date=' + amz_date
    canonical_querystring += '&X-Amz-Expires=' + str(expires_in)
    canonical_querystring += '&X-Amz-SignedHeaders=' + signed_headers
    canonical_request = METHOD + '\n' + canonical_uri + '\n' + canonical_querystring[1:] + '\n' + canonical_headers + '\n' + signed_headers + '\nUNSIGNED-PAYLOAD'
    string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + hashlib.sha256(canonical_request.encode(ENCODING)).hexdigest()
    signing_key = get_signature_key(secret_key, datestamp, region, SERVICE)
    signature = hmac.new(signing_key, (string_to_sign).encode("utf-8"), hashlib.sha256).hexdigest()
    canonical_querystring += '&X-Amz-Signature=' + signature
    url = endpoint + canonical_uri + canonical_querystring
    logger.info('presigned url: %s' % url)
    return url

I've also reported this issue to boto3 peeps: https://github.com/boto/boto3/issues/1644

like image 156
Trog Avatar answered Nov 19 '22 04:11

Trog


The problem was the version of boto3.

I tried the latest version (boto3 1.7.14) which yielded above mentioned error.

Works exactly like I want it to with version 1.6.6.

like image 1
kidman01 Avatar answered Nov 19 '22 04:11

kidman01