Uploading to Amazon S3 using cURL/libcurl - c++

I am currently trying to develop an application to upload files to an Amazon S3 bucket using cURL and c++. After carefully reading the S3 developers guide I have started implementing my application using cURL and forming the Header as described by the Developers guide and after lots of trials and errors to determine the best way to create the S3 signature, I am now facing a 501 error. The received header suggests that the method I'm using is not implemented. I am not sure where I'm wrong but here is the HTTP header that I'm sending to amazon:
PUT /test1.txt HTTP/1.1
Accept: */*
Transfer-Encoding: chunked
Content-Type: text/plain
Content-Length: 29
Host: [BucketName].s3.amazonaws.com
Date: [Date]
Authorization: AWS [Access Key ID]:[Signature]
Expect: 100-continue
I have truncated the Bucket Name, Access Key ID and Signature for security reasons.
I am not sure what I'm doing wrong but I think that the error is generating because of the Accept and Transfer-Encoding Fields (Not Really Sure). So can anyone tell me what I'm doing wrong or why I'm getting a 501.

The game changed significantly since the question was asked, the simple authorization headers no longer apply, yet it is still feasible to perform with a UNIX shell script, as follows.
Ensure 'openssl' and 'curl' are available at the command line. TIP: double check the openSSL argument syntax as these may vary with different versions of the tool; e.g. openssl sha -sha256 ... versus openssl sha256 ...
Beware, a single extra newline or space character, else the use of CRLF in place of the NewLine char alone would defeat the signature. Note too that you may want to use content types possibly with encodings to prevent any data transformation through the communication media. You may then have to adjust the list of signed headers at several places; please refer to AMAZON S3 API docs for the numerous conventions to keep enforced like alphabetical-lowercase ordering of header info used in hash calculations at several (redundant) places.
# BERHAUZ Nov 2019 - curl script for file upload to Amazon S3 Buckets
test -n "$1" || {
echo "usage: $0 <myFileToSend.txt>"
echo "... missing argument file ..."
exit
}
yyyymmdd=`date +%Y%m%d`
isoDate=`date --utc +%Y%m%dT%H%M%SZ`
# EDIT the next 4 variables to match your account
s3Bucket="myBucket.name.here"
bucketLocation="eu-central-1"
s3AccessKey="THISISMYACCESSKEY123"
s3SecretKey="ThisIsMySecretKeyABCD1234efgh5678"
#endpoint="${s3Bucket}.s3-${bucketLocation}.amazonaws.com"
endpoint="s3-${bucketLocation}.amazonaws.com"
fileName="$1"
contentLength=`cat ${fileName} | wc -c`
contentHash=`openssl sha256 -hex ${fileName} | sed 's/.* //'`
canonicalRequest="PUT\n/${s3Bucket}/${fileName}\n\ncontent-length:${contentLength}\nhost:${endpoint}\nx-amz-content-sha256:${contentHash}\nx-amz-date:${isoDate}\n\ncontent-length;host;x-amz-content-sha256;x-amz-date\n${contentHash}"
canonicalRequestHash=`echo -en ${canonicalRequest} | openssl sha256 -hex | sed 's/.* //'`
stringToSign="AWS4-HMAC-SHA256\n${isoDate}\n${yyyymmdd}/${bucketLocation}/s3/aws4_request\n${canonicalRequestHash}"
echo "----------------- canonicalRequest --------------------"
echo -e ${canonicalRequest}
echo "----------------- stringToSign --------------------"
echo -e ${stringToSign}
echo "-------------------------------------------------------"
# calculate the signing key
DateKey=`echo -n "${yyyymmdd}" | openssl sha256 -hex -hmac "AWS4${s3SecretKey}" | sed 's/.* //'`
DateRegionKey=`echo -n "${bucketLocation}" | openssl sha256 -hex -mac HMAC -macopt hexkey:${DateKey} | sed 's/.* //'`
DateRegionServiceKey=`echo -n "s3" | openssl sha256 -hex -mac HMAC -macopt hexkey:${DateRegionKey} | sed 's/.* //'`
SigningKey=`echo -n "aws4_request" | openssl sha256 -hex -mac HMAC -macopt hexkey:${DateRegionServiceKey} | sed 's/.* //'`
# then, once more a HMAC for the signature
signature=`echo -en ${stringToSign} | openssl sha256 -hex -mac HMAC -macopt hexkey:${SigningKey} | sed 's/.* //'`
authoriz="Authorization: AWS4-HMAC-SHA256 Credential=${s3AccessKey}/${yyyymmdd}/${bucketLocation}/s3/aws4_request, SignedHeaders=content-length;host;x-amz-content-sha256;x-amz-date, Signature=${signature}"
curl -v -X PUT -T "${fileName}" \
-H "Host: ${endpoint}" \
-H "Content-Length: ${contentLength}" \
-H "x-amz-date: ${isoDate}" \
-H "x-amz-content-sha256: ${contentHash}" \
-H "${authoriz}" \
http://${endpoint}/${s3Bucket}/${fileName}
I must acknowledge that, for someone a bit involved in cryptography like me, the Amazon signature scheme deserves numerous critics:
there's much redundancy in the information being signed,
the 5 step HMAC cascade is almost inverting semantics between key seed and data where 1 step would suffice with proper usage and same security
the last 12 characters of the secret key are useless here, because the significant key length of a SHA256 HMAC is ... 256 bits, hence 32 bytes, of which the first 4 always start with "AWS4" for just no purpose.
overall AWS S3 API re-invents standards where a S/MIME payload would have done
Apologize for the critics, I was not able to resist. Yet acknowledge: it is working reliably, useful for many companies, and an interesting service with a rich API.

You could execute a bash file. Here is an example upload.sh script which you could just run as: sh upload.sh yourfile
#!/bin/bash
file=$1
bucket=YOUR_BUCKET
resource="/${bucket}/${file}"
contentType="application/x-itunes-ipa"
dateValue=`date -R`
stringToSign="PUT\n\n${contentType}\n${dateValue}\n${resource}"
s3Key=YOUR_KEY_HERE
s3Secret=YOUR_SECRET
echo "SENDING TO S3"
signature=`echo -en ${stringToSign} | openssl sha1 -hmac ${s3Secret} -binary | base64`
curl -vv -X PUT -T "${file}" \
-H "Host: ${bucket}.s3.amazonaws.com" \
-H "Date: ${dateValue}" \
-H "Content-Type: ${contentType}" \
-H "Authorization: AWS ${s3Key}:${signature}" \
https://${bucket}.s3.amazonaws.com/${file}
more on: http://www.jamesransom.net/?p=58
http://www.jamesransom.net/?p=58

Solved: was missing an CURLOPT for the file size in my code and now everything is working perfectly

Related

How to download a secured file using curl from s3 bucket using SecretAccessKey and AccessKeyId

I want to download an apk which exists into my private s3 bucket using curl command. I dont want to use awscli/boto3. I have SecretAccessKey, SessionToken, Expiration, AccessKeyId
Tried Following Code:
curl -k -v -L -o url="https://s3-eu-west-1.amazonaws.com" -H "x-amz-security-token: xxxxxxxxxxxxxxxx" -H "Content-Type: application/xml" -X GET https://xyz/test.apk
curl -k -v -L -o url="https://s3-eu-west-1.amazonaws.com" -H "Content-Type: application/xml" -X GET https://xyz/.test.apk?AWSAccessKeyId=xxxxxxxxxxxxx
Here is the script that downloads and upload file to s3, you have to export keys or can modify the script accordingly.
export AWS_ACCESS_KEY_ID=AKxxx
export AWS_SECRET_ACCESS_KEY=zzzz
Download a file
./s3download.sh get s3://mybucket/myfile.txt myfile.txt
That's it, all you need to pass s3 bucket along with file name
#!/bin/bash
set -eu
s3simple() {
local command="$1"
local url="$2"
local file="${3:--}"
# todo: nice error message if unsupported command?
if [ "${url:0:5}" != "s3://" ]; then
echo "Need an s3 url"
return 1
fi
local path="${url:4}"
if [ -z "${AWS_ACCESS_KEY_ID-}" ]; then
echo "Need AWS_ACCESS_KEY_ID to be set"
return 1
fi
if [ -z "${AWS_SECRET_ACCESS_KEY-}" ]; then
echo "Need AWS_SECRET_ACCESS_KEY to be set"
return 1
fi
local method md5 args
case "$command" in
get)
method="GET"
md5=""
args="-o $file"
;;
put)
method="PUT"
if [ ! -f "$file" ]; then
echo "file not found"
exit 1
fi
md5="$(openssl md5 -binary $file | openssl base64)"
args="-T $file -H Content-MD5:$md5"
;;
*)
echo "Unsupported command"
return 1
esac
local date="$(date -u '+%a, %e %b %Y %H:%M:%S +0000')"
local string_to_sign
printf -v string_to_sign "%s\n%s\n\n%s\n%s" "$method" "$md5" "$date" "$path"
local signature=$(echo -n "$string_to_sign" | openssl sha1 -binary -hmac "${AWS_SECRET_ACCESS_KEY}" | openssl base64)
local authorization="AWS ${AWS_ACCESS_KEY_ID}:${signature}"
curl $args -s -f -H Date:"${date}" -H Authorization:"${authorization}" https://s3.amazonaws.com"${path}"
}
s3simple "$#"
You can find more detail here

Why do I get a signature error with this AWS bash deploy script?

I am trying to create a bash script to upload files to my s3 bucket. I am having difficulty generating the correct signature.
I get the following error message:
The request signature we calculated does not match the signature you provided. Check your key and signing method.
Here is my script:
Thanks for your help!
#!/usr/bin/env bash
#upload to S3 bucket
sourceFilePath="$1"
#file path at S3
folderPathAtS3="packages";
#S3 bucket region
region="eu-central-1"
#S3 bucket name
bucket="my-bucket-name";
#S3 HTTP Resource URL for your file
resource="/${bucket}/${folderPathAtS3}";
#set content type
contentType="gzip";
#get date as RFC 7231 format
dateValue="$(date +'%a, %d %b %Y %H:%M:%S %z')"
acl="x-amz-acl:private"
#String to generate signature
stringToSign="PUT\n\n${contentType}\n${dateValue}\n${acl}\n${resource}";
#S3 key
s3Key="my-key";
#S3 secret
s3Secret="my-secret-code";
#Generate signature, Amazon re-calculates the signature and compares if it matches the one that was contained in your request. That way the secret access key never needs to be transmitted over the network.
signature=$(echo -en "${stringToSign}" | openssl sha1 -hmac ${s3Secret} -binary | base64);
#Curl to make PUT request.
curl -L -X PUT -T "${sourceFilePath}" \
-H "Host: ${bucket}.${region}.amazonaws.com" \
-H "Date: ${dateValue}" \
-H "Content-Type: ${contentType}" \
-H "$acl" \
-H "Authorization: AWS ${s3Key}:${signature}" \
https://s3.amazonaws.com/${bucket}/${folderPathAtS3}
Your signature seems fine, but your request is wrong and consequently does not match.
-H "Host: ${bucket}.${region}.amazonaws.com" \ is incorrect.
The correct value is ${bucket}.s3 ${region}.amazonaws.com. You're overlooking the s3. in the hostname... but even if correct, this is still invalidj because your URL https://s3.amazonaws.com/${bucket}/... also includes the bucket, which means your bucket name is being implicitly added to the beginning of the object key because it appears twice.
Additionally, https://s3.amazonaws.com is us-east-1. To connect to the correct region, your URL needs to be one of these variants:
https://${region}.s3.amazonaws.com/${bucket}/${folderPathAtS3}
https://${bucket}.${region}.s3.amazonaws.com/${folderPathAtS3}
https://${bucket}.s3.amazonaws.com/${folderPathAtS3}
Use one of these formats, and eliminate -H "Host: ..." because it will then be redundant.
The last of the 3 URL formats will only start to work after the bucket is more than a few minutes or hours old. S3 creates these automatically but it takes some time.

AWS S3 rest api signature

Can someone please help me with calculating the AWS_SIGNATURE in bash
Here is the GET I am trying to do:
curl -k \
-X GET \
-H "Host: ${AWS_BUCKET_NAME}.s3.eu-west-1.amazonaws.com" \
-H "Date: Tue, 27 Nov 2018 11:20:00 +0200" \
-H "Authorization: AWS ${AWS_ACCESS_KEY_ID}:${AWS_SIGNATURE}" \
"https://s3.eu-west-1.amazonaws.com/${AWS_BUCKET_NAME}/?list-type=2"
You need to install openssl and base64 encoder to create the signature.
Assume you supply value for each $variable
Signature=`echo -n $StringToSign | openssl sha1 -hmac $YourSecretAccessKeyID | base64`

How can I call Amazon's AWS kms decrypt function without using a binary file?

I have code that retrieves a string that was encrypted using Amazon's aws kms encrypt function. I would like to call aws kms decrypt to get back the unencrypted value, but I would like to do this without writing the string to a binary file. All the examples I've found assume you will convert the base64 encoded encrypted value into a binary file using either linux's base64 command or Window's certutil command. I'm trying to do this on a Windows system. It seems to me you should be able to run:
aws kms encrypt --key-id <mykey> --plaintext "mysecret"
Which for me generates this result:
{
"KeyId": "arn:aws:kms:us-east-1:192491131326:key/<mykey>",
"CiphertextBlob": "AQICAHjQ7sViXQdeS4wWbFZpkOQWvCdNXqiy4Cnz0/xEBe39SQGz0vofeAo0+SyOXv172fqkAAAAZjBkBgkqhkiG9w0BBwagVzBVAgEAMFAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMhchHh0ugGzwRTC4gAgEQgCMlkhYlCYk2SfYIkfQ6ruwA71KBcN7ih/OPzSE86OT/eBOz3Q=="
}
And that I should then be able to run:
aws kms decrypt --ciphertext-blob AQICAHjQ7sViXQdeS4wWbFZpkOQWvCdNXqiy4Cnz0/xEBe39SQGz0vofeAo0+SyOXv172fqkAAAAZjBkBgkqhkiG9w0BBwagVzBVAgEAMFAGCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMhchHh0ugGzwRTC4gAgEQgCMlkhYlCYk2SfYIkfQ6ruwA71KBcN7ih/OPzSE86OT/eBOz3Q==
To get back the result. But so far I've been unable to get anything except:
An error occurred (InvalidCiphertextException) when calling the Decrypt operation:
Is there some set of parameters I can pass into the decrypt command so that it will decrypt this string?
Not sure if you've already found this, but this seems to work:
aws kms decrypt --ciphertext-blob fileb://<(echo "{YOUR CIPHERTEXTBLOB HERE}" | base64 -d) --output text --query Plaintext --region {REGION} | base64 -d
This is for a Mac. On Windows I think you need base64 -d.
Hope this helps.
I do a stuff with aws kms so I wanted a way to work with streams rather than files. I have define 2 functions in my shell that could also be put into scripts. (I left the semicolons in because I have these condensed to oneliners in my .bash_profile but I like to break things up when I'm "teaching".)
export KMS_KEY=b31ef212-168e-4f7c-ab2a-fe8a623ee465
kmse(){
local key=${1:-$KMS_KEY};
aws kms encrypt \
--key-id $key \
--plaintext "$(cat /dev/stdin)" \
--query CiphertextBlob \
--output text;
}
kmsd(){
aws kms decrypt \
--ciphertext-blob fileb://<( \
cat /dev/stdin | \
sed 's/.*kmscrypt:://' | \
tr -d '\n' | \
base64 -D
) \
--output text \
--query Plaintext | \
base64 -D;
}
The kmse function takes an options argument which is the UUID of the key to use. Otherwise it uses the key specified in the KMS_KEY environment var. Both functions read /dev/stdin to get what needs to be encrypted/decrypted. Therefore, the following examples all work:
## Encrypting
$ tar -czf - /etc | kmse > etc.tgz.encrypted
$ kmse 77ed1d23-6013-47ce-b48a-2a968ef0ddaa < ~/.ssh/id_rsa > id_rsa.pem.encrypted
$ cat | kmse | netcat 10.0.0.123 8080
<content_pasted_from_my_clipboard>
^D
## Decrypting
$ kmsd < etc.tgz.encrypted | tar -zxf -
$ kmsd < id_rsa.pem.encrypted > ~/.ssh/id_rsa.pem
$ cat | kmsd | tee output.txt
<content_pasted_from_my_clipboard>
^D
# Note: ^D is a CTRL-D character that tells `cat` this is the End Of File.
While the cat paste CTRL-D is a cool trick. I have a script called cb that I use for getting data in and out of my clipboard via stdin and stdout. And it now works on Mac, Linux, Windows Cygwin, and Windows WSL! So, I can encrypt and decrypt what is in my clipboard with:
## Encrypt
$ cb | kmse | cb
## Decrypt
$ cb | kmsd | cb
A similar concept that is Mac OSX specific is:
## Encrypt
$ pbpaste | kmse | pbcopy
## Decrypt
$ pbpaste | kmsd | pbcopy

Evaluate output of OpenSSL before parsing output

Using the following code to check for expiry of SSL certs:
cat localdomains | xargs -L 1 bash -c 'openssl s_client -connect $0:443 -servername $0 2> /dev/null | openssl x509 -noout -enddate | cut -d = -f 2 | xargs -I {} echo {} $0'
When I run into domains with no cert returning, I am trying to wrap my head around how to change the output to something like N/A, instead of trying to evaluate "cut -d = -f 2" and then xargs -I
If you capture the output of your command in a variable you can then validate it. Assuming this doesn't have to be a one liner:
#!/bin/bash
while read domain; do
expiry=$(openssl s_client -connect ${domain}:443 -servername ${domain} 2>/dev/null </dev/null | \
openssl x509 -noout -enddate 2>&1 | cut -d = -f 2)
# validate output with date
if date -d "${expiry}" > /dev/null 2>/dev/null ; then
echo ${expiry} ${domain}
else
echo "N/A" ${domain}
fi
done
Note a couple of things:
Redirect /dev/null into stdin of openssl s_client to get the prompt back (see here for technical details)
Redirect stderr of the second openssl x509 to stdout in order to validate against it.
You could use grep or sed to validate the output. I found date to be convenient (and if you wanted to use it to reformat the date, it would be extra-convenient).
I tested this solution by putting it into a file check_cert_expiry.sh:
$ cat localdomains
stackoverflow.com
example.example
google.com
$ cat localdomains | ./check_cert_expiry.sh
Aug 14 12:00:00 2019 GMT stackoverflow.com
N/A example.example
Feb 21 09:37:00 2018 GMT google.com
Cheers!