com.rcythr.masq.keymanagement.KeyManager.java Source code

Java tutorial

Introduction

Here is the source code for com.rcythr.masq.keymanagement.KeyManager.java

Source

/**   This file is part of Masq.
    
Masq is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Masq is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with Masq.  If not, see <http://www.gnu.org/licenses/>.
**/

package com.rcythr.masq.keymanagement;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map.Entry;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.crypto.InvalidCipherTextException;

import com.rcythr.masq.SetupActivity;
import com.rcythr.masq.util.AES;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Environment;

/**
 * Singleton class used to store key data to the file system.
 * 
 * @author Richard Laughlin
 */
public class KeyManager {

    private static final String KEYSTORE = "KEYSTORE";
    private static final String PREFERENCES_NAME = "com.rcythr.masq";
    private static final String USE_INTERNAL_STORAGE = "useInternalStorage";
    private static final String PASSWORD_PROTECTED = "passwordProtected";
    private static final String SETUP_COMPLETE = "setupComplete";

    private HashMap<String, Key> lookup;
    private boolean setupComplete;
    private boolean internalStorage;
    private boolean passwordProtected;
    private byte[] keyStoreKey;

    private boolean storeLoaded = false;

    private static KeyManager instance = new KeyManager();

    public static KeyManager getInstance() {
        return instance;
    }

    /**
     * Create a new KeyManager from the information in the context
     */
    protected KeyManager() {
        lookup = new HashMap<String, Key>();
    }

    public void init(Activity context) throws InvalidCipherTextException, IOException {
        if (!storeLoaded) {
            SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
            internalStorage = preferences.getBoolean(USE_INTERNAL_STORAGE, true);
            passwordProtected = preferences.getBoolean(PASSWORD_PROTECTED, true);
            setupComplete = preferences.getBoolean(SETUP_COMPLETE, false);
            if (passwordProtected) {
                Intent intent = new Intent(context, SetupActivity.class);
                context.startActivity(intent);
            } else {
                load(context);
            }
        }
    }

    /**
     * @return a java.io.File to the keystore location on the sdcard
     */
    private File getSDCardFile() {

        //First make sure the directories exist
        File dir = new File(Environment.getExternalStorageDirectory(), "Android/data/com.rcythr.masq/files/");
        dir.mkdirs();

        //Return the actual location
        return new File(dir, KEYSTORE);

    }

    /**
     * Returns a file in stream for a keystore. It will use the settings loaded to determine where it is.
     * @param context the context to use
     * @return the opened FileInputStream 
     * 
     * @throws FileNotFoundException if the file is not found
     */
    private FileInputStream getAssociatedInFileStream(Context context) throws FileNotFoundException {
        if (internalStorage) {
            return context.openFileInput(KEYSTORE);
        } else {
            return new FileInputStream(getSDCardFile());
        }
    }

    /**
     * Returns a file out stream for a keystore. It will use the settings loaded to determine where it goes.
     * @param context the context to use
     * @return the opened FileInputStream 
     * 
     * @throws FileNotFoundException if the file is not found
     */
    private FileOutputStream getAssociatedOutFileStream(Context context) throws FileNotFoundException {
        if (internalStorage) {
            return context.openFileOutput(KEYSTORE, Context.MODE_PRIVATE);
        } else {
            return new FileOutputStream(getSDCardFile());
        }
    }

    /**
     * Load a Keystore. Uses either internal or external memory depending on settings.
     * @param context the context to use.
     * 
     * @throws IOException if the stream is bad
     * @throws InvalidCipherTextException if the key or data is bad
     */
    public void load(Context context) throws IOException, InvalidCipherTextException {

        DataInputStream stream = null;
        if (passwordProtected) {
            byte[] data = AES.handle(false, IOUtils.toByteArray(getAssociatedInFileStream(context)), keyStoreKey);
            stream = new DataInputStream(new ByteArrayInputStream(data));
        } else {
            stream = new DataInputStream(getAssociatedInFileStream(context));
        }

        String type = stream.readUTF();
        if (type.equals("RCYTHR1")) {
            //Valid
            int count = stream.readInt();
            for (int i = 0; i < count; ++i) {
                String name = stream.readUTF();
                lookup.put(name, Key.readData(stream));
            }
        } else {
            throw new IOException("Bad Format");
        }

        storeLoaded = true;
    }

    /**
     * Saves the Keystore. Uses either internal or external memory depending on settings.
     * @param context the context to use
     * 
     * @throws IOException if the outstream is somehow bad or interrupted
     * @throws InvalidCipherTextException if the key is bad or the data is bad
     */
    public void commit(Context context) throws IOException, InvalidCipherTextException {
        //Commit Preferences
        Editor edit = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE).edit();
        edit.putBoolean(USE_INTERNAL_STORAGE, internalStorage);
        edit.putBoolean(PASSWORD_PROTECTED, passwordProtected);
        edit.putBoolean(SETUP_COMPLETE, true);
        edit.commit();

        //Commit Key Data
        DataOutputStream writer = null;
        ByteArrayOutputStream output = null;

        //Create the proper streams
        if (passwordProtected) {
            output = new ByteArrayOutputStream();
            writer = new DataOutputStream(output);
        } else {
            writer = new DataOutputStream(getAssociatedOutFileStream(context));
        }

        //Write everything out to the stream
        writer.writeUTF("RCYTHR1"); //Special indicator to determine if we decrypt properly
        writer.writeInt(lookup.size());
        for (Entry<String, Key> entry : lookup.entrySet()) {
            writer.writeUTF(entry.getKey());
            entry.getValue().writeData(writer);
        }
        writer.writeByte(1); //Prevent null padding from causing too much truncation.

        writer.flush();

        //If we're password protecting we still need to encrypt and output to file
        if (passwordProtected) {
            OutputStream finalOut = getAssociatedOutFileStream(context);
            finalOut.write(AES.handle(true, output.toByteArray(), keyStoreKey));
            finalOut.close();
        }

        writer.close();
    }

    /**
     * Deletes the keystore from internal or external memory depending on settings
     * @param context the context to use
     */
    public void delete(Context context) {

        //Perform the delete.
        if (internalStorage) {
            context.deleteFile(KEYSTORE);
        } else {
            getSDCardFile().delete();
        }

        //Clear out shared preferences because they are not longer valid
        SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE);
        preferences.edit().clear().commit();
    }

    /**
     * Move Keystore from internal to external memory or vice versa depending on settings
     * @param context the context to use
     * @return true if successful, false otherwise
     */
    public boolean swap(Context context) {
        boolean initialState = internalStorage;
        try {
            //Toggle it
            internalStorage ^= true;

            //Create it all new
            commit(context);

            //Delete the old location
            if (!internalStorage) {
                context.deleteFile(KEYSTORE);
            } else {
                getSDCardFile().delete();
            }

        } catch (Exception e) {
            e.printStackTrace();
            internalStorage = initialState;
            return false;
        }
        return true;
    }

    /**
     * @return the lookup
     */
    public HashMap<String, Key> getLookup() {
        return lookup;
    }

    /**
     * @param lookup the lookup to set
     */
    public void setLookup(HashMap<String, Key> lookup) {
        this.lookup = lookup;
    }

    /**
     * @return the setupComplete
     */
    public boolean isSetupComplete() {
        return setupComplete;
    }

    /**
     * @param setupComplete the setupComplete to set
     */
    public void setSetupComplete(boolean setupComplete) {
        this.setupComplete = setupComplete;
    }

    /**
     * @return the internalStorage
     */
    public boolean isInternalStorage() {
        return internalStorage;
    }

    /**
     * @param internalStorage the internalStorage to set
     */
    public void setInternalStorage(boolean internalStorage) {
        this.internalStorage = internalStorage;
    }

    /**
     * @return the passwordProtected
     */
    public boolean isPasswordProtected() {
        return passwordProtected;
    }

    /**
     * @param passwordProtected the passwordProtected to set
     */
    public void setPasswordProtected(boolean passwordProtected) {
        this.passwordProtected = passwordProtected;
    }

    /**
     * @return the keyStoneKey
     */
    public byte[] getKeyStoneKey() {
        return keyStoreKey;
    }

    /**
     * @param keyStoneKey the keyStoneKey to set
     */
    public void setKeyStoreKey(byte[] keyStoneKey) {
        this.keyStoreKey = keyStoneKey;
    }

}