AWS cannot signed CloudFront urls - amazon-web-services

Excepted: I want to get signed urls with my AWS CloudFront url.
What I have done: I have created a AWS CloudFront instence and enabled Restrict Viewer Access function, Trusted Signers is Self.
Below is the php code I want to sign the url
function getSignedURL()
{
$resource = 'http://d2qui8qg6d31zk.cloudfront.net/richardcuicks3sample/140-140.bmp';
$timeout = 300;
//This comes from key pair you generated for cloudfront
$keyPairId = "YOUR_CLOUDFRONT_KEY_PAIR_ID";
$expires = time() + $timeout; //Time out in seconds
$json = '{"Statement":[{"Resource":"'.$resource.'","Condition":{"DateLessThan":{"AWS:EpochTime":'.$expires.'}}}]}';
//Read Cloudfront Private Key Pair
$fp=fopen("private_key.pem","r");
$priv_key=fread($fp,8192);
fclose($fp);
//Create the private key
$key = openssl_get_privatekey($priv_key);
if(!$key)
{
echo "<p>Failed to load private key!</p>";
return;
}
//Sign the policy with the private key
if(!openssl_sign($json, $signed_policy, $key, OPENSSL_ALGO_SHA1))
{
echo '<p>Failed to sign policy: '.openssl_error_string().'</p>';
return;
}
//Create url safe signed policy
$base64_signed_policy = base64_encode($signed_policy);
$signature = str_replace(array('+','=','/'), array('-','_','~'), $base64_signed_policy);
//Construct the URL
$url = $resource.'?Expires='.$expires.'&Signature='.$signature.'&Key-Pair-Id='.$keyPairId;
return $url;
}
For $keyPairId and private_key.pem, I logged in my root account and generated this two variables in Security Credentials->CloudFront Key Pairs section.
If I access http://d2qui8qg6d31zk.cloudfront.net/richardcuicks3sample/140-140.bmp on browser directly. It will response like
<Error>
<Code>MissingKey</Code>
<Message>
Missing Key-Pair-Id query parameter or cookie value
</Message>
</Error>
After I run the function, I got a long signed url, parse the url on chrome browser, it will response like
<Error>
<Code>InvalidKey</Code>
<Message>Unknown Key</Message>
</Error>
Question: I have search AWS document and google much time about this, Could anyone tell me why this happened or if I miss something? Thanks in advance!

$priv_key=fread($fp,8192);
If I understand, you generated the key. If so, it looks like you are setting a key size that is not supported.
The key pair must be an SSH-2 RSA key pair.
The key pair must be in base64 encoded PEM format.
The supported key lengths are 1024, 2048, and 4096 bit
Docs: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-trusted-signers.html#private-content-creating-cloudfront-key-pairs

I opted for Trusted Key Groups and i got that invalidkey/unknownkey error when i initially thought that the keypair id is the same as the access key id under "My Security Credentials". The correct one to use is that ID from your public keys (CloudFront > Key Management > Public Keys).

Thanks #imperalix for answering this question.
I have solved this issue,
Inspired by this site, I found I used the wrong CloudFront url to be signed.
Before: http://d2qui8qg6d31zk.cloudfront.net/richardcuicks3sample/140-140.bmp
After: http://d2qui8qg6d31zk.cloudfront.net/140-140.bmp
Because I create the CloudFront distribution for the richardcuicks3sample bucket, so don't need include this bucket name in the url. After I changed the url, the signed url works well.

Related

how to generate download signed url v4 in gcs and cloudflare

i'm using cloudflare cd to serve my website
all my static files and downloads on gcs and i'm using GCS pytho library for generating signed url v4 i case users want to download some files from my website
the problem is when i use this function generate_download_signed_url_v4 from google
it give me back the signed url with link statring with https://storage.googleapis.com/my_bucket/........
i want to change this link with my own sub domain ex. download.doamin.com
i figured out that i have to use bucket_bound_hostname
but when i use it and try to download with given url i get this message
<Error>
<Code>NoSuchKey</Code>
<Message>The specified key does not exist.</Message>
</Error>
and this is the fuction i use
def generate_download_signed_url_v4(bucket_name, blob_name):
storage_client = storage.Client()
bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_name)
url = blob.generate_signed_url(
version="v4",
# This URL is valid for ...
expiration=datetime.timedelta(minutes=10),
# Allow GET requests using this URL.
method="GET",
bucket_bound_hostname="mysub.domain.com",
scheme='https'
)
return url
PS. i've added Cname in dns setting for c.storage.googleapis.com

Sign google cloud storage blob using access token

Goal: Generate Signed-URL Using OAuth2.0 Access Token
The examples and source codes I find for signing Google Cloud Storage blobs all require service account credentials file (the private key to be specific). For instance:
https://cloud.google.com/storage/docs/access-control/signing-urls-with-helpers#storage-signed-url-get-object-python
However, since I follow the authorization flow discussed here, I only have OAuth2.0 access token (and I do NOT have the credentials file and private key of a service account with access to GCS bucket/object). Hence, I was wondering how I can sign blobs using OAuth2.0 access tokens.
The Code Used:
I use the following to sign blob:
# First, get access token:
service_account = "<email address of a service account>"
access_token = build(
serviceName='iamcredentials',
version='v1',
http=http
).projects().serviceAccounts().generateAccessToken(
name="projects/{}/serviceAccounts/{}".format(
"-",
service_account),
body=body
).execute()["accessToken"]
credentials = AccessTokenCredentials(access_token, "MyAgent/1.0", None)
# Second, use the access token to sign a blob
url = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/{}:signBlob".format(service_account)
encoded = base64.b64encode(blob)
sign_blob_request_body = {"payload": encoded}
response = requests.post(url,
data=json.dumps(sign_blob_request_body),
headers={
'Content-Type': 'application/json',
'Authorization': 'Bearer {}'.format(credentials.access_token)})
signature = response.json()["signedBlob"]
# Third, use the signature to create signed URL:
encoded_signature = base64.b64encode(signature)
signed_url = "https://storage.googleapis.com/<BUCKET>/<OBJECT>?" \
"GoogleAccessId={}&" \
"Expires={}&" \
"Signature={}".format(service_account,
expiration,
encoded_signature)
The Error Message Received:
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
<StringToSign>GET 1561832204 /<BUCKET>/<OBJECT></StringToSign>
</Error>
In case you do NOT want to use API secret key, follow procedure described in this sample that is using iamcredentials.signBlob() API signing URL 'remotely' for a service account with no need to distribute API secret key.
Signature string (that has to be signed) has this format:
signature_string = ('{verb}\n'
'{content_md5}\n'
'{content_type}\n'
'{expiration}\n'
'{resource}')

How can I find my personal endpoint in AWS IoT?

I'm trying to write a Java app that behaves as a Thing, publishing data in AWS. The Documentation has this code sample:
String clientEndpoint = "<prefix>.iot.<region>.amazonaws.com"; // replace <prefix> and <region> with your own
String clientId = "<unique client id>"; // replace with your own client ID. Use unique client IDs for concurrent connections.
String certificateFile = "<certificate file>"; // X.509 based certificate file
String privateKeyFile = "<private key file>"; // PKCS#1 or PKCS#8 PEM encoded private key file
// SampleUtil.java and its dependency PrivateKeyReader.java can be copied from the sample source code.
// Alternatively, you could load key store directly from a file - see the example included in this README.
KeyStorePasswordPair pair = SampleUtil.getKeyStorePasswordPair(certificateFile, privateKeyFile);
AWSIotMqttClient client = new AWSIotMqttClient(clientEndpoint, clientId, pair.keyStore, pair.keyPassword);
// optional parameters can be set before connect()
client.connect();
I know what clientId is and how to find my ID, but I cannot understand the in clientEndpoint.
It's not the account's personal endpoint, but the Thing's endpoint.
Go to IoT Core -> Manage -> Things, select your thing -> Interact.
Its the URL under the HTTPS part. It should be in the form xxxxxxxxxxxxxxxxx.iot.region.amazonaws.com, where the x's should contain mainly lowercase letters, and maybe some numbers.
Call the DescribeEndpoint API.
In Java, this would be:
AWSIot awsIotClient = AWSIotClientBuilder.defaultClient();
DescribeEndpointRequest request = new DescribeEndpointRequest().withEndpointType("iot:Data");
DescribeEndpointResult result = awsIotClient.describeEndpoint(request);
String endpoint = result.getEndpointAddress();

AWS IOT - Credential should be scoped to correct service

I am trying to access a simple AWS IOT REST service but I have not been able to do so successfully yet. Here is what I did.
I created an iam user in my aws and downloaded the access key and secret key
Logged into AWS IOT with that user and created a "thing"
From the thing's property I found the REST URL for the shadow
Used Postman with the new "aws signature" feature and provided it with the access key, secret key, region (us-east-1) and service name (iot)
Tried to "GET" the endpoint and this is what I got -
{
"message": "Credential should be scoped to correct service. ",
"traceId": "be056198-d202-455f-ab85-805defd1260d"
}
I thought there is something wrong with postman so I tried using aws-sdk-sample example of connecting to S3 and changed it to connect to the IOT URL.
Here is my program snippet (Java)
String awsAccessKey = "fasfasfasdfsdafs";
String awsSecretKey = "asdfasdfasfasdfasdfasdf/asdfsdafsd/fsdafasdf";
URL endpointUrl = null;
String regionName = "us-east-1";
try {
endpointUrl = new URL("https://dasfsdfasdf.iot.us-east-1.amazonaws.com/things/SOMETHING/shadow");
}catch (Exception e){
e.printStackTrace();
}
Map<String, String> headers = new HashMap<String, String>();
headers.put("x-amz-content-sha256", AWSSignerBase.EMPTY_BODY_SHA256);
AWSSignerForAuthorizationHeader signer = new AWSSignerForAuthorizationHeader(
endpointUrl, "GET", "iot", regionName);
String authorization = signer.computeSignature(headers,
null, // no query parameters
AWSSignerBase.EMPTY_BODY_SHA256,
awsAccessKey,
awsSecretKey);
// place the computed signature into a formatted 'Authorization' header
// and call S3
headers.put("Authorization", authorization);
String response = HttpUtils.invokeHttpRequest(endpointUrl, "GET", headers, null);
System.out.println("--------- Response content ---------");
System.out.println(response);
System.out.println("------------------------------------");
This gives me the same error -
--------- Request headers ---------
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Authorization: AWS4-HMAC-SHA256 Credential=fasfasfasdfsdafs/20160212/us-east-1/iot/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=3b2194051a8dde8fe617219c78c2a79b77ec92338028e9e917a74e8307f4e914
x-amz-date: 20160212T182525Z
Host: dasfsdfasdf.iot.us-east-1.amazonaws.com
--------- Response content ---------
{"message":"Credential should be scoped to correct service. ","traceId":"cd3e0d96-82fa-4da5-a4e1-b736af6c5e34"}
------------------------------------
Can someone tell me what I am doing wrong please? AWS documentation does not have much information on this error. Please help
Sign your request with iotdata instead if iot
example:
AWSSignerForAuthorizationHeader signer = new AWSSignerForAuthorizationHeader(
endpointUrl, "GET", "iotdata", regionName);
In your 4th step, don't fill anything for Service Name. Postman will default the value with execute-api.
Hope this works!
Its basically due to Service name is not given correctly you can use service Name = 'iotdata' instead of iot.
If you user Key management then Service Name would be kms.
For EC2 Service Name would be ec2 etc.
Use the AWS IoT SDK for Node.js instead. Download the IoT Console generated private key and client cert as well as the CA Root cert from here. Start with the scripts in the examples directory.

Protecting Amazon S3 Files for User-Specific display

My server would communicate with S3. There are two possibilities as far as I understand:
1) Load the file to my server and send it to the user, keeping S3 access only to my server's IP
2) Redirect to S3 while handling authentication on my server
I've understood(I think) how to do #1 from:
Does Amazon S3 support HTTP request with basic authentication
But is there any way to accomplish #2? I want to avoid the latency of first loading the file to my server and then sending it to the user.
I'm not sure how to keep the S3 url protected from public access in #2. Someone might go through my authentication, get a download link, but that link will be publicly accessible.
I'm new to S3 in general, so bear with me if I've misunderstood anything.
Edit: I've looked into signed links with expiration times, but they can still be accessed by others. I would also prefer to use my own authentication so I can allow access to a link only while a user is signed in.
You should try below code, which your server produce an URL which will expire in say 60 seconds, for users to directly download the file from S3 server.
First: Download HMAX.php from here:
http://pear.php.net/package/Crypt_HMAC/redirected
<?php
require_once('Crypt/HMAC.php');
echo getS3Redirect("/test.jpg") . "\n";
function getS3Redirect($objectName)
{
$S3_URL = "http://s3.amazonaws.com";
$keyId = "your key";
$secretKey = "your secret";
$expires = time() + 60;
$bucketName = "/your bucket";
$stringToSign = "GET\n\n\n$expires\n$bucketName$objectName";
$hasher =& new Crypt_HMAC($secretKey, "sha1");
$sig = urlencode(hex2b64($hasher->hash($stringToSign)));
return "$S3_URL$bucketName$objectName?AWSAccessKeyId=$keyId&Expires=$expires&Signature=$sig";
}
function hex2b64($str)
{
$raw = ";
for ($i=0; $i < strlen($str); $i+=2)
{
$raw .= chr(hexdec(substr($str, $i, 2)));
}
return base64_encode($raw);
}
?>
Take a try.