diff --git a/cdn/signed-urls/README.md b/cdn/signed-urls/README.md new file mode 100644 index 00000000000..942b8013572 --- /dev/null +++ b/cdn/signed-urls/README.md @@ -0,0 +1,9 @@ +# Google Cloud CDN - Signing URLs +Java implementation of [`gcloud alpha compute sign-url`](https://cloud.google.com/sdk/gcloud/reference/alpha/compute/sign-url) +- uses a private key to create a time-sensitive URL that can be used to access a private Cloud CDN endpoint +- requires [random 128-bit key](https://cloud.google.com/cdn/docs/signed-urls#creatingkeys) encoded as base64 and [uploaded to a backend bucket](https://cloud.google.com/sdk/gcloud/reference/alpha/compute/backend-buckets/add-signed-url-key) + +## Getting Started + +1. [Download](https://maven.apache.org/download.cgi) and [install](https://maven.apache.org/install.html) maven to handle the project's dependencies +2. run `mvn clean verify` to build the project and run the tests diff --git a/cdn/signed-urls/pom.xml b/cdn/signed-urls/pom.xml new file mode 100644 index 00000000000..b19b5d62a6a --- /dev/null +++ b/cdn/signed-urls/pom.xml @@ -0,0 +1,37 @@ + + 4.0.0 + + com.google.cdn + signedurls + 1.0 + jar + + + + com.google.cloud.samples + shared-configuration + 1.0.8 + + + signedurls + http://maven.apache.org + + + 1.8 + 1.8 + UTF-8 + + + + + junit + junit + 4.12 + test + + + diff --git a/cdn/signed-urls/src/main/java/com/google/cdn/SignedUrls.java b/cdn/signed-urls/src/main/java/com/google/cdn/SignedUrls.java new file mode 100644 index 00000000000..03d7a9a3cfe --- /dev/null +++ b/cdn/signed-urls/src/main/java/com/google/cdn/SignedUrls.java @@ -0,0 +1,91 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cdn; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Calendar; +import java.util.Date; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +/** + * Samples to create a signed URL for a Cloud CDN endpoint + */ +public class SignedUrls { + + // [START signUrl] + /** + * Creates a signed URL for a Cloud CDN endpoint with the given key + * URL must start with http:// or https://, and must contain a forward + * slash (/) after the hostname. + * @param url the Cloud CDN endpoint to sign + * @param key url signing key uploaded to the backend service/bucket, as a 16-byte array + * @param keyName the name of the signing key added to the back end bucket or service + * @param expirationTime the date that the signed URL expires + * @return a properly formatted signed URL + * @throws InvalidKeyException when there is an error generating the signature for the input key + * @throws NoSuchAlgorithmException when HmacSHA1 algorithm is not available in the environment + */ + public static String signUrl(String url, + byte[] key, + String keyName, + Date expirationTime) + throws InvalidKeyException, NoSuchAlgorithmException { + + final long unixTime = expirationTime.getTime() / 1000; + + String urlToSign = url + + (url.contains("?") ? "&" : "?") + + "Expires=" + unixTime + + "&KeyName=" + keyName; + + String encoded = SignedUrls.getSignature(key, urlToSign); + return urlToSign + "&Signature=" + encoded; + } + + public static String getSignature(byte[] privateKey, String input) + throws InvalidKeyException, NoSuchAlgorithmException { + + final String algorithm = "HmacSHA1"; + final int offset = 0; + Key key = new SecretKeySpec(privateKey, offset, privateKey.length, algorithm); + Mac mac = Mac.getInstance(algorithm); + mac.init(key); + return Base64.getUrlEncoder().encodeToString(mac.doFinal(input.getBytes())); + } + // [END signUrl] + + public static void main(String[] args) throws Exception { + Calendar cal = Calendar.getInstance(); + cal.setTime(new Date()); + cal.add(Calendar.DATE, 1); + Date tomorrow = cal.getTime(); + + //read the key as a base 64 url-safe encoded string, then convert to byte array + final String keyPath = "/path/to/key"; + String base64String = new String(Files.readAllBytes(Paths.get(keyPath))); + byte[] keyBytes = Base64.getUrlDecoder().decode(base64String); + + String result = signUrl("http://example.com/", keyBytes, "YOUR-KEY-NAME", tomorrow); + System.out.println(result); + } +} diff --git a/cdn/signed-urls/src/test/java/com/google/cdn/SignedUrlsTest.java b/cdn/signed-urls/src/test/java/com/google/cdn/SignedUrlsTest.java new file mode 100644 index 00000000000..c742eac8da2 --- /dev/null +++ b/cdn/signed-urls/src/test/java/com/google/cdn/SignedUrlsTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +package com.google.cdn; + +import static com.google.cdn.SignedUrls.signUrl; +import static junit.framework.TestCase.assertEquals; + +import java.util.Base64; +import java.util.Date; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Test SignedUrls samples + */ +@RunWith(JUnit4.class) +@SuppressWarnings("checkstyle:abbreviationaswordinname") +public class SignedUrlsTest { + private static long TIMESTAMP = 1518135754; + private static Date EXPIRATION = new Date(TIMESTAMP * 1000); + private static byte[] KEY_BYTES = Base64.getUrlDecoder().decode("aaaaaaaaaaaaaaaaaaaaaa=="); + private static String KEY_NAME = "my-key"; + private static String BASE_URL = "https://www.example.com/"; + + @Test + public void testUrlPath() throws Exception { + String result = signUrl(BASE_URL + "foo", KEY_BYTES, KEY_NAME, EXPIRATION); + final String expected = "https://www.example.com/foo?Expires=1518135754&KeyName=my-key&Signature=vUfG4yv47dyns1j9e_OI6_5meuA="; + assertEquals(result, expected); + } + + @Test + public void testUrlParams() throws Exception { + String result = signUrl(BASE_URL + "?param=true", KEY_BYTES, KEY_NAME, EXPIRATION); + final String expected = "https://www.example.com/?param=true&Expires=1518135754&KeyName=my-key&Signature=6TijW8OMX3gcMI5Kqs8ESiPY97c="; + assertEquals(result, expected); + } + + + @Test + public void testStandard() throws Exception { + String result = signUrl(BASE_URL, KEY_BYTES, KEY_NAME, EXPIRATION); + final String expected = "https://www.example.com/?Expires=1518135754&KeyName=my-key&Signature=4D0AbT4y0O7ZCzCUcAtPOJDkl2g="; + assertEquals(result, expected); + } +} diff --git a/pom.xml b/pom.xml index d6b6fb6cdf4..0a15493d0ca 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,7 @@ bigquery/datatransfer/cloud-client bigquery/rest + cdn/signed-urls cloud-tasks compute container-registry/container-analysis