From 2c20da3d525cbbba1d9250a1521fe18572ff54b9 Mon Sep 17 00:00:00 2001 From: Brona Robenek Date: Fri, 4 Jan 2019 16:53:15 +0100 Subject: [PATCH 1/3] Adds example for generating Cloud CDN Signed URLs Example implementation of generating URL signature for Cloud CDN. Based on: https://cloud.google.com/cdn/docs/using-signed-urls#creating_signed_urls --- cdn/signUrl.php | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 cdn/signUrl.php diff --git a/cdn/signUrl.php b/cdn/signUrl.php new file mode 100644 index 0000000000..68a76af6dc --- /dev/null +++ b/cdn/signUrl.php @@ -0,0 +1,51 @@ + Date: Mon, 7 Jan 2019 14:56:14 +0100 Subject: [PATCH 2/3] Implements base64url, adds test and readme Base64url implementation based on RFC. Test cases based on: https://github.com/GoogleCloudPlatform/golang-samples/blob/master/cdn/signedurls/signurl_test.go --- cdn/README.md | 15 ++++++++++ cdn/phpunit.xml.dist | 31 ++++++++++++++++++++ cdn/signUrl.php | 53 +++++++++++++++++++++++++++------- cdn/test/signUrlTest.php | 62 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 cdn/README.md create mode 100644 cdn/phpunit.xml.dist create mode 100644 cdn/test/signUrlTest.php diff --git a/cdn/README.md b/cdn/README.md new file mode 100644 index 0000000000..a747807efe --- /dev/null +++ b/cdn/README.md @@ -0,0 +1,15 @@ +# Google Cloud CDN Sign URL + +PHP implementation of [`gcloud compute sign-url`](https://cloud.google.com/sdk/gcloud/reference/compute/sign-url) based on [Google Cloud CDN Documentation](https://cloud.google.com/cdn/docs/using-signed-urls#signing_urls). +The provided file includes implementation of base64url encode and decode functions based on [RFC4648 Section 5](https://tools.ietf.org/html/rfc4648#section-5). + +## Usage + +``` + +``` diff --git a/cdn/phpunit.xml.dist b/cdn/phpunit.xml.dist new file mode 100644 index 0000000000..42d504927c --- /dev/null +++ b/cdn/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + test + + + + + + + + signUrl.php + + + diff --git a/cdn/signUrl.php b/cdn/signUrl.php index 68a76af6dc..78deebd556 100644 --- a/cdn/signUrl.php +++ b/cdn/signUrl.php @@ -16,19 +16,57 @@ */ # [START signed_url] +/** + * Decodes base64url (RFC4648 Section 5) string + * + * @param string $input base64url encoded string + * + * @return string + */ +function base64url_decode($input) +{ + $input .= str_repeat('=', (4 - strlen($input) % 4) % 4); + return base64_decode(strtr($input, '-_', '+/')); +} + +/** +* Encodes a string with base64url (RFC4648 Section 5) +* Keeps the '=' padding by default. +* +* @param string $input String to be encoded +* @param bool $padding Keep the '=' padding +* +* @return string +*/ +function base64url_encode($input, $padding = true) +{ + $output = strtr(base64_encode($input), '+/', '-_'); + return ($padding) ? $output : str_replace('=', '', $output); +} + /** * Creates signed URL for Google Cloud CDN * Details about order of operations: https://cloud.google.com/cdn/docs/using-signed-urls#creating_signed_urls * + * Example function invocation (In production store the key safely with other secrets): + * + * + * * @param string $url URL of the endpoint served by Cloud CDN * @param string $keyName Name of the signing key added to the Google Cloud Storage bucket or service - * @param string $key Signing key as base64 encoded string + * @param string $base64url_key Signing key as base64url (RFC4648 Section 5) encoded string * @param int $expiration_time Expiration time as a UNIX timestamp (GMT, e.g. time()) + * + * @return string */ -function signUrl($url, $keyName, $key, $expiration_time) +function signUrl($url, $keyName, $base64url_key, $expiration_time) { // Decode the key - $decoded_key = base64_decode($key, true); + $decoded_key = base64url_decode($base64url_key, true); // Determine which separator makes sense given a URL $separator = (strpos($url, '?') === false) ? '?' : '&'; @@ -36,16 +74,11 @@ function signUrl($url, $keyName, $key, $expiration_time) // Concatenate url with expected query parameters Expires and KeyName $url = "{$url}{$separator}Expires={$expiration_time}&KeyName={$keyName}"; - // Sign the url using the key and encode the signature using base64 + // Sign the url using the key and encode the signature using base64url $signature = hash_hmac('sha1', $url, $decoded_key, true); - $encoded_signature = base64_encode($signature); + $encoded_signature = base64url_encode($signature); // Concatenate the URL and encoded signature return "{$url}&Signature={$encoded_signature}"; } // [END signed_url] - -// Example function call (In production store the key safely with other secrets) -$base64_key = '4RfgBoqmotRolLCtU-82Ew=='; // head -c 16 /dev/urandom | base64 | tr +/ -_ -$signed_url = signUrl('https://example.com/foo', 'MY-KEY', $base64_key, time() + 1800); -echo $signed_url."\n"; diff --git a/cdn/test/signUrlTest.php b/cdn/test/signUrlTest.php new file mode 100644 index 0000000000..5dfb077e48 --- /dev/null +++ b/cdn/test/signUrlTest.php @@ -0,0 +1,62 @@ +assertEquals(base64url_encode(hex2bin("9d9b51a2174d17d9b770a336e0870ae3")), "nZtRohdNF9m3cKM24IcK4w=="); + } + + public function testBase64UrlEncodeWithoutPadding() + { + $this->assertEquals(base64url_encode(hex2bin("9d9b51a2174d17d9b770a336e0870ae3"), false), "nZtRohdNF9m3cKM24IcK4w"); + } + + public function testBase64UrlDecode() + { + $this->assertEquals(hex2bin("9d9b51a2174d17d9b770a336e0870ae3"), base64url_decode("nZtRohdNF9m3cKM24IcK4w==")); + } + + public function testBase64UrlDecodeWithoutPadding() + { + $this->assertEquals(hex2bin("9d9b51a2174d17d9b770a336e0870ae3"), base64url_decode("nZtRohdNF9m3cKM24IcK4w")); + } + + public function testSignUrl() + { + $encoded_key="nZtRohdNF9m3cKM24IcK4w=="; // base64url encoded key + + $cases = array( + array("http://35.186.234.33/index.html", "my-key", 1558131350, + "http://35.186.234.33/index.html?Expires=1558131350&KeyName=my-key&Signature=fm6JZSmKNsB5sys8VGr-JE4LiiE="), + array("https://www.google.com/", "my-key", 1549751401, + "https://www.google.com/?Expires=1549751401&KeyName=my-key&Signature=M_QO7BGHi2sGqrJO-MDr0uhDFuc="), + array("https://www.example.com/some/path?some=query&another=param", "my-key", 1549751461, + "https://www.example.com/some/path?some=query&another=param&Expires=1549751461&KeyName=my-key&Signature=sTqqGX5hUJmlRJ84koAIhWW_c3M="), + ); + + foreach($cases as $c) + { + $this->assertEquals(signUrl($c[0], $c[1], $encoded_key, $c[2]), $c[3]); + } + } +} From 1e41c628f6be8998d00761802e0fb128381120f4 Mon Sep 17 00:00:00 2001 From: Brona Robenek Date: Wed, 16 Jan 2019 13:56:31 +0100 Subject: [PATCH 3/3] Naming convention and removing test bootstrap --- cdn/phpunit.xml.dist | 2 +- cdn/signUrl.php | 22 +++++++++++----------- cdn/test/signUrlTest.php | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cdn/phpunit.xml.dist b/cdn/phpunit.xml.dist index 42d504927c..8227a75be8 100644 --- a/cdn/phpunit.xml.dist +++ b/cdn/phpunit.xml.dist @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - + test diff --git a/cdn/signUrl.php b/cdn/signUrl.php index 78deebd556..b7b6753b71 100644 --- a/cdn/signUrl.php +++ b/cdn/signUrl.php @@ -51,34 +51,34 @@ function base64url_encode($input, $padding = true) * Example function invocation (In production store the key safely with other secrets): * * * * @param string $url URL of the endpoint served by Cloud CDN * @param string $keyName Name of the signing key added to the Google Cloud Storage bucket or service - * @param string $base64url_key Signing key as base64url (RFC4648 Section 5) encoded string - * @param int $expiration_time Expiration time as a UNIX timestamp (GMT, e.g. time()) + * @param string $base64UrlKey Signing key as base64url (RFC4648 Section 5) encoded string + * @param int $expirationTime Expiration time as a UNIX timestamp (GMT, e.g. time()) * * @return string */ -function signUrl($url, $keyName, $base64url_key, $expiration_time) +function sign_url(https://github.com/$url,%20$keyName,%20$base64UrlKey,%20$expirationTime) { // Decode the key - $decoded_key = base64url_decode($base64url_key, true); + $decodedKey = base64url_decode($base64UrlKey, true); // Determine which separator makes sense given a URL $separator = (strpos($url, '?') === false) ? '?' : '&'; // Concatenate url with expected query parameters Expires and KeyName - $url = "{$url}{$separator}Expires={$expiration_time}&KeyName={$keyName}"; + $url = "{$url}{$separator}Expires={$expirationTime}&KeyName={$keyName}"; // Sign the url using the key and encode the signature using base64url - $signature = hash_hmac('sha1', $url, $decoded_key, true); - $encoded_signature = base64url_encode($signature); + $signature = hash_hmac('sha1', $url, $decodedKey, true); + $encodedSignature = base64url_encode($signature); // Concatenate the URL and encoded signature - return "{$url}&Signature={$encoded_signature}"; + return "{$url}&Signature={$encodedSignature}"; } // [END signed_url] diff --git a/cdn/test/signUrlTest.php b/cdn/test/signUrlTest.php index 5dfb077e48..e8c0abb04f 100644 --- a/cdn/test/signUrlTest.php +++ b/cdn/test/signUrlTest.php @@ -56,7 +56,7 @@ public function testSignUrl() foreach($cases as $c) { - $this->assertEquals(signUrl($c[0], $c[1], $encoded_key, $c[2]), $c[3]); + $this->assertEquals(sign_url(https://github.com/$c[0],%20$c[1],%20$encoded_key,%20$c[2]), $c[3]); } } }