How to get aws sever-side encrypted object - php

I am trying to store object at S3 and retrieve it for that I am doing following
$key = 'myverykey';
$hash = hash_hmac('sha256',$key,true);
$SSECustomerKey = substr($hash,0,32);
$params = [
'Bucket' => 'somebucketname',
'Key' => 'abc.png',
'Body' => "resource",
'ACL' => 'private',
'SSECustomerAlgorithm' => 'AES256',
'SSECustomerKey' => $SSECustomerKey,
'SSECustomerKeyMD5' => md5($SSECustomerKey, true),
];
s3->putObject($params);
And then I am creating signed url like
echo el_s3_getTemporaryLink(id, 'mysecretkey', 'cloudfront-config', 'ab.png');
function el_crypto_hmacSHA1($key, $data, $blocksize = 64) {
if (strlen($key) > $blocksize) $key = pack('H*', sha1($key));
$key = str_pad($key, $blocksize, chr(0x00));
$ipad = str_repeat(chr(0x36), $blocksize);
$opad = str_repeat(chr(0x5c), $blocksize);
$hmac = pack( 'H*', sha1(
($key ^ $opad) . pack( 'H*', sha1(
($key ^ $ipad) . $data
))
));
return base64_encode($hmac);
}
function el_s3_getTemporaryLink($accessKey, $secretKey, $bucket, $path, $expires = 1) {
$expires = time() + intval(floatval($expires) * 60);
$path = str_replace('%2F', '/', rawurlencode($path = ltrim($path, '/')));
$signpath = '/'. $bucket .'/'. $path;
$signsz = implode("\n", $pieces = array('GET', null, null, $expires, $signpath));
$signature = el_crypto_hmacSHA1($secretKey, $signsz);
$url = sprintf('http://%s.s3.amazonaws.com/%s', $bucket, $path);
$qs = http_build_query($pieces = array(
'AWSAccessKeyId' => $accessKey,
'Expires' => $expires,
'Signature' => $signature,
));
return $url.'?'.$qs;
}
it works perfectly fine I am getting url like
http://demobucketname.s3.amazonaws.com/ab.png?AWSAccessKeyId=AKIAJUZQHGRTYNOLEUXQ&Expires=1445855704&Signature=3y6eAq1fe5PVASma5DPFmjV7BB3dY%3D
I can access private object with that if I don't add
'SSECustomerAlgorithm' => 'AES256',
'SSECustomerKey' => $SSECustomerKey,
'SSECustomerKeyMD5' => md5($SSECustomerKey, true),
while putting object to bucket and now I am getting following error
<Error>
<Code>InvalidRequest</Code>
<Message>
The object was stored using a form of Server Side Encryption. The correct parameters must be provided to retrieve the object.
</Message>
<RequestId>E19B53C0EE2A6E7C</RequestId>
<HostId>
WOwzj29NBh49uE5Lmtut96PFY8pf3UD8bBWGLXdAFHryNT94WD7qJbqMbu7fKCfROKEIWKwPPX4=
</HostId>
</Error>
now how to create signed url which works in this case too?

When using the presigned URL to upload a new object, retrieve an existing object, or retrieve only object metadata, you must provide all the encryption headers in your client application. (emphasis added)
http://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
Note "headers," not query string parameters.
SSE-C essentially doesn't support signed URLs for use in web browsers... which makes sense, since exposing pre-signed URL with the encryption key embedded would also expose the encryption key.
If you need user data encrypted server-side, and for users to access the data with pre-signed URLs, SSE-C isn't likely the correct choice.

Related

sending push notification with vapid keys

can someone assist where i am doing mistake
generate vapid keys and store them
use push-api and store endpoint, p256dh and auth
send push notification using webPush protocol
but getting this at 3rd stage
Warning: openssl_sign(): Supplied key param cannot be coerced into a private key
code from 1 to 3 please go through
generate VAPID keys in php on server side and store them
// Generate VAPID keys
$private_key = openssl_pkey_new([
'private_key_type' => OPENSSL_KEYTYPE_EC,
'curve_name' => 'prime256v1',
]);
$details = openssl_pkey_get_details($private_key);
$private_key_raw = $details['ec']['d'];
$public_key_raw = $details['ec']['x'] . $details['ec']['y'];
$auth_token = base64_encode(openssl_random_pseudo_bytes(16));
$vapid = [
'private_key' => rtrim(strtr(base64_encode($private_key_raw), '+/', '-_'), '='),
'public_key' => rtrim(strtr(base64_encode($public_key_raw), '+/', '-_'), '='),
'auth_token' => $auth_token,
];
echo json_encode($vapid);
generate applicationServerKey using public key
$publicKey = 'SzRJTTxfRvvoIfYJye-Oj-xJ-eDxHjIBhPLxILieNbZ86KjRE_EIvdjdKDmUH9RLwgmkITs-v_z_6J44aP1TtA';
// Base64-decode the public key
$public_key_bytes = base64_decode($publicKey);
// Check that the public key has the correct format
if (strlen($public_key_bytes) != 65 || ord($public_key_bytes[0]) != 4) {
// The public key has an incorrect format
// Handle the error here
}
// Extract the x and y coordinates of the point
$x = substr($public_key_bytes, 1, 32);
$y = substr($public_key_bytes, 33, 32);
// Pack the bytes of the public key in the correct order
$application_server_key = "\x04" . $x . $y;
echo base64_encode($application_server_key);
use the base64 applicationServerKey on client side
let b64ASK = 'BDRJTTxfRvvoIfYJyeOjxJeDxHjIBhPLxILieNbZ86KjREEIvdjdKDmUH9RLwgmkITsvz6J44aP1TtAauthtokc=';
let asKey = encodeToUint8Array(b64ASK);
pushManager.subscribe({applicationServerKey:asKey,userVisibleOnly:true})
.then(subscription=>{console.log(subscription.toJSON())})
store endpoint,p256dh,auth and send push notification using vapid keys on server side
send-notification.php
$publicKey = 'SzRJTTxfRvvoIfYJye-Oj-xJ-eDxHjIBhPLxILieNbZ86KjRE_EIvdjdKDmUH9RLwgmkITs-v_z_6J44aP1TtA';
$privateKey = '-----BEGIN EC PRIVATE KEY-----
Lnp9eUjWE7o8oqneZPzOW8nbz7hTVosE25sJm47Arrg=
-----END EC PRIVATE KEY-----';
$endpoint = "https://fcm.googleapis.com/fcm/send/cI0XqQ4quFM:APA91bExZFUuSZ9lgTDJQqmrHJpV-w5pIVnvaiODI9WIeER-K0Vg0U5P8wfTslRF5KdTlCmF9_Tp7bpAohKLxLvQCuS2Cy6ZG2BpVKO4f0wLWrfU-mGD6GCMCVUYLna3uwDLR6NqZxNi";
$auth = "X6syP0cUxjDjMAcUunH3FA";
$p256dh = "BIUuCWdoJykH6u3vERcfZe8gxEx1ajaFTPGnM4cWdYv-Hp-qRgt5GShAtbFYRr5my8hH66uIJEiHf22XW_i_Bps";
// Set the payload for the notification
$payload = [
'title' => 'push-message-test',
'body' => 'This is a test notification using VAPID.',
'icon' => 'assets/icons/favicon-32x32.png',
];
// Encode the payload as a JSON string
$payloadJson = json_encode($payload);
// Generate the JWT header
$header = [
'alg' => 'ES256',
'typ' => 'JWT',
];
$headerJson = json_encode($header);
$headerBase64Url = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($headerJson));
// Generate the JWT claim
$now = time();
$exp = $now + (12 * 60 * 60); // 12 hours in the future
$claim = [
'aud' => $endpoint,
'exp' => $exp,
'sub' => 'https://example.com',
];
$claimJson = json_encode($claim);
$claimBase64Url = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($claimJson));
// Generate the JWT signature
$signingString = $headerBase64Url . '.' . $claimBase64Url;
$signature = '';
$privateKeyResource = openssl_pkey_get_private($privateKey);
openssl_sign($signingString, $signature, $privateKeyResource, 'sha256');
// Encode the signature as base64url
$signatureBase64Url = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));
// Combine the JWT components into a string
$jwt = $headerBase64Url . '.' . $claimBase64Url . '.' . $signatureBase64Url;
// Send the notification using the Web Push protocol
$headers = [
'Authorization: Bearer ' . $jwt,
'Crypto-Key: p256ecdsa=' . $publicKey,
'Content-Length: ' . strlen($payloadJson),
'Content-Type: application/json',
];
$data = [
'endpoint' => $endpoint,
'publicKey' => $p256dh,
'authToken' => $auth,
'payload' => $payloadJson,
];
$options = [
'http' => [
'header' => implode("\r\n", $headers),
'method' => 'POST',
'content' => json_encode($data),
],
];
$context = stream_context_create($options);
$result = file_get_contents($endpoint, false, $context);
// Handle the response
if ($result === false) {
// Error handling
echo 'failed to send push';
} else {
// Success handling
echo 'succesfully sent push using push protocol';
}

How to create a valid JWT in PHP using RSA256

I have been having similar difficulties to this question. I need to create a JWT in php. I have the following code:
define('RSA_PRIVATE', 'MIICXAIB......EWxOf9o=');
$payload = json_encode([
'sub' => 'username',
'email' => 'john#example.com',
'given_name' => 'John',
'family_name' => 'Example',
'iat' => time(),
'nonce' => '12345'
]);
$header = json_encode([
'typ' => 'JWT',
'alg' => 'RS256'
]);
$base64UrlHeader = base64UrlEncode($header);
$base64UrlPayload = base64UrlEncode($payload);
$signature = hash_hmac('sha256', "$base64UrlHeader.$base64UrlPayload", RSA_PRIVATE, true);
$base64UrlSignature = base64UrlEncode($signature);
$jwt = "$base64UrlHeader.$base64UrlPayload.$base64UrlSignature";
echo($jwt);
function base64UrlEncode($text)
{
return str_replace(
['+', '/', '='],
['-', '_', ''],
base64_encode($text)
);
}
Whenever I attempt to validate with the third party that needs my JWT it comes back with a message telling me
Signature validation failed: rsa signature did not match
When I attempt to validate using the JWT debugger, it too says it is invalid.
Am I missing something here? Like the previous question I have only ever seen examples where people are using small secrets and not private RSA keys.
Following the answer by #Michal Trojanowski I went on to use openssl_sign. However this did not work when I had the key as a variable in my code. Instead, following the example on the php manual I managed to get it to work using the following adjustment:
$private_key = openssl_pkey_get_private("file:///path.to/jwtRS256.key");
openssl_sign(
"$base64UrlHeader.$base64UrlPayload",
$signature,
$private_key,
"sha256WithRSAEncryption"
);
openssl_free_key($private_key);
$base64UrlSignature = zd_base64UrlEncode($signature);
$jwt = "$base64UrlHeader.$base64UrlPayload.$base64UrlSignature";
You chose RS256 as the signature algorithm, which is an asymmetrical signing algorithm and you create the signature with a HMAC function, which is a symmetrical signing algorithm.
If you want to stick to RS256 try to follow this answer: https://stackoverflow.com/a/43313973/1712294
If you want to stick with a symmetrical algorithm change the data in your header to
$header = json_encode([
'typ' => 'JWT',
'alg' => 'HS256'
]);
The recommended way is to go with an asymmetrical algorithm.

How do you calculate an AWS signature in PHP?

I am writing a webhook in bref and want it to send a message to SQS. Using the entire AWS SDK for this is a colossal waste. How could I calculate the signature?
const AWS_DATETIME_FORMAT = 'Ymd\THis\Z';
$url = getenv('SQS_URL');
$data = [
'Action' => 'SendMessage',
'MessageBody' => $body,
'Expires' => (new DateTime('UTC'))->add(new DateInterval('PT1M'))->format(AWS_DATETIME_FORMAT),
'Version' => '2012-11-05',
];
$result = Requests::post($url, sign_request($url, $data), $data);
function sign_request($url, $data) {
// These values are provided by AWS Lambda itself.
$secret_key = getenv('AWS_SECRET_ACCESS_KEY');
$access_key = getenv('AWS_ACCESS_KEY_ID');
$token = getenv('AWS_SESSION_TOKEN');
$region = getenv('AWS_REGION');
$service = 'sqs';
$current = new DateTime('UTC');
$current_date_time = $current->format(AWS_DATETIME_FORMAT);
$current_date = $current->format('Ymd');
$signed_headers = [
'content-type' => 'application/x-www-form-urlencoded',
'host' => "sqs.$region.amazonaws.com",
'x-amz-date' => $current_date_time,
'x-amz-security-token' => $token, // leave this one out if you have a IAM created fixed access key - secret pair and do not need the token.
];
$signed_headers_string = implode(';', array_keys($signed_headers));
$canonical = [
'POST',
parse_url($url, PHP_URL_PATH),
'', // this would be the query string but we do not have one.
];
foreach ($signed_headers as $header => $value) {
$canonical[] = "$header:$value";
}
$canonical[] = ''; // this is always an empty line
$canonical[] = $signed_headers_string;
$canonical[] = hash('sha256', http_build_query($data));
$canonical = implode("\n", $canonical);
$credential_scope = [$current_date, $region, $service, 'aws4_request'];
$key = array_reduce($credential_scope, fn ($key, $credential) => hash_hmac('sha256', $credential, $key, TRUE), 'AWS4' . $secret_key);
$credential_scope = implode('/', $credential_scope);
$string_to_sign = implode("\n", [
'AWS4-HMAC-SHA256',
$current_date_time,
$credential_scope,
hash('sha256', $canonical),
]);
$signature = hash_hmac('sha256', $string_to_sign, $key);
unset($signed_headers['host']);
$signed_headers['Authorization'] = "AWS4-HMAC-SHA256 Credential=$access_key/$credential_scope, SignedHeaders=$signed_headers_string, Signature=$signature";
return $signed_headers;
}
Note this code is using rmccue/requests to do a POST request.
My serverless.yml looks like:
provider:
name: aws
region: us-east-1
runtime: provided.al2
environment:
SQS_URL: !Ref BadgeQueue
resources:
Resources:
BadgeQueue:
Type: AWS::SQS::Queue
Properties:
RedrivePolicy:
maxReceiveCount: 3
deadLetterTargetArn: !GetAtt BadgeDeadLetterQueue.Arn
BadgeDeadLetterQueue:
Type: AWS::SQS::Queue
Properties:
MessageRetentionPeriod: 1209600

Docusign PHP getting "invalid_grant: unsupported_grant_type" when trying to get token (JWT auth)

I'm trying to get the access token using the Docusign JWT authentication, but I always get:
{"error":"invalid_grant","error_description":"unsupported_grant_type"}
I double checked all the data (integration key, api username, etc) and they are fine.
I followed all the steps in the Docusign guidelines.
The only part I'm not 100% sure is when I generate the signature of the JWT token.
The documentation says:
The first two parts of the JWT are signed with your application's private key (using the RSA SHA-256 digital signature algorithm) as shown in the diagram.
This is how I'm generating the signature:
$header = [
'typ' => 'JWT',
'alg' => 'RS256'
];
$body = [
'iss' => getenv('INTEGRATION_KEY'),
'sub' => getenv('API_USERNAME'),
'iat' => time(),
'exp' => time() + 3600,
'aud' => str_replace('https://', '', getenv('AUTH_URL')),
'scope' => 'signature impersonation'
];
$signature = JWT::encode($body, file_get_contents(env('PRIVATE_KEY')), 'RS256');
$header = $this->base64url_encode(json_encode($header));
$body = $this->base64url_encode(json_encode($body));
$jwt = $header . '.' . $body . '.' . $signature;
Is that correct?
If not, and since JWT::encode expects an array as first parameter, how should I do to make it work?
This is how I'm requesting the access token:
return Http::withHeaders(
[
'Content-Type' => 'application/x-www-form-urlencoded'
]
)->post(
getenv('AUTH_URL') . '/oauth/token',
[
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt
]
);
Thanks!
Apparently Firebase JWT encode method doesn't encode a string in the right way.
I used this:
$header = $this->base64url_encode(json_encode($header));
$body = $this->base64url_encode(json_encode($body));
openssl_sign(
$header.".".$body,
$signature,
file_get_contents(env('PRIVATE_KEY_PATH')),
"sha256WithRSAEncryption"
);
and it worked.
Make sure you're using the same scopes when requesting consent and requesting the jwt token.
Thanks everyone for the help.
Creating a correct JWT token is hard. I suggest you either use the requestJWTUserToken from the PHP SDK or review its source to see how it makes the OAuth request.
I was having the same problem in a application using Laravel 6 and I managed to solve it as follows:
// the Header will not be needed as it is automatically generated
$header = [
'typ' => 'JWT',
'alg' => 'RS256'
];
$body = [
'iss' => getenv('INTEGRATION_KEY'),
'sub' => getenv('API_USERNAME'),
'iat' => time(),
'exp' => time() + 3600,
'aud' => str_replace('https://', ​​'', getenv('AUTH_URL')),
'scope' =>'signature impersonation'
];
/**
* Note that when creating the JWT, only the $body is provided,
* as the function already performs the necessary concatenations.
* in your code you put it like this:
* $jwt = $header . '.' . $body . '.' . $signature;
* which generates a hash that cannot be validated,
*/
// create the JWT
$jwt = JWT::encode($body , $privateKey, 'RS256');
// make the request
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', getenv('AUTH_URL').'/oauth/token',['query' =>[
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt,
]
]);
echo '<pre>';
print_r($response);
echo '</pre>';

Create AWS S3 presigned URL with MD5 hash using PHP SDK

I've been unable to construct an S3 presigned URL containing a MD5 hash. Here is the code I'm using:
$s3 = new Aws\S3\S3Client( ... );
$md5Hash = "YmEwMWVmNzE5MjI3YzdjNmI0ZjYzNGMxZWI1MmY1Y2U=";
$cmd = $s3->getCommand('PutObject', [
'Bucket' => $bucket,
'Key' => $key,
'Content-MD5' => $md5Hash,
'ContentType' => $contentType,
'Body' => ''
]);
$request = $s3->createPresignedRequest($cmd, '+5 minutes');
$presignedUrl = (string)$request->getUri();
The resulting presignedURL looks something like:
http://s3-external-1.amazonaws.com/{bucket}/{key}?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWC7FFD2T3NCEUNQ%2F20170503%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20170503T215723Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3000&X-Amz-Signature=ca61d80dcfee351e7ee8440a47c5007f1bc905ddc8229077abddab9c39412dc7
When I add the Content-MD5 hash to my PUT request, I get back an error indicating the MD5 header wasn't signed:
AccessDenied - There were headers present in the request which were not signed
HeadersNotSigned - content-md5
Any help appreciated!

Categories