Skip to content

Instantly share code, notes, and snippets.

@mloureiro
Last active June 19, 2016 11:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mloureiro/14503293c3cb2191e2f6 to your computer and use it in GitHub Desktop.
Save mloureiro/14503293c3cb2191e2f6 to your computer and use it in GitHub Desktop.
Generate a Google Signed URL
<?
/**
* Google Bucket GET file with signed string example
*/
require_once("vendor/autoload.php");
/*
* Helper Methods
*/
function googleBuildConfigurationString($method, $expiration, $file, array $options = [])
{
$allowedMethods = ['GET', 'HEAD', 'PUT', 'DELETE'];
// initialize
$method = strtoupper($method);
$contentType = $options['Content_Type'];
$contentMd5 = $options['Content_MD5']
? base64_encode($options['Content_MD5'])
: '';
$headers = $options['Canonicalized_Extension_Headers']
? $options['Canonicalized_Extension_Headers'] . PHP_EOL
: '';
$file = $file ? $file : $options['Canonicalized_Resource'];
// validate
if(array_search($method, $allowedMethods) === false)
{
throw new RuntimeException("Method '{$method}' is not allowed");
}
if(!$expiration)
{
throw new RuntimeException("An expiration date should be provided.");
}
return <<<TXT
{$method}
{$contentMd5}
{$contentType}
{$expiration}
{$headers}{$file}
TXT;
}
function googleSignString($p12FilePath, $string)
{
$certs = [];
if (!openssl_pkcs12_read(file_get_contents($p12FilePath), $certs, 'notasecret'))
{
echo "Unable to parse the p12 file. OpenSSL error: " . openssl_error_string(); exit();
}
$RSAPrivateKey = openssl_pkey_get_private($certs["pkey"]);
$signed = '';
if(!openssl_sign( $string, $signed, $RSAPrivateKey, 'sha256' ))
{
error_log( 'openssl_sign failed!' );
$signed = 'failed';
}
else
{
$signed = base64_encode($signed);
}
return $signed;
}
function googleBuildSignedUrl($serviceEmail, $file, $expiration, $signature)
{
return "http://storage.googleapis.com{$file}"
. "?GoogleAccessId={$serviceEmail}"
. "&Expires={$expiration}"
. "&Signature=" . urlencode($signature);
}
/*
* Initialization
*/
$serviceEmail = '<service-email>';
$p12FilePath = 'keys/google.certificate.p12';
$expiration = (new DateTime())->modify('+3hours')->getTimestamp();
$bucket = 'example';
$fileToGet = 'video.mp4';
/*
* Building!
*/
$file = "/{$bucket}/{$fileToGet}";
$string = googleBuildConfigurationString('GET', $expiration, $file);
$signedString = googleSignString($p12FilePath, $string);
$signedUrl = googleBuildSignedUrl($serviceEmail, $file, $expiration, $signedString);
?>
<html>
<body>
<style>pre {background: #eee;}</style>
<h2>String</h2>
<pre><?= $string ?></pre>
<br>
<h2>Final Url</h2>
<pre><?= $signedUrl ?></pre>
<br>
<video controls>
<source src="<?= $signedUrl ?>" type="video/mp4">
</video>
</body>
</html>
@mloureiro
Copy link
Author

Notice:
If you're using the PHP API from Google you can use the Google_Signer_P12::sign() method, so it's possible to change sign method to:

function googleSignString($p12FilePath, $string)
{
  return base64_encode((new Google_Signer_P12(
    file_get_contents($p12FilePath),
    'notasecret'
  ))->sign($string));
}

Copy link

ghost commented Jan 20, 2016

Thanks for writing this up. I'm trying to figure this part out but this is kinda confusing. I've never seen anything like this. Care to elaborate?

return <<<TXT
{$method}
{$contentMd5}
{$contentType}
{$expiration}
{$headers}{$file}
TXT;

@mloureiro
Copy link
Author

Each variable on the return string represents a line (or multiple in case of headers).
The required ones are method (called HTTP_VERB on goolge), expiration and the file.

So that will return a string similar to the examples at Sign Urls#Construct-the-String

For instance:

PUT
rmYdCNHKFXam78uCt7xQLw==
text/plain
1388534400
x-goog-acl:public-read
x-goog-meta-foo:bar,baz
/bucket/objectname

@johan-lejdung
Copy link

johan-lejdung commented Jun 19, 2016

Are you positive this works? I'm getting a 'SignatureDoesNotMatch' error.. And my although it looks like it signed the url and creates a correct signed string.

Edit: The problem seemed to be with the way the signString was constructed. It might be a problem isolated to me but sometimes i had one too few linebreaks between GET and the expire date :/. I resorted to build it myself

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment