sending push notification with vapid keys - php

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';
}

Related

how to return a sql query using jwt in php?

I'm new using jwts and I want to return a query,
how could i do it?
With this code I generate the token with the header, payload etc..
Help would be greatly appreciated
// php code //
$secret = "examamplesecrett";
function CreateToken($secret){
$header = json_encode(
[
"type"=> "JWT","alg" => "HS256"
]
);
// creas el payload
$payload = json_encode(
[
'idUsuarioexample' => '100',
'iat' => time(),
'exp' => time() + 1800,
]
);
$headerbase64 = str_replace('*','',strtr(base64_encode($header),'*/','-_'));
$payloadbase64 = str_replace('*','',strtr(base64_encode($payload),'*/','-_'));
$signature = hash_hmac('sha256',$headerbase64.'.'.$payloadbase64,$secret,true);
$base64signature = str_replace('*','',strtr(base64_encode($signature),'+/','-_'));
$jwt = $headerbase64.".".$payloadbase64.".".$base64signature;
return $jwt;
}
$token = CreateTokenToken($secret);
echo $token ;

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>';

How to get aws sever-side encrypted object

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.

PUT file with AWS presignedURL

I'm creating presigned URL with aws-sdk-php for uploading files to S3 bucket. URLs for GET are working.
Here is the code
$client = S3Client::factory(array('region' => 'eu-west-1','key' => 'xxx','secret' => 'xxx',));
//option 1
$command = $client->getCommand('PutObject', array(
'Bucket' => 'myBucket',
'Key' => 'testing/signedPHP1_'.time(),
'ContentType' => 'image/jpeg',
'Body' => 'dump' //it's mandatory
));
$signedUrl = $command->createPresignedUrl('+5 minutes');
$signedUrl .= '&Content-Type=image%2Fjpeg';
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run from console for upload:\ncurl -v -H \"Content-Type: image/jpeg\" -T /tmp/temp.jpg '" . $signedUrl . "'");
//option 2
$request = $client->put('myBucket/testing/signedPHP2_'.time());
$signedUrl = $client->createPresignedUrl($request, '+5 minutes');
$signedUrl .= '&Content-Type=image%2Fjpeg';
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run from console for upload:\ncurl -v -H \"Content-Type: image/jpeg\" -T /tmp/temp.jpg '" . $signedUrl . "'");
//GET which works
$request = $client->get('myBucket/testing/existingFile.txt');
$signedUrl = $client->createPresignedUrl($request, '+5 minutes');
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run:\ncurl '" . $signedUrl . "'");
//GET which works
$command = $client->getCommand('GetObject', array('Bucket' => 'myBucket','Key' => 'upload/data.txt'));
$signedUrl = $command->createPresignedUrl('+5 minutes');
echo("\n\nThe URL is: ". $signedUrl . "\n");
echo("Now run:\ncurl '" . $signedUrl . "'");
When trying to use curl command I'm getting and error
SignatureDoesNotMatch with message The request signature we calculated does not match the signature you provided. Check your key and signing method.
The similar code in aws-sdk for Javascript is working
var AWS = require('aws-sdk');
AWS.config.update({ accessKeyId: 'xxx', secretAccessKey: 'xxx', region: 'eu-west-1' });
var s3 = new AWS.S3();
var params = {
Bucket: 'myBucket',
Key: 'testing/preSignedURLnodeJS_' + (+new Date),
ContentType: 'image/jpeg',
Expires: 60 * 5
};
s3.getSignedUrl('putObject', params, function(err, url) {
console.log('The URL is: ', url);
console.log('Now run from console for upload:\n\ncurl -v -H "Content-Type: image/jpeg" -T /tmp/temp.jpg \'' + url + '\'');
});
Already done a lot of researching but no results. What am I doing wrong?
On Github I got an answer from SDK developer and it says
$command = $client->getCommand('PutObject', array(
'Bucket' => 'myBucket',
'Key' => 'testing/signedPHP1_'.time(),
'ContentType' => 'image/jpeg',
'Body' => '',
'ContentMD5' => false
));
Reference: https://github.com/aws/aws-sdk-php/issues/239
Here you go:
First, create the presigned URLs
<?php
/**
* getDocumentUploadUrls
*
* creates a list of url so you can upload multiple files per
* document. After uploading the files it is requires for you
* to confirm the uploads.
*
* #param Int $documentId the document id
* #param Int $count the amount of files you want to upload for this document
*
* #return Array list of URLs to use with PUT request to upload files to s3.
*/
public function getDocumentUploadUrls(Int $documentId, Int $count = 1): Array
{
$awsService = $this->getService('aws');
$s3Client = $awsService->getSdk()->createS3();
$urls = array_fill(1, $count, null);
$bucket = 'yourbucket';
$result = [];
$expire = '+20 minutes';
for ($i = 0; $i < $count; $i++) {
$fileCount = $i + 1;
$key = "{$documentId}/{$fileCount}";
$cmd = $s3Client->getCommand('PutObject', [
'Bucket' => $bucket,
'Key' => $key
]);
$request = $s3Client->createPresignedRequest($cmd, $expire);
$result[] = [
'url' => (string) $request->getUri(),
'reference' => "{$bucket}/{$key}"
];
}
return $result;
}
The result may look similar to this:
$result = [
0 => [
'url' => 'AwsS3://put/uri',
'reference' => 'docId/count'
]
];
Now, to upload with php cURL:
if ($request->isPost()) {
$files = $request->getFiles()->toArray();
$files = reset($files);
$data = $request->getPost();
$docId = $data['documentId']; // get this from your frontend POST params
$docCount = count($files);
try {
$endpoints = $this->getDocumentUploadUrls($userId, $docId, $docCount);
$uploadInfo = $this->uploadDocuments($endpoints, $files);
} catch (\Exception $e) {
// handle exception
}
}
public function uploadDocuments(Array $endpoints, Array $files)
{
$info = [];
foreach ($files as $index => $file) {
$url = $endpoints[$index]['url']; // the no. of files must match the number of endpoints for this to work
$type = isset($file['type']) ? $file['type'] : 'application/octet-stream';
$headers = [
"Content-Type: $type",
'Access-Control-Allow-Origin: *',
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, $file);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new \Exception(curl_error($ch));
}
curl_close($ch);
$info[] = [
'headers' => $headers,
'response' => $response
];
}
return $info;
}
If you are using the sdk why not use the putObject command, rather than creating a put request yourself. This will take care of the signedUrl for you.
$result = $client->putObject(array(
'Bucket' => $bucket,
'Key' => 'data_from_file.txt',
'SourceFile' => $pathToFile
));
http://docs.aws.amazon.com/aws-sdk-php/latest/class-Aws.S3.S3Client.html#_putObject
If you don't want that, you need to look at the body of your put command, this should be the contents of the file you are uploading, not just a random string 'dump'.
Run wireshark on the machine running the command and you will see the body of the request curl is making is the contents of the file.

Categories