Skip to content
55 changes: 55 additions & 0 deletions storage/json-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Google Cloud Storage (GCS) and the Google Java API Client library

Google Cloud Storage Service features a REST-based API that allows developers to store and access arbitrarily-large objects. These sample Java applications demonstrate how to access the Google Cloud Storage JSON API using the Google Java API Client Libraries. For more information, read the [Google Cloud Storage JSON API Overview][1].

## Quickstart

1. Install the [Google Cloud SDK](https://cloud.google.com/sdk/), including the [gcloud tool](https://cloud.google.com/sdk/gcloud/).

1. Setup the gcloud tool.

```
gcloud init
```

1. Clone this repo.

```
git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git
```

1. Install [Maven](http://maven.apache.org/).

1. Build this project from this directory:

```
mvn package
```

1. Run one of the sample apps by specifying its class name and a bucket name:

```
mvn exec:java -Dexec.mainClass=StorageSample \
-Dexec.args="ABucketName"
```

Note that if it's been a while, you may need to login with gcloud.

```
gcloud auth login
```

## Products
- [Google Cloud Storage][2]

## Language
- [Java][3]

## Dependencies
- [Google APIs Client Library for Java][4]

[1]: https://cloud.google.com/storage/docs/json_api
[2]: https://cloud.google.com/storage
[3]: https://java.com
[4]: http://code.google.com/p/google-api-java-client/

28 changes: 8 additions & 20 deletions storage/json-api/pom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>doc-samples</artifactId>
<groupId>com.google.cloud</groupId>
Expand All @@ -19,36 +20,23 @@
</properties>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>StorageSample</mainClass>
</configuration>
</plugin>
</plugins>
<finalName>${project.artifactId}-${project.version}</finalName>
</build>
<dependencies>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-storage</artifactId>
<version>v1-rev18-1.19.0</version>
<version>v1-rev65-1.21.0</version>
</dependency>
<dependency>
<groupId>com.google.oauth-client</groupId>
<artifactId>google-oauth-client-jetty</artifactId>
<version>1.21.0</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.InputStreamContent;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.model.RewriteResponse;

import java.io.IOException;
import java.io.InputStream;

/**
* Demonstrates the use of GCS's CSEK features via the Java API client library
*
* This program demonstrates some quick, basic examples of using GCS's CSEK functionality.
*
* <p>When run, it begins by uploading an object named "encrypted_file.txt" to the specified bucket
* that will be protected with a provided CSEK.</p>
*
* <p>Next, it will fetch that object by providing that same CSEK to GCS.</p>
*
* <p>Finally, it will rotate that key to a new value.</p>
**/
class CustomerSuppliedEncryptionKeysSamples {

// You can (and should) generate your own CSEK Key! Try running this from the command line:
// python -c 'import base64; import os; print(base64.encodestring(os.urandom(32)))'
// Also, these encryption keys are included here for simplicity, but please remember that
// private keys should not be stored in source code.
private static final String CSEK_KEY = "4RzDI0TeWa9M/nAvYH05qbCskPaSU/CFV5HeCxk0IUA=";

// You can use openssl to quickly calculate the hash of your key. Try running this:
// openssl base64 -d <<< YOUR_KEY_FROM_ABOVE | openssl dgst -sha256 -binary | openssl base64
private static final String CSEK_KEY_HASH = "aanjNC2nwso8e2FqcWILC3/Tt1YumvIwEj34kr6PRpI=";

// Used for the key rotation example
private static final String ANOTHER_CESK_KEY = "oevtavYZC+TfGtV86kJBKTeytXAm1s2r3xIqam+QPKM=";
private static final String ANOTHER_CSEK_KEY_HASH =
"/gd0N3k3MK0SEDxnUiaswl0FFv6+5PHpo+5KD5SBCeA=";

private static final String OBJECT_NAME = "encrypted_file.txt";

/**
* Downloads a CSEK-protected object from GCS. The download may continue in the background after
* this method returns. The caller of this method is responsible for closing the input stream.
*
* @param storage A Storage object, ready for use
* @param bucketName The name of the destination bucket
* @param objectName The name of the destination object
* @param base64CSEKey An AES256 key, encoded as a base64 string.
* @param base64CSEKeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
* @throws IOException if there was some error download from GCS.
*
* @return An InputStream that contains the decrypted contents of the object.
*/
public static InputStream downloadObject(
Storage storage,
String bucketName,
String objectName,
String base64CSEKey,
String base64CSEKeyHash)
throws Exception {
Storage.Objects.Get getObject = storage.objects().get(bucketName, objectName);

// If you're using AppEngine, turn off setDirectDownloadEnabled:
// getObject.getMediaHttpDownloader().setDirectDownloadEnabled(false);

// Now set the CSEK headers
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-encryption-key", base64CSEKey);
httpHeaders.set("x-goog-encryption-key-sha256", base64CSEKeyHash);

getObject.setRequestHeaders(httpHeaders);

try {
return getObject.executeMediaAsInputStream();
} catch (GoogleJsonResponseException e) {
System.out.println("Error downloading: " + e.getContent());
System.exit(1);
return null;
}
}

/**
* Uploads an object to GCS, to be stored with a customer-supplied key (CSEK). The upload may
* continue in the background after this method returns. The caller of this method is responsible
* for closing the input stream.
*
* @param storage A Storage object, ready for use
* @param bucketName The name of the destination bucket
* @param objectName The name of the destination object
* @param data An InputStream containing the contents of the object to upload
* @param base64CSEKey An AES256 key, encoded as a base64 string.
* @param base64CSEKeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
* @throws IOException if there was some error uploading to GCS.
*/
public static void uploadObject(
Storage storage,
String bucketName,
String objectName,
InputStream data,
String base64CSEKey,
String base64CSEKeyHash)
throws IOException {
InputStreamContent mediaContent = new InputStreamContent("text/plain", data);
Storage.Objects.Insert insertObject =
storage.objects().insert(bucketName, null, mediaContent).setName(objectName);
// The client library's default gzip setting may cause objects to be stored with gzip encoding,
// which can be desirable in some circumstances but has some disadvantages as well, such as
// making it difficult to read only a certain range of the original object.
insertObject.getMediaHttpUploader().setDisableGZipContent(true);

// Now set the CSEK headers
final HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-encryption-key", base64CSEKey);
httpHeaders.set("x-goog-encryption-key-sha256", base64CSEKeyHash);

insertObject.setRequestHeaders(httpHeaders);

try {
insertObject.execute();
} catch (GoogleJsonResponseException e) {
System.out.println("Error uploading: " + e.getContent());
System.exit(1);
}
}

/**
* Given an existing, CSEK-protected object, changes the key used to store that object.
*
* @param storage A Storage object, ready for use
* @param bucketName The name of the destination bucket
* @param objectName The name of the destination object
* @param originalBase64Key The AES256 key currently associated with this object,
* encoded as a base64 string.
* @param originalBase64KeyHash The SHA-256 hash of the above key,
* also encoded as a base64 string.
* @param newBase64Key An AES256 key which will replace the existing key,
* encoded as a base64 string.
* @param newBase64KeyHash The SHA-256 hash of the above key, also encoded as a base64 string.
* @throws IOException if there was some error download from GCS.
*/
public static void rotateKey(
Storage storage,
String bucketName,
String objectName,
String originalBase64Key,
String originalBase64KeyHash,
String newBase64Key,
String newBase64KeyHash)
throws Exception {
Storage.Objects.Rewrite rewriteObject =
storage.objects().rewrite(bucketName, objectName, bucketName, objectName, null);

// Now set the CSEK headers
final HttpHeaders httpHeaders = new HttpHeaders();

// Specify the exiting object's current CSEK.
httpHeaders.set("x-goog-copy-source-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-copy-source-encryption-key", originalBase64Key);
httpHeaders.set("x-goog-copy-source-encryption-key-sha256", originalBase64KeyHash);

// Specify the new CSEK that we would like to apply.
httpHeaders.set("x-goog-encryption-algorithm", "AES256");
httpHeaders.set("x-goog-encryption-key", newBase64Key);
httpHeaders.set("x-goog-encryption-key-sha256", newBase64KeyHash);

rewriteObject.setRequestHeaders(httpHeaders);

try {
RewriteResponse rewriteResponse = rewriteObject.execute();

// If an object is very large, you may need to continue making successive calls to
// rewrite until the operation completes.
while (!rewriteResponse.getDone()) {
System.out.println("Rewrite did not complete. Resuming...");
rewriteObject.setRewriteToken(rewriteResponse.getRewriteToken());
rewriteResponse = rewriteObject.execute();
}
} catch (GoogleJsonResponseException e) {
System.out.println("Error rotating key: " + e.getContent());
System.exit(1);
}
}

public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.out.println("\nPlease run this with one argument: "
+ "the GCS bucket into which this program should upload an object.\n\n"
+ "You can create a bucket using gsutil like this:\n\n\t"
+ "gsutil mb gs://name-of-bucket\n\n");
System.exit(1);
}
String bucketName = args[0];

Storage storage = StorageFactory.getService();
InputStream dataToUpload = new StorageUtils.ArbitrarilyLargeInputStream(10000000);

System.out.format("Uploading object gs://%s/%s using CSEK.\n", bucketName, OBJECT_NAME);
uploadObject(storage, bucketName, OBJECT_NAME, dataToUpload, CSEK_KEY, CSEK_KEY_HASH);

System.out.format("Downloading object gs://%s/%s using CSEK.\n", bucketName, OBJECT_NAME);
InputStream objectData =
downloadObject(storage, bucketName, OBJECT_NAME, CSEK_KEY, CSEK_KEY_HASH);
StorageUtils.readStream(objectData);

System.out.println("Rotating object to use a different CSEK.");
rotateKey(storage, bucketName, OBJECT_NAME, CSEK_KEY, CSEK_KEY_HASH,
ANOTHER_CESK_KEY, ANOTHER_CSEK_KEY_HASH);

System.out.println("Done");
}

}
57 changes: 57 additions & 0 deletions storage/json-api/src/main/java/StorageFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.storage.Storage;
import com.google.api.services.storage.StorageScopes;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collection;

/*
* Copyright (c) 2016 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.
*/

/**
* This class manages the details of creating a Storage service, including auth.
*/
public class StorageFactory {

private static Storage instance = null;

public static synchronized Storage getService() throws IOException, GeneralSecurityException {
if (instance == null) {
instance = buildService();
}
return instance;
}

private static Storage buildService() throws IOException, GeneralSecurityException {
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = GoogleCredential.getApplicationDefault(transport, jsonFactory);

if (credential.createScopedRequired()) {
Collection<String> bigqueryScopes = StorageScopes.all();
credential = credential.createScoped(bigqueryScopes);
}

return new Storage.Builder(transport, jsonFactory, credential)
.setApplicationName("GCS Samples")
.build();
}
}
Loading