when i take a look at the paypal documentation, they say "Note that the PayPal SDK for PHP does not require SSL encryption".
https://developer.paypal.com/docs/classic/api/apiCredentials/#encrypting-your-certificate
Is the statement of this phrase, that i don't have to create a p12 certificate when working with php, but use the public_key.pem and paypal_public_key.pem?
If yes:
Is it secure enough to create the encrypted form input elements without p12 certificate?
If no:
What do they mean? :-)
Before this question came up, i've tested this little programm.
http://www.softarea51.com/blog/how-to-integrate-your-custom-shopping-cart-with-paypal-website-payments-standard-using-php/
There is a config file paypal-wps-config.inc.php where i can define the paths to my certificates.
// tryed to use // 'paypal_cert.p12 ';
$config['private_key_path'] = '/home/folder/.cert/pp/prvkey.pem';
// must match the one you set when you created the private key
$config['private_key_password'] = ''; //'my_password';
When i try to use the p12 certificate, openssl_error_string() returns "Could not sign data: error:0906D06C:PEM routines:PEM_read_bio:no start line openssl_pkcs7_sign
When i instead use the prvkey.pem without password all works fine.
Here is the function, which signs and encrypt the data.
function signAndEncrypt($dataStr_, $ewpCertPath_, $ewpPrivateKeyPath_, $ewpPrivateKeyPwd_, $paypalCertPath_)
{
$dataStrFile = realpath(tempnam('/tmp', 'pp_'));
$fd = fopen($dataStrFile, 'w');
if(!$fd) {
$error = "Could not open temporary file $dataStrFile.";
return array("status" => false, "error_msg" => $error, "error_no" => 0);
}
fwrite($fd, $dataStr_);
fclose($fd);
$signedDataFile = realpath(tempnam('/tmp', 'pp_'));
**// here the error came from**
if(!#openssl_pkcs7_sign( $dataStrFile,
$signedDataFile,
"file://$ewpCertPath_",
array("file://$ewpPrivateKeyPath_", $ewpPrivateKeyPwd_),
array(),
PKCS7_BINARY)) {
unlink($dataStrFile);
unlink($signedDataFile);
$error = "Could not sign data: ".openssl_error_string();
return array("status" => false, "error_msg" => $error, "error_no" => 0);
}
unlink($dataStrFile);
$signedData = file_get_contents($signedDataFile);
$signedDataArray = explode("\n\n", $signedData);
$signedData = $signedDataArray[1];
$signedData = base64_decode($signedData);
unlink($signedDataFile);
$decodedSignedDataFile = realpath(tempnam('/tmp', 'pp_'));
$fd = fopen($decodedSignedDataFile, 'w');
if(!$fd) {
$error = "Could not open temporary file $decodedSignedDataFile.";
return array("status" => false, "error_msg" => $error, "error_no" => 0);
}
fwrite($fd, $signedData);
fclose($fd);
$encryptedDataFile = realpath(tempnam('/tmp', 'pp_'));
if(!#openssl_pkcs7_encrypt( $decodedSignedDataFile,
$encryptedDataFile,
file_get_contents($paypalCertPath_),
array(),
PKCS7_BINARY)) {
unlink($decodedSignedDataFile);
unlink($encryptedDataFile);
$error = "Could not encrypt data: ".openssl_error_string();
return array("status" => false, "error_msg" => $error, "error_no" => 0);
}
unlink($decodedSignedDataFile);
$encryptedData = file_get_contents($encryptedDataFile);
if(!$encryptedData) {
$error = "Encryption and signature of data failed.";
return array("status" => false, "error_msg" => $error, "error_no" => 0);
}
unlink($encryptedDataFile);
$encryptedDataArray = explode("\n\n", $encryptedData);
$encryptedData = trim(str_replace("\n", '', $encryptedDataArray[1]));
return array("status" => true, "encryptedData" => $encryptedData);
} // signAndEncrypt
} // PPCrypto
The main questions:
Is it possible to use p12 cert with php, or is it secure enough to work without it?
Why i become an error when using openssl_pkcs7_sign
Please help.
Greetings
ninchen
You should not confuse 'using SSL' with 'using SSL with a predefined client certificate'. The document you link to describes the latter. Simply calling an https URL will enable SSL and deliver browser-equivalent security. This is done by the SDK automatically.
Predefined client certificates guard against a sophisticated attacker performing a man-in-the-middle attack. Both methods will stop an unsophisticated attacker from reading your network traffic directly.
Client certificates also serve to authenticate you to PayPal, as an alternate for user/password/signature.
Related
I am having some issues in creating a Discord bot. I want it to be able to respond to slash commands, but to do so I need to verify an endpoint.
I am using PHP 7.4, and can't use any external libraries (hosting on a server that does not allow them).
I have found documents for PHP, but they do require libraries to work.
I tried taking the documents from Node.JS and "converting" them to PHP.
Here's my code:
<?php
$public_key = "shh-it's-a-seekrit";
$headers = getallheaders();
$signature = $headers["X-Signature-Ed25519"];
$timestamp = $headers["X-Signature-Timestamp"];
$raw_body = file_get_contents('php://input');
/* To compute the signature, we need the following
* 1. Message ($timestamp + $body)
* 2. $signature
* 3. $public_key
* The algorythm is SHA-512
*/
$message = $timestamp . $raw_body;
$hash_signature = hash_hmac('sha512', $message, $public_key);
if (!hash_equals($signature, $hash_signature)) {
header("HTTP/1.1 401 Unauthorized", true, 401);
die("Request is not properly authorized!");
}
$return_array = [
'type' => 1,
];
echo json_encode($return_array);
?>
When I put the address the file is uploaded to and try to save the changes, Discord says the following:
Validation errors:
interactions_endpoint_url: The specified interactions endpoint URL could not be verified.
This is a method that works for me on PHP 8.1.
Pass in the headers and raw JSON body, and it returns an array with the response code and payload to send back through whatever you are using to handle the response. Note the response must be JSON encoded.
$discord_public is the public key from the Discord app.
public function authorize(array $headers, string $body, string $discord_public): array
{
$res = [
'code' => 200,
'payload' => []
];
if (!isset($headers['x-signature-ed25519']) || !isset($headers['x-signature-timestamp'])) {
$res['code'] = 401;
return $res;
}
$signature = $headers['x-signature-ed25519'];
$timestamp = $headers['x-signature-timestamp'];
if (!trim($signature, '0..9A..Fa..f') == '') {
$res['code'] = 401;
return $res;
}
$message = $timestamp . $body;
$binary_signature = sodium_hex2bin($signature);
$binary_key = sodium_hex2bin($discord_public);
if (!sodium_crypto_sign_verify_detached($binary_signature, $message, $binary_key)) {
$res['code'] = 401;
return $res;
}
$payload = json_decode($body, true);
switch ($payload['type']) {
case 1:
$res['payload']['type'] = 1;
break;
case 2:
$res['payload']['type'] = 2;
break;
default:
$res['code'] = 400;
return $res;
}
return $res;
}
For completeness, adding the missing code to #Coder1 answer:
<?php
$payload = file_get_contents('php://input');
$result = endpointVerify($_SERVER, $payload, 'discord app public key');
http_response_code($result['code']);
echo json_encode($result['payload']);
function endpointVerify(array $headers, string $payload, string $publicKey): array
{
if (
!isset($headers['HTTP_X_SIGNATURE_ED25519'])
|| !isset($headers['HTTP_X_SIGNATURE_TIMESTAMP'])
)
return ['code' => 401, 'payload' => null];
$signature = $headers['HTTP_X_SIGNATURE_ED25519'];
$timestamp = $headers['HTTP_X_SIGNATURE_TIMESTAMP'];
if (!trim($signature, '0..9A..Fa..f') == '')
return ['code' => 401, 'payload' => null];
$message = $timestamp . $payload;
$binarySignature = sodium_hex2bin($signature);
$binaryKey = sodium_hex2bin($publicKey);
if (!sodium_crypto_sign_verify_detached($binarySignature, $message, $binaryKey))
return ['code' => 401, 'payload' => null];
$payload = json_decode($payload, true);
switch ($payload['type']) {
case 1:
return ['code' => 200, 'payload' => ['type' => 1]];
case 2:
return ['code' => 200, 'payload' => ['type' => 2]];
default:
return ['code' => 400, 'payload' => null];
}
}
According to Discord documentation, interactions endpoint must do two things:
Your endpoint must be prepared to ACK a PING message
Your endpoint must be set up to properly handle signature headers--more on that in Security and Authorization
The first part is very simple:
So, to properly ACK the payload, return a 200 response with a payload of type: 1:
Thus, you need to return JSON object and not an array (maybe that's one of the problems with your code).
Additionally, endpoint must be able to respond to invalid requests as:
We will also do automated, routine security checks against your endpoint, including purposefully sending you invalid signatures. If you fail the validation, we will remove your interactions URL in the future and alert you via email and System DM.
One thing I noticed is that Discord sends their headers lowercased! Another problem is that you use:
$raw_body = file_get_contents('php://input'); that doesn't look right.
Finally, if you know node.JS take a look at my working example: https://github.com/iaforek/discord-interactions
I need to send Push notifications through PHP script by using .p8 file and found following code in similar question asked here.
<?php
$keyfile = 'AuthKey_AABBCC1234.p8'; # <- Your AuthKey file
$keyid = 'AABBCC1234'; # <- Your Key ID
$teamid = 'AB12CD34EF'; # <- Your Team ID (see Developer Portal)
$bundleid = 'com.company.YourApp'; # <- Your Bundle ID
$url = 'https://api.development.push.apple.com'; # <- development url, or use http://api.push.apple.com for production environment
$token = 'e2c48ed32ef9b018........'; # <- Device Token
$message = '{"aps":{"alert":"Hi there!","sound":"default"}}';
$key = openssl_pkey_get_private('file://'.$keyfile);
$header = ['alg'=>'ES256','kid'=>$keyid];
$claims = ['iss'=>$teamid,'iat'=>time()];
$header_encoded = base64($header);
$claims_encoded = base64($claims);
$signature = '';
openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
$jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);
// only needed for PHP prior to 5.5.24
if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', 3);
}
$http2ch = curl_init();
curl_setopt_array($http2ch, array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_URL => "$url/3/device/$token",
CURLOPT_PORT => 443,
CURLOPT_HTTPHEADER => array(
"apns-topic: {$bundleid}",
"authorization: bearer $jwt"
),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $message,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADER => 1
));
$result = curl_exec($http2ch);
if ($result === FALSE) {
throw new Exception("Curl failed: ".curl_error($http2ch));
}
$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
echo $status;
function base64($data) {
return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
}
?>
However, I found that openssl_pkey_get_private doesn't read the key file and it gives following error when I debug it.
$key = openssl_pkey_get_private('file://'.$keyfile);
if ($key === false) {
var_dump(openssl_error_string());
}
error :
'error:02001002:system library:fopen:No such file or directory'
Please note that there is no issue with curl as HTTP2 was enabled for the curl and I am using PHP7. In testing phase I'm using the script and file on the same folder to avoid any path issues.
Any clue where it went wrong ?
Please refer to next URL if did not read yet.
https://www.php.net/manual/en/function.openssl-pkey-get-private.php
To narrow down your issue, please use same directory for your php file and key file and try this working code.
Working code
$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absolute path
$key = openssl_pkey_get_private($keyfile);
if ($key === false) {
var_dump(openssl_error_string());
}else{
var_dump($key);
}
The following might be an issue.
Path
Following path styles should work.
$keyfile="file:///home/john/php/key.p8"; // unix absoulute path
$keyfile="file://C:\\users\\john\\php\\key.p8"; // windows absoulute path
$keyfile="file://".__DIR__.DIRECTORY_SEPARATOR."key.p8"; //absoulute path for unix, windows
$keyfile="file://key.p8"; // relative path, unix, windows, (php,key files in same directory)
$key = openssl_pkey_get_private($keyfile);
If path does not exist, error will be like
"error:02001002:system library:fopen:No such file or directory"
Web environment
Check your web root and web user access permission to the folder and key file.
To reduce issues, test it on php build-in web server env rather than WAMP env.
>php -S localhost:80
Corrupted key file
saved as certain type which include whitespaces.
This can occur error like next.
"error:0906D06C:PEM routines:PEM_read_bio:no start line"
in my case, key file was saved as UTF-8 with BOM(whitespaces)
DEBUG key file 1 - READ FROM VARIABLE
This code should work. I got key file from
http://micmap.org/php-by-example/en/function/openssl_pkey_get_private
Please replace $str to yours.
$str = <<<EOF
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA0llCeBjy18RylTdBih9GMUSZIC3GzeN0vQ9W8E3nwy2jdeUn
H3GBXWpMo3F43V68zM2Qz5epRNmlLSkY/PJUfJIC8Yc1VEokT52q87hH/XJ5eS8h
eZnjuSlPAGi8oZ3ImVbruzV7XmlD+QsCSxJW7tBv0dqJ71e1gAAisCXK2m7iyf/u
l6rT0Zz0ptYH4IZfwc/hQ9JcMg69uM+3bb4oBFsixMmEQwxKZsXk3YmO/YRjRbay
+6+79bSV/frW+lWhknyGSIJp2CJArYcOdbK1bXx1dRWpbNSExo7dWwuPC0Y7a5AE
eoZofieQPPBhXlp1hPgLYGat71pDqBjKLvF5GwIDAQABAoIBACPItYsSy3UzYT7L
OKYTrfBBuD8GKpTqBfkHvAWDa1MD15P92Mr7l0NaCxGfAy29qSa6LdFy/oPM9tGY
9TxKyV6rxD5sfwEI3+Z/bw6pIe4W5F1eTDaQnHHqehsatkRUQET9yXp+na8w/zRF
0C0PQKS95tfvcpm59RGCdGQ8+aZw+cIy/xez75W8IS/hagMxe7xYPjpkOkSCCEJU
zmbVq6AyWodASV0p4H9p8I+c0vO2hJ/ELJ167w6T+2/GlZg979rlyHoTW8jK2BbG
IRGaPo+c2GANXa686tdpbkPd6oJliXwBSNolxmXShvlveBbPFAJJACzCmbXNj9kH
6/K+SWkCgYEA7FNudcTkRPV8TzKhJ1AzDjw3VcnraYhY8IlNxbk7RVHLdkoUtwk/
mImeBlEfCoz9V+S/gRgeQ+1Vb/BCbS24+bN/+IGoNRFMRcOieFt6lQUpj7a9NeSo
IEclGgUiU7QR3xH73SB4GC3rgSPeHJhJZC5EJq5TzYjXTPGPpBD3zicCgYEA49wz
zfMDYIH8h4L65r/eJYIbLwpvgktgaYvhijO3qfZSWW+Y19jCBn55f65YOhPGQBHA
my0f+tVxFNZ/OupbrAIIzogxlCIYHNBawDhoHN/sB3/lSBAjifySNLyRlA62oA0w
wXvXVLVWMa3aXim3c9AlnLF1fHwcvwpOKSfdye0CgYBb1mBKq+T5V1yjek1d9bCh
i40FbZ5qOG43q2Ppvn3mBk9G/KroJlPsdy5NziB9/SRGj8JL7I92Xjihc4Cc5PPJ
NZQ5gklXtg0p30i39PTCDGuGScFlvCIJyRwF7JDWblezlE2INSH2Y4HtgX7DJfr/
T2t0jLJMYS0p3YWwgFeMaQKBgHUIe/8y6zAdc5QynSX5tGL1gXrW1FFK39k2RICU
cag1YTSYkhuDNJzbRxJifORPlcsAkzngooVWLb+zMCQVjUI6xUU3RKe+Hz5lccc6
8ZarGHL9qMkrqOVNudamZ+tw5zIrtDgcoIvcm8nmbrtgl94/MaJar2ph4O3qoByZ
Ylw9AoGAIdS79s0VKkj4VVXqK47ZcI7jGL4V4C8ujU8YcMNV88xwCoDg9ZIFprWA
P5p/cnvj6aHnqL58XiH0+bE0Lt3J+U6N6JelQQevgBHooMFh4FpDXcVda7xB3rK3
woqbi8fNhr827H2maxIZPtVG95/mvR4k5z1Jrdnr34ZUmtC6U5Q=
-----END RSA PRIVATE KEY-----
EOF;
$key = openssl_pkey_get_private($str);
if ($key === false) {
var_dump(openssl_error_string());
}else{
var_dump($key);
}
OUTPUT
resource(4) of type (OpenSSL key)
DEBUG key file 2 - READ FROM FILE
copy your key strings($str) to key file like "key.p8".
$str = <<<EOF
-----BEGIN RSA PRIVATE KEY-----
...YOUR KEY STINGS HERE...
-----END RSA PRIVATE KEY-----
EOF;
$str2 = file_get_contents("key.p8");
$len1 = strlen ($str);
$len2 = strlen ($str2);
if($len1 !== $len2) echo "File has been corrupted.";
$key = openssl_pkey_get_private($str2);
if ($key === false) {
var_dump(openssl_error_string());
}else{
var_dump($key);
}
This script can be used to send a push to IOS using .p8 certificate.
Make sure the location of the certificate is correct
<?php
$keyfile = 'AuthKey_AABBCC1234.p8'; // Your p8 Key file
$keyid = 'AABBCC1234'; // Your Key ID
$teamid = 'AB12CD34EF'; // Your Team ID (see Developer Portal)
$bundleid = 'com.company.YourApp'; // Your Bundle ID
$url = 'https://api.development.push.apple.com'; // development url, or use http://api.push.apple.com for production environment
$token = 'e2c48ed32ef9b018........'; // Device Token
$message = '{"aps":{"alert":"Hi there!","sound":"default"}}';
$key = openssl_pkey_get_private('file://'.$keyfile);
$header = ['alg'=>'ES256','kid'=>$keyid];
$claims = ['iss'=>$teamid,'iat'=>time()];
$header_encoded = base64($header);
$claims_encoded = base64($claims);
$signature = '';
openssl_sign($header_encoded . '.' . $claims_encoded, $signature, $key, 'sha256');
$jwt = $header_encoded . '.' . $claims_encoded . '.' . base64_encode($signature);
// only needed for PHP prior to 5.5.24
if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', 3);
}
$http2ch = curl_init();
curl_setopt_array($http2ch, array(
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
CURLOPT_URL => "$url/3/device/$token",
CURLOPT_PORT => 443,
CURLOPT_HTTPHEADER => array(
"apns-topic: {$bundleid}",
"authorization: bearer $jwt"
),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => $message,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_TIMEOUT => 30,
CURLOPT_HEADER => 1
));
$result = curl_exec($http2ch);
if ($result === FALSE) {
throw new Exception("Curl failed: ".curl_error($http2ch));
}
$status = curl_getinfo($http2ch, CURLINFO_HTTP_CODE);
echo $status;
function base64($data) {
return rtrim(strtr(base64_encode(json_encode($data)), '+/', '-_'), '=');
}
?>```
I want to transfer to my Amazon S3 bucket an archive of around 10GB, using a PHP script (it's a backup script).
I actually use the following code :
$uploader = new \Aws\S3\MultipartCopy($s3Client, $tmpFilesBackupDirectory, [
'Bucket' => 'MyBucketName',
'Key' => 'backup'.date('Y-m-d').'.tar.gz',
'StorageClass' => $storageClass,
'Tagging' => 'expiration='.$tagging,
'ServerSideEncryption' => 'AES256',
]);
try
{
$result = $uploader->copy();
echo "Upload complete: {$result['ObjectURL']}\n";
}
catch (Aws\Exception\MultipartUploadException $e)
{
echo $e->getMessage() . "\n";
}
My issue is that after few minutes (let's say 10mn), I receive an error message from the apache server : 504 Gateway timeout.
I understand that this error is related to the configuration of my Apache server, but I don't want to increase the timeout of my server.
My idea is to use the PHP SDK Low-Level API to do the following steps:
Use Aws\S3\S3Client::uploadPart() method in order to manually upload 5 parts, and store the response obtained in $_SESSION (I need the ETag values to complete the upload);
Reload the page using header('Location: xxx');
Perform again the first 2 steps for the next 5 parts, until all parts are uploaded;
Finalise the upload using Aws\S3\S3Client::completeMultipartUpload().
I suppose that this should work but before to use this method, I'd like to know if there is an easier way to achieve my goal, for example by using the high-level API...
Any suggestions?
NOTE : I'm not searching for some existing script : my main goal is to learn how to fix this issue :)
Best regards,
Lionel
Why not just use the AWS CLI to copy the file? You can create a script in the CLI and that way everything is AWS native. (Amazon has a tutorial on that.) You can use the scp command:
scp -i Amazonkey.pem /local/path/backupfile.tar.gz ec2-user#Elastic-IP-of-ec2-2:/path/backupfile.tar.gz
From my perspective, it would be easier to do the work within AWS, which has features to move files and data. If you'd like to use a shell script, this article on automating EC2 backups has a good one, plus more detail on backup options.
To answer my own question (I hope it might help someone one day!), he is how I fixed my issue, step by step:
1/ When I load my page, I check if the archive already exists. If not, I create my .tar.gz file and I reload the page using header().
I noticed that this step was quite slow since there is lot of data to archive. That's why I reload my page to avoid any timeout during the next steps!
2/ If the backup file exists, I use AWS MultipartUpload to send 10 chunks of 100MB each. Everytime that a chunk is sent successfully, I update a session variable ($_SESSION['backup']['partNumber']) to know what is the next chunk that needs to be uploaded.
Once my 10 chunks are sent, I reload the page again to avoid any timeout.
3/ I repeat the second step until the upload of all parts is done, using my session variable to know which part of the upload needs to be sent next.
4/ Finally, I complete the multipart upload and I delete the archive stored locally.
You can of course send more than 10 times 100MB before to reload your page. I chose this value to be sure that I won't reach a timeout even if the download is slow. But I guess I could send easilly around 5GB each time without issue.
Note: You cannot redirect you script to itself too much time. There is a limit (I think it's around 20 times for Chrome and Firefoxbefore to get an error, and more for IE). In my case (the archive is around 10GB), transfering 1GB per reload is fine (the page will be reloaded around 10 times). But it the archive size increases, I'll have to send more chunks each time.
Here is my full script. I could surely be improved, but it's working quite well for now and it may help someone having similar issue!
public function backup()
{
ini_set('max_execution_time', '1800');
ini_set('memory_limit', '1024M');
require ROOT.'/Public/scripts/aws/aws-autoloader.php';
$s3Client = new \Aws\S3\S3Client([
'version' => 'latest',
'region' => 'eu-west-1',
'credentials' => [
'key' => '',
'secret' => '',
],
]);
$tmpDBBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.sql.gz';
if(!file_exists($tmpDBBackupDirectory))
{
$this->cleanInterruptedMultipartUploads($s3Client);
$this->createSQLBackupFile();
$this->uploadSQLBackup($s3Client, $tmpDBBackupDirectory);
}
$tmpFilesBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.tar.gz';
if(!isset($_SESSION['backup']['archiveReady']))
{
$this->createFTPBackupFile();
header('Location: '.CURRENT_URL);
}
$this->uploadFTPBackup($s3Client, $tmpFilesBackupDirectory);
unlink($tmpDBBackupDirectory);
unlink($tmpFilesBackupDirectory);
}
public function createSQLBackupFile()
{
// Backup DB
$tmpDBBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.sql.gz';
if(!file_exists($tmpDBBackupDirectory))
{
$return_var = NULL;
$output = NULL;
$dbLogin = '';
$dbPassword = '';
$dbName = '';
$command = 'mysqldump -u '.$dbLogin.' -p'.$dbPassword.' '.$dbName.' --single-transaction --quick | gzip > '.$tmpDBBackupDirectory;
exec($command, $output, $return_var);
}
return $tmpDBBackupDirectory;
}
public function createFTPBackupFile()
{
// Compacting all files
$tmpFilesBackupDirectory = ROOT.'Var/Backups/backup'.date('Y-m-d').'.tar.gz';
$command = 'tar -cf '.$tmpFilesBackupDirectory.' '.ROOT;
exec($command);
$_SESSION['backup']['archiveReady'] = true;
return $tmpFilesBackupDirectory;
}
public function uploadSQLBackup($s3Client, $tmpDBBackupDirectory)
{
$result = $s3Client->putObject([
'Bucket' => '',
'Key' => 'backup'.date('Y-m-d').'.sql.gz',
'SourceFile' => $tmpDBBackupDirectory,
'StorageClass' => '',
'Tagging' => '',
'ServerSideEncryption' => 'AES256',
]);
}
public function uploadFTPBackup($s3Client, $tmpFilesBackupDirectory)
{
$storageClass = 'STANDARD_IA';
$bucket = '';
$key = 'backup'.date('Y-m-d').'.tar.gz';
$chunkSize = 100 * 1024 * 1024; // 100MB
$reloadFrequency = 10;
if(!isset($_SESSION['backup']['uploadId']))
{
$response = $s3Client->createMultipartUpload([
'Bucket' => $bucket,
'Key' => $key,
'StorageClass' => $storageClass,
'Tagging' => '',
'ServerSideEncryption' => 'AES256',
]);
$_SESSION['backup']['uploadId'] = $response['UploadId'];
$_SESSION['backup']['partNumber'] = 1;
}
$file = fopen($tmpFilesBackupDirectory, 'r');
$parts = array();
//Reading parts already uploaded
for($i = 1; $i < $_SESSION['backup']['partNumber']; $i++)
{
if(!feof($file))
{
fread($file, $chunkSize);
}
}
// Uploading next parts
while(!feof($file))
{
do
{
try
{
$result = $s3Client->uploadPart(array(
'Bucket' => $bucket,
'Key' => $key,
'UploadId' => $_SESSION['backup']['uploadId'],
'PartNumber' => $_SESSION['backup']['partNumber'],
'Body' => fread($file, $chunkSize),
));
}
}
while (!isset($result));
$_SESSION['backup']['parts'][] = array(
'PartNumber' => $_SESSION['backup']['partNumber'],
'ETag' => $result['ETag'],
);
$_SESSION['backup']['partNumber']++;
if($_SESSION['backup']['partNumber'] % $reloadFrequency == 1)
{
header('Location: '.CURRENT_URL);
die;
}
}
fclose($file);
$result = $s3Client->completeMultipartUpload(array(
'Bucket' => $bucket,
'Key' => $key,
'UploadId' => $_SESSION['backup']['uploadId'],
'MultipartUpload' => Array(
'Parts' => $_SESSION['backup']['parts'],
),
));
$url = $result['Location'];
}
public function cleanInterruptedMultipartUploads($s3Client)
{
$tResults = $s3Client->listMultipartUploads(array('Bucket' => ''));
$tResults = $tResults->toArray();
if(isset($tResults['Uploads']))
{
foreach($tResults['Uploads'] AS $result)
{
$s3Client->abortMultipartUpload(array(
'Bucket' => '',
'Key' => $result['Key'],
'UploadId' => $result['UploadId']));
}
}
if(isset($_SESSION['backup']))
{
unset($_SESSION['backup']);
}
}
If someone has questions don't hesitate to contact me :)
I'm trying to test in PHP Amazon S3 on my localhost on Ubuntu system but keep getting the same error:
S3::listBuckets(): [35] error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol
It is the function to display bucket list.
public function buckets() {
$s3 = $this->getInstance();
/*print_r($this->_s3->listBuckets()); nothing is print else shows error */
return $this->_s3->listBuckets();
}
Here is the Amazon API function that has been called by this function.
public static function listBuckets($detailed = false) {
$rest = new S3Request('GET', '', '');
$rest = $rest->getResponse();
if ($rest->error === false && $rest->code !== 200)
$rest->error = array('code' => $rest->code, 'message' => 'Unexpected HTTP status');
if ($rest->error !== false) {
trigger_error(sprintf("S3::listBuckets(): [%s] %s", $rest->error['code'], $rest->error['message']), E_USER_WARNING);
return false;
}
$results = array();
if (!isset($rest->body->Buckets))
return $results;
if ($detailed) {
if (isset($rest->body->Owner, $rest->body->Owner->ID, $rest->body->Owner->DisplayName))
$results['owner'] = array(
'id' => (string) $rest->body->Owner->ID, 'name' => (string) $rest->body->Owner->ID
);
$results['buckets'] = array();
foreach ($rest->body->Buckets->Bucket as $b)
$results['buckets'][] = array(
'name' => (string) $b->Name, 'time' => strtotime((string) $b->CreationDate)
);
}
else
foreach ($rest->body->Buckets->Bucket as $b)
$results[] = (string) $b->Name;
return $results;
}
It seems that you have changed your PHP version as this bug is occurred several time in PHP 5.4 but it works perfectly in previous versions. You can re-install cURL with Open SSL again.
It is most common error occurred in integration of AWS S3 on localhost.
Check is cURL is enable and Open SSL is also active.
Get file from http://curl.haxx.se/ca/cacert.pem and save it to your libraries at hard drive. Rename it cacert.pem.
Configure curl.cainfo in php.ini with the full path to the file downloaded in step 2.
Restart Apache.
It will be worked perfectly.
I have Signed URLs on Cloudfront working fine in PHP. Bucket policies work with HTTP referrers on S3 but because Cloudfront doesn't support HTTP referrer checks I need to serve a file to one IP address only (the client that requested the file and generated the signed URL or my web server ideally).
Can someone please help me add the IP Address element to the JSON code so it works?
"IpAddress":{"AWS:SourceIp":"192.0.2.0/24"},
I'm lost with the PHP and Policy Statement but think it might be easy for someone who knows: http://tinyurl.com/9czr5lp
It does encoding/signing a bit differently for a custom policy: http://docs.amazonwebservices.com/AmazonCloudFront/latest/DeveloperGuide/private-content-creating-signed-url-custom-policy.html#private-content-custom-policy-statement
The below is an AWS example and works except not for the IP Address lock in.
I can test this very quickly if someone can please give me a hand for two minutes!
Thanks MASSIVELY for any help :)
Jon
function getSignedURL($resource, $timeout)
{
$keyPairId = "XXXXXXXXXXXX";
$expires = time() + $timeout;
$json = '{"Statement":[{"Resource":"'.$resource.'","Condition":{"DateLessThan": {"AWS:EpochTime":'.$expires.'}}}]}';
$fp=fopen("pk-XXXXXXXX.pem","r");
$priv_key=fread($fp,8192);
fclose($fp);
$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;
}
$url = getSignedURL("http://s675765.cloudfront.net/filename.mp4", 600);
print $url;
{"Statement":[{"Resource":"testRes","Condition":{"DateLessThan":{"AWS:EpochTime":1357034400},"IpAddress":{"AWS:SourceIp":"192.0.2.0\/24"}}}]}
This is a valid JSON string with filled and escaped values.
If you pass the IP address as a variable make sure you escape the /
e.g.
$escapedIp = str_replace( '/', '\/', $ipAddress );
Have a look at the php json extension
This would make things quite easier:
example statement as php array
$statement = array(
'Statement' => array(
array(
'Resource' => $resource,
'Condition' => array(
'DateLessThan' => array(
'AWS:EpochTime' => $expires
),
'IpAddress' => array(
'AWS:SourceIp' => $ipAddress
)
)
)
)
);
$json = json_encode( $statement );