com.saife.sample.S3Sample.java Source code

Java tutorial

Introduction

Here is the source code for com.saife.sample.S3Sample.java

Source

/*
 * Copyright (c) 2015 SAIFE 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, THE SOFTWARE
 * AND DOCUMENTATION ARE DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
 * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT.  REFER TO THE WRITTEN AGREEMENT FOR SPECIFIC
 * LANGUAGE GOVERNING PERMISSIONS AND LIMITATIONS.
 *
 *
 */

package com.saife.sample;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
import java.util.concurrent.ScheduledThreadPoolExecutor;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.profile.ProfileCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ListObjectsRequest;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.google.gson.Gson;
import com.saife.Saife;
import com.saife.SaifeFactory;
import com.saife.crypto.internal.InvalidCredentialException;
import com.saife.dar.NetworkShare;
import com.saife.dar.NetworkShareDoesNotExistException;
import com.saife.dar.NetworkShareExistsException;
import com.saife.dar.NetworkShareManager;
import com.saife.dar.PersistedObject;
import com.saife.dar.PersistentStore;
import com.saife.logging.LogSink.LogLevel;
import com.saife.logging.LogSinkFactory;
import com.saife.logging.LogSinkManager;
import com.saife.management.CertificationSigningRequest;
import com.saife.management.DistinguishedName;
import com.saife.management.InvalidManagementStateException;
import com.saife.management.ManagementService.ManagementState;

/**
 * The S3Sample. Uses SAIFE NetworkShare and Amazon S3 as a back end.
 */
public class S3Sample {

    /** The bucketName. */
    static String bucketName;

    /** The s3. */
    static AmazonS3 s3;

    /** The saifeWorker. */
    static S3Sample saifeWorker;

    /**
     * SAIFE definitions
     */
    static Persister blackDataHandler;

    /** The network share. */
    static NetworkShare ns;

    /**
     * A pointer to the SAIFE interface
     */
    static Saife saife;

    /** The default path where all persisted SAIFE data is written. */
    static final String defaultKeyStore = ".SaifeStore";

    /**
     * The default password to unlock the SAIFE private key. In practice a user is always prompted for this input.
     */
    static final String defaultPassword = "mysecret";

    /** The string used to identify the type of a message . */
    static final String echoMsgType = "com.saife.demo.echo";

    /** The saifeThreadPool. */
    static ScheduledThreadPoolExecutor saifeThreadPool;

    /** The saife_updated. */
    static boolean saifeUpdated = false;

    /**
     * The AnObject, This class derives PersistedObjects to track the local objects and easily relate them to the
     * PersistedObjects on the other side of the JNI interface.
     */
    public class AnObject implements PersistedObject {

        /** The objName. The persisted object needs a name to return in function getName */
        String objName;

        /** The objectData. */
        ByteArrayOutputStream objectData;

        /**
         * The constructor.
         *
         * @param aName is set here, since it is convenient
         */
        public AnObject(final String aName) {
            objName = aName;
            objectData = new ByteArrayOutputStream();
        }

        @Override
        public String getName() {
            return objName;
        }

        /**
         * @return true if data has been stored in this object
         */
        public Boolean isUploadable() {
            return (objectData.size() > 0);
        }

        /**
         * @return the byte array where data is stored
         */
        public ByteArrayOutputStream getStream() {
            return objectData;
        }

        /**
         * Writes the file out to S3
         */
        public void writeOut() {
            System.out.println("writeOut: file " + getName() + ".");

            final File file = getFile();
            if (null != file) {
                s3.putObject(new PutObjectRequest(bucketName, getName(), file));
                file.delete();
            } else {
                System.out.println("writeOut: Uploadable persistent object without a file.");
            }
            objectData.reset();

        }

        /**
         * S3 only uploads entire files, so the easiest thing is to create a temp file and let Amazon handle it. (The temp
         * file is encrypted and secure, just like the data stored in S3.)
         * 
         * @return File. Deleted after use.
         */
        @SuppressWarnings("resource")
        public File getFile() {
            File file;
            try {
                file = File.createTempFile(getName(), ".bin");
            } catch (final IOException e) {
                System.out.println("getFile: could not create temp file.");
                e.printStackTrace();
                return null;
            }

            OutputStream os = null;
            try {
                os = new FileOutputStream(file);
            } catch (final FileNotFoundException e) {
                System.out.println("getFile: could not create an output stream.");
                file.delete();
                e.printStackTrace();
                return null;
            }
            try {
                objectData.writeTo(os);
            } catch (final IOException e) {
                System.out.println("getFile: could not write to temp file.");
                e.printStackTrace();
                try {
                    os.close();
                } catch (final IOException e1) {
                    // do nothing
                }
                file.delete();
                return null;
            }
            return file;
        }

    }

    /** The workingObj. Tracks the last used persisted object. */
    AnObject workingObj = null;

    /**
     * The saifeUpdater. The SAIFE library needs to updated successfully at least once. The thread can go away after that.
     */
    public class saifeUpdater implements Runnable {

        @Override
        public void run() {
            while (!saifeUpdated) {
                try {
                    saife.updateSaifeData();
                    saifeUpdated = true;
                } catch (final InvalidManagementStateException e) {
                    System.out.println("saifeUpdater: InvalidManagementStateException.");
                } catch (final IOException e) {
                    System.out.println("saifeUpdater: IOException.");
                }
                try {
                    Thread.sleep(10000);
                } catch (final InterruptedException e) {
                    // do nothing
                }
            }
        }
    }

    /**
     * The Persister. The PersistentStore manages the storage and retrieval of black data on behalf of the NetworkShare
     * (Check SAIFE documentation for specifics)
     */
    public class Persister implements PersistentStore {

        /** The objects. */
        List<AnObject> objects;

        /**
         * The constructor.
         */
        public Persister() {
            // objects can be empty, but should not be null.
            objects = new Vector<AnObject>();
        }

        /**
         * Locate an S3 object by its key and return an associated object.
         * 
         * @param a_key The S3 storage name
         * @return AnObject a local view of an object.
         */
        private AnObject findObject(final String a_key) {

            final ObjectListing objectListing = s3
                    .listObjects(new ListObjectsRequest().withBucketName(bucketName).withPrefix(""));

            for (final S3ObjectSummary objectSummary : objectListing.getObjectSummaries()) {

                if (objectSummary.getKey().equals(a_key)) {
                    System.out.println("findObject Found " + objectSummary.getKey());

                    workingObj = new AnObject(a_key);
                    objects.add(workingObj);

                    return workingObj;
                }
            }

            System.out.println("findObject: No match for " + a_key + " in "
                    + objectListing.getObjectSummaries().size() + " objects.");
            return null;
        }

        @Override
        public List<PersistedObject> getObjects(final String storagePath, final String prefix) throws IOException {
            final List<PersistedObject> returnObjects = new Vector<PersistedObject>();

            returnObjects.clear();

            final ObjectListing objectListing = s3
                    .listObjects(new ListObjectsRequest().withBucketName(bucketName).withPrefix(prefix));
            System.out.println("getObjects found " + objectListing.getObjectSummaries().size() + " object(s)");

            for (final S3ObjectSummary objectSummary : objectListing.getObjectSummaries()) {
                System.out.println("-> " + objectSummary.getKey());

                // Pass objects to SAIFE but keep a local object too.
                final AnObject newObj = new AnObject(objectSummary.getKey());
                objects.add(newObj);
                returnObjects.add(newObj);
            }
            return returnObjects;
        }

        @Override
        public void releaseObjects(final List<PersistedObject> releaseObjects) {
            for (final PersistedObject po : releaseObjects) {

                AnObject localO = null;

                // see if we have this object hanging around.
                for (final AnObject tmp : objects) {
                    if (tmp.getName().equals(po.getName())) {
                        localO = tmp;
                        break;
                    }
                }
                // This object is in our list.
                if (null != localO) {

                    if (localO.isUploadable()) {
                        // data has been written to this object that is not stored. Write it out to S3.
                        final File file = localO.getFile();
                        if (null != file) {
                            System.out.println("releaseObjects: Writting a file upon its release by SAIFE.");
                            s3.putObject(new PutObjectRequest(bucketName, localO.getName(), file));
                            file.delete();
                        }
                    }

                    objects.remove(localO);
                }
            }
        }

        @SuppressWarnings("resource")
        @Override
        public InputStream getInputStream(final PersistedObject object) throws IOException {

            final S3Object s3object = s3.getObject(new GetObjectRequest(bucketName, object.getName()));

            // This must be closed via the NS
            return s3object.getObjectContent();
        }

        @SuppressWarnings("resource")
        @Override
        public InputStream getInputStream(final String storagePath, final String name) throws IOException {

            // ignoring storage path in this example everything is flat.
            final S3Object s3object = s3.getObject(new GetObjectRequest(bucketName, name));

            // This must be closed via the NS
            return s3object.getObjectContent();
        }

        @Override
        public void releaseInputStream(final InputStream is) {
            try {
                is.close();
            } catch (final IOException e) {
                System.out.println("NOTE: releaseInputStream ignored an IOException");
            }
        }

        @Override
        public OutputStream getOutputStream(final PersistedObject object) throws IOException {

            // libsaife callbacks will return PersistedObjects, not the expanded AnObject class we defined
            for (final AnObject tmp : this.objects) {
                if (tmp.getName().equals(object.getName())) {

                    System.out.println("Found local object " + tmp.getName() + " returning local stream.");
                    return tmp.getStream();

                }
            }
            return null;
        }

        @Override
        public OutputStream getOutputStream(final String storagePath, final String name) throws IOException {
            final AnObject obj = findObject(name);
            if (null != obj) {
                final ByteArrayOutputStream stream = obj.getStream();
                return stream;
            }

            System.out.println("creating new object for output stream at path " + storagePath + ".");
            workingObj = new AnObject(name);
            return workingObj.getStream();
        }

        @Override
        public void releaseOutputStream(final OutputStream os) {
            try {
                os.close();
            } catch (final IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void deleteObject(final PersistedObject object) throws IOException {

        }

        @Override
        public void deleteObject(final String storagePath, final String name) throws IOException {

        }
    }

    /**
     * runNS() Starts a NetworkShare and uses it to upload/download encrypted content.
     */
    @SuppressWarnings("resource")
    public void runNS() {

        // SAIFE should be initialized by now. Make sure update completes before continuing.
        final Thread t = new Thread(new saifeUpdater());
        t.start();

        while (!saifeUpdated) {
            try {
                System.out.println("Waiting for SAIFE update.");
                Thread.sleep(5000);
            } catch (final InterruptedException e) {

            }
        }

        // Unlock SAIFE library with user's credential
        try {
            saife.unlock(defaultPassword);
        } catch (final InvalidCredentialException e1) {
            e1.printStackTrace();
        } catch (final InvalidManagementStateException e1) {
            e1.printStackTrace();
        }

        // Start a PersistentStore so the network share can do its reads and writes
        blackDataHandler = this.new Persister();

        //
        // This creates a network share manager.
        //
        final NetworkShareManager mgr = new NetworkShareManager(saife);

        //
        // This will load a NetworkShare, including the network share keys. In this example,
        // blackDataHandler is used by the network share to interact with S3. If a share with
        // the given bucketname does not exist, getNetowrkShare will throw an exception
        //
        try {
            ns = mgr.getNetworkShare(bucketName, "/", blackDataHandler);
        } catch (final IOException e1) {
            System.out.println("getNetworkShare IO exception!");
            return;
        } catch (final NetworkShareDoesNotExistException e1) {
            System.out.println("NetworkShareDoesNotExistException.  Creating NetworkShare.");
            try {
                //
                // This will create a new NetworkShare, network share keys are created and encrypted. blackDataHandler
                // is used to save the resulting encrypted meta data to S3.
                //
                ns = mgr.createNetworkShare(bucketName, "/", blackDataHandler);
            } catch (final IOException e) {
                System.out.println("CreateNetworkShare IOException");
                e.printStackTrace();
                return;
            } catch (final NetworkShareExistsException e) {
                System.out.println("Unrecoverable NetworkShareExistsException, since GetNetworkShare also failed.");
                return;
            }
        }

        /*
         * SAIFE has set up the network share, now it can be used to encrypt and decrypt content
         */
        System.out.println("Creating an encrypted file");
        final String aStorageName = "anyStringWillSuffice";
        final AnObject newObj = new AnObject(aStorageName);

        OutputStream new_os = null;
        try {
            new_os = ns.getEncryptStream(newObj.getStream());
        } catch (final IOException e) {
            System.out.println("getEncryptStream fails to get a stream for output.");
            e.printStackTrace();
            return;
        }

        if (null == new_os) {
            System.out.println("getEncryptStream returns null without a throw."); // should be impossible
            return;
        }

        createSampleFile(new_os); // closes new_os
        newObj.writeOut(); // saves the encrypted data out to S3

        InputStream new_is = null;
        System.out.println("Creating an encrypted file");
        try {
            new_is = ns.getDecryptStream(blackDataHandler.getInputStream(bucketName, aStorageName));
        } catch (final IOException e) {
            System.out.println("getInputStream failed.");
            e.printStackTrace();
            return;
        }

        try {
            System.out.println("decrypted content follows:");
            displayTextInputStream(new_is);
        } catch (final IOException e) {
            System.out.println("Failed to deliver decrypted content.");
            e.printStackTrace();
        }

        return;

    }

    /**
     * The constructor of this example class.
     */
    public S3Sample() {

        // The consoleSink logger, logs to console.
        final LogSinkManager logMgr = LogSinkFactory.constructConsoleSinkManager();

        // Create instance of SAIFE. A log manager may be optionally specified to redirect SAIFE logging.
        saife = SaifeFactory.constructSaife(logMgr);

        // Set SAIFE logging level
        saife.setSaifeLogLevel(LogLevel.SAIFE_LOG_WARNING);
    }

    /**
     * @param args main ignores command line input
     */
    public static void main(final String[] args) {

        saifeWorker = new S3Sample();

        /**
         * SAIFE initialization
         */
        try {
            final ManagementState state = saife.initialize(defaultKeyStore);
            if (state == ManagementState.UNKEYED) {
                // The UNKEYED state is returned when SAIFE doesn't have a public/private key pair.

                // Setup the DN attributes to be used in the X509 certificate.
                final DistinguishedName dn = new DistinguishedName("SaifeEcho");

                // Generate the public/private key pair and certificate signing request.
                final CertificationSigningRequest csr = saife.generateSmCsr(dn, defaultPassword);

                // Add additional capabilities to the SAIFE capabilities list that convey the application specific capabilities.
                final List<String> capabilities = csr.getCapabilities();
                capabilities.add("com::saife::demo::ns");

                // Provide CSR and capabilities (JSON string) to user for provisioning.
                // The application must restart from the UNKEYED state.
                final PrintWriter f = new PrintWriter(defaultKeyStore + "/newkey.smcsr");
                f.println("CSR: " + csr.getEncodedCsr());
                final Gson gson = new Gson();
                f.println("CAPS: " + gson.toJson(capabilities));
                f.close();

                System.out.println("SAIFE generated " + defaultKeyStore
                        + "/newkey.smcsr which contains a certificate and capabilities to provision at the SAIFE dashboard.");
                return;
            }

            System.out.println("SAIFE has abeen initialized correctly.");
            saifeWorker.initS3();

            saifeWorker.runNS();

            System.out.println("GoodBye.");

        } catch (final InvalidManagementStateException e) {
            System.out.println("SAIFE entered an invalid or unrecoverable state.");
        } catch (final FileNotFoundException e) {
            System.out.println("File Not Found (smcsr)");
            e.printStackTrace();
        } catch (final InvalidCredentialException e) {
            System.out.println("Invalid credentials");
            e.printStackTrace();
        }

    }

    /**
     * 
     */
    private void initS3() {
        // S3 credential identity
        final String me = "john.curtis@saife-tiprnet";

        AWSCredentials credentials = null;
        try {
            credentials = new ProfileCredentialsProvider(me).getCredentials();
        } catch (final Exception e) {
            throw new AmazonClientException("Cannot load the credentials from the credential profiles file. "
                    + "Please make sure that your credentials file is at the correct "
                    + "location (/home/builder/.aws/credentials), and is in valid format.", e);
        }

        s3 = new AmazonS3Client(credentials);
        final Region usWest2 = Region.getRegion(Regions.US_WEST_2);
        s3.setRegion(usWest2);

        // define a bucket name
        bucketName = null;

        try {

            /*
             * List the buckets in your account
             */
            for (final Bucket bucket : s3.listBuckets()) {
                if (bucket.getName().startsWith("saife-test-bucket")) {
                    bucketName = bucket.getName();
                    System.out.println("Found Test Bucket:" + bucket.getName());
                }
            }

            /*
             * Create a globally unique bucket name if needed.
             */
            if (null == bucketName) {
                bucketName = "saife-test-bucket" + UUID.randomUUID();
                System.out.println("Creating bucket " + bucketName + "\n");
                s3.createBucket(bucketName);
            }
        } catch (final AmazonServiceException ase) {
            System.out.println("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon S3, but was rejected with an error response for some reason.");
            System.out.println("Error Message:    " + ase.getMessage());
            System.out.println("HTTP Status Code: " + ase.getStatusCode());
            System.out.println("AWS Error Code:   " + ase.getErrorCode());
            System.out.println("Error Type:       " + ase.getErrorType());
            System.out.println("Request ID:       " + ase.getRequestId());
        } catch (final AmazonClientException ace) {
            System.out.println("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with S3, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message: " + ace.getMessage());
        }

        System.out.println("S3 services are enabled.");
    }

    /**
     * writes sample text data to a stream
     * 
     * @param os the stream to write
     */
    private static void createSampleFile(final OutputStream os) {

        final Writer writer = new OutputStreamWriter(os);

        try {
            writer.write("        ,-----------,       \n");
            writer.write("       /|          /|       \n");
            writer.write("      / |         / |       \n");
            writer.write("     +-----------+  |       \n");
            writer.write("     |  +-----+  |  |       \n");
            writer.write("     |  | - o |__|__|       \n");
            writer.write("     | /|  -  |  |  /       \n");
            writer.write("     |/ +-----+  | /        \n");
            writer.write("     +-----------+'         \n");
            writer.close();

            os.close();
        } catch (final IOException e) {
            System.out.println("write failure Message: ");
            e.printStackTrace();
        }
        return;
    }

    /**
     * Displays the contents of the specified input stream as text.
     *
     * @param input The input stream to display as text.
     * @throws IOException thrown upon IO failure
     */
    private static void displayTextInputStream(final InputStream input) throws IOException {
        final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        while (true) {
            final String line = reader.readLine();
            if (line == null)
                break;

            System.out.println("    " + line);
        }
        System.out.println();
    }

}