org.panbox.core.keymgmt.JDBCHelperNonRevokeable.java Source code

Java tutorial

Introduction

Here is the source code for org.panbox.core.keymgmt.JDBCHelperNonRevokeable.java

Source

/*
 * 
 *               Panbox - encryption for cloud storage 
 *      Copyright (C) 2014-2015 by Fraunhofer SIT and Sirrix AG 
 *
 * This program 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.
 * 
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Additonally, third party code may be provided with notices and open source
 * licenses from communities and third parties that govern the use of those
 * portions, and any licenses granted hereunder do not alter any rights and
 * obligations you may have under such open source licenses, however, the
 * disclaimer of warranty and limitation of liability provisions of the GPLv3 
 * will apply to all the product.
 * 
 */
package org.panbox.core.keymgmt;

import java.io.File;
import java.security.PublicKey;
import java.security.SignatureException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Properties;
import java.util.TreeMap;
import java.util.UUID;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.log4j.Logger;
import org.panbox.core.Utils;
import org.panbox.core.crypto.CryptCore;
import org.panbox.core.crypto.KeyConstants;
import org.panbox.core.crypto.Signable;
import org.panbox.core.crypto.SignatureHelper;
import org.panbox.core.exception.DeviceListException;
import org.panbox.core.exception.InitializaionException;
import org.panbox.core.exception.PersistanceException;
import org.sqlite.SQLiteErrorCode;

public class JDBCHelperNonRevokeable implements DBHelper {

    private static final Logger logger = Logger.getLogger(JDBCHelperNonRevokeable.class);

    private final String dbURL;

    private static final Properties p = new Properties();

    static {
        p.setProperty("journal_mode", "MEMORY");
    }

    public JDBCHelperNonRevokeable(String sqLitepath) {
        this.dbURL = sqLitepath;
    }

    @Override
    public void init(ShareMetaData smd) throws InitializaionException, SignatureException, DeviceListException {
        load(smd);
    }

    private void loadSPLValues(Connection con, ShareMetaData smd)
            throws SQLException, InitializaionException, SignatureException {
        loadID(con, smd);
        loadAlgorithms(con, smd);
        loadSharePaticipants(con, smd);
    }

    private void loadAlgorithms(Connection con, ShareMetaData smd) throws SQLException, InitializaionException {
        smd.publicKeyAlgorithm = loadMetadata(con, KEY_PK_ALGO);
        if (smd.publicKeyAlgorithm == null) {
            smd.publicKeyAlgorithm = KeyConstants.ASYMMETRIC_ALGORITHM;
        }
        smd.shareKeyAlgorithm = loadMetadata(con, KEY_SK_ALGO);
        if (smd.shareKeyAlgorithm == null) {
            smd.shareKeyAlgorithm = KeyConstants.SYMMETRIC_ALGORITHM;
        }
        smd.obfuscationKeyAlgorithm = loadMetadata(con, KEY_OK_ALGO);
        if (smd.obfuscationKeyAlgorithm == null) {
            smd.obfuscationKeyAlgorithm = KeyConstants.SYMMETRIC_ALGORITHM;
        }
    }

    private void loadSharePaticipants(Connection con, ShareMetaData smd) throws SQLException, SignatureException {

        smd.shareParticipants = new SharePartList();

        // Load Publickeys of participants
        PreparedStatement s = con.prepareStatement(QUERY_SPL);
        ResultSet rs = s.executeQuery();
        while (rs.next()) {
            PublicKey key = CryptCore.createPublicKeyFromBytes(rs.getBytes(COL_PUB_KEY));
            smd.shareParticipants.add(key, rs.getString(COL_ALIAS));
        }
        rs.close();

        // Load and verify signature
        s = con.prepareStatement(QUERY_SIGNATURE);
        rs = s.executeQuery();
        while (rs.next()) {
            byte[] signature = rs.getBytes(COL_SIGNATURE);
            boolean verified = false;
            try {
                verified = SignatureHelper.verify(smd.shareParticipants, signature, smd.ownerPubSigKey);
                smd.shareParticipants.setSignature(signature);
            } catch (Exception e) {
            }
            if (!verified) {
                throw new SignatureException("ShareParticipantList signature could not be verified!");
            }
        }
        rs.close();
        s.close();
    }

    private void loadID(Connection con, ShareMetaData smd) throws SQLException, InitializaionException {
        smd.id = UUID.fromString(loadMetadata(con, KEY_UUID));
    }

    protected String loadMetadata(Connection con, String key) throws SQLException, InitializaionException {
        String result = null;
        PreparedStatement s = con.prepareStatement(QUERY_METADATA);
        s.setString(1, key);

        ResultSet rs = s.executeQuery();
        if (rs.next()) {
            result = rs.getString(COL_VALUE);
        } else {
            throw new InitializaionException("Could not find " + key + " in database! DB might be corrupt.");
        }
        rs.close();
        return result;
    }

    protected void initSPL(ShareMetaData smd) throws SQLException, InitializaionException, SignatureException {
        Connection con = DriverManager.getConnection(dbURL + Volume.SPL_FILE, p);
        try {
            Statement s = con.createStatement();
            ResultSet rs = s.executeQuery(SPL_HAS_TABLES);
            if (rs.next() && SPL_NUM_TABLES == rs.getInt(1)) {
                logger.debug("Tables exist, loading values...");
                loadSPLValues(con, smd);
            } else {
                logger.debug("new Volume, creating tables...");
                createSPLTables(s, smd);
            }
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    logger.warn("Could not close DataBase Connection", e);
                }
            }
        }
    }

    private void createSPLTables(Statement s, ShareMetaData smd) throws SQLException {
        // Create generall metadata table
        s.executeUpdate("drop table if exists " + TABLE_METADATA + ";");
        s.executeUpdate("create table " + TABLE_METADATA + " (" + COL_KEY + " string, " + COL_VALUE + " string);");

        s.executeUpdate(
                "insert into " + TABLE_METADATA + " values('" + KEY_UUID + "', '" + smd.id.toString() + "')");
        // Set database version: this can be used to determine whether a
        // migration of data is needed
        s.executeUpdate("insert into " + TABLE_METADATA + " values('" + KEY_DB_VERSION + "', '"
                + Integer.toString(DB_VERSION) + "')");

        // Create shareparticipants table
        s.executeUpdate("drop table if exists " + TABLE_SHARE_PARTICIPANTS + ";");
        s.executeUpdate(CREATE_SPL);

        // Create signatures table
        s.executeUpdate("drop table if exists " + TABLE_SIGNATURES + ";");
        s.executeUpdate("create table " + TABLE_SIGNATURES + " (" + COL_SIGNATURE + " blob);");

    }

    @Override
    public void load(ShareMetaData smd) throws SignatureException, InitializaionException, DeviceListException {
        try {
            initSPL(smd);
            initDeviceLists(smd);
        } catch (SQLException e) {
            throw new InitializaionException("Could not load Share Meta Data", e);
        }
    }

    private void loadShareKeys(Connection con, ShareMetaData smd) throws SQLException, SignatureException {
        if (smd.shareKeys == null) {
            smd.shareKeys = new ShareKeyDB();
        }

        PreparedStatement s = con.prepareStatement(QUERY_SHARE_KEYS);
        ResultSet rs = s.executeQuery();
        while (rs.next()) {
            PublicKey key = CryptCore.createPublicKeyFromBytes(rs.getBytes(COL_DEV_PUB_KEY));
            int keyID = rs.getInt(COL_KEY_ID);
            byte[] encKey = rs.getBytes(COL_ENC_KEY);
            ShareKeyDBEntry entry = smd.shareKeys.getEntry(keyID);
            if (entry == null) {
                logger.debug("Creating new ShareKeyEntry: " + keyID);
                entry = new ShareKeyDBEntry(smd.shareKeyAlgorithm, keyID);
                smd.shareKeys.add(keyID, entry);
            }
            logger.debug("Adding ShareKey to entry " + keyID + " for key " + DigestUtils.md2Hex(key.getEncoded()));
            entry.addEncryptedKey(encKey, key);
            logger.debug("Number of keys in entry: " + entry.size());
        }
        rs.close();
    }

    private void loadObfuscationKeys(Connection con, ShareMetaData smd) throws SQLException, SignatureException {
        if (smd.obfuscationKeys == null) {
            smd.obfuscationKeys = new ObfuscationKeyDB();
        }
        PreparedStatement s = con.prepareStatement(QUERY_OBFUSCATION_KEYS);
        ResultSet rs = s.executeQuery();
        while (rs.next()) {
            PublicKey key = CryptCore.createPublicKeyFromBytes(rs.getBytes(COL_DEV_PUB_KEY));
            byte[] encKey = rs.getBytes(COL_ENC_KEY);
            smd.obfuscationKeys.add(key, encKey);
        }
        rs.close();
    }

    protected void loadKeyValues(Connection con, ShareMetaData smd)
            throws SQLException, InitializaionException, SignatureException {
        loadShareKeys(con, smd);
        loadObfuscationKeys(con, smd);
    }

    private void loadDeviceList(Connection con, ShareMetaData smd, PublicKey masterPubKey)
            throws SQLException, SignatureException, NumberFormatException, InitializaionException {

        DeviceList list = smd.deviceLists.get(masterPubKey);
        if (list == null) {
            list = new DeviceList(masterPubKey, null);
            smd.deviceLists.put(masterPubKey, list);
        }

        PreparedStatement s = con.prepareStatement(QUERY_DEVICE_LIST);
        ResultSet rs = s.executeQuery();
        while (rs.next()) {
            String device = rs.getString(COL_DEV_ALIAS);
            logger.debug("Loading device: " + device);
            PublicKey devicePubKey = CryptCore.createPublicKeyFromBytes(rs.getBytes(COL_DEV_PUB_KEY));
            list.addDevice(device, devicePubKey);
        }
        rs.close();
        s.close();

        loadKeyValues(con, smd);

        loadDeviceListSignature(con, smd, masterPubKey, list);
    }

    protected byte[] getDeviceListSignature(Connection con) throws SQLException, SignatureException {
        PreparedStatement s = con.prepareStatement(QUERY_SIGNATURE);
        ResultSet rs = s.executeQuery();
        if (rs.next()) {
            byte[] result = rs.getBytes(COL_SIGNATURE);
            if (rs.next()) {
                logger.error("More than one device list signature found");
                throw new SignatureException("More than one device list signature found");
            }
            rs.close();
            s.close();
            return result;
        } else {
            rs.close();
            s.close();
            // throw new
            // SignatureException("No signature found for device list");
            return null;
        }
    }

    private void loadDeviceListSignature(Connection con, ShareMetaData smd, PublicKey masterPubKey, DeviceList list)
            throws SQLException, SignatureException {
        // Check Signature
        if (list != null) {
            byte[] signature = getDeviceListSignature(con);
            list.setSignature(signature);
        }
    }

    private void verifyDeviceList(ShareMetaData smd, PublicKey masterPubKey, DeviceList list)
            throws SignatureException {
        Collection<PublicKey> pKeys = list.getPublicKeys();
        byte[] signature = list.getSignature();
        if (signature == null) {
            logger.fatal("No signature for devicelist found");
            throw new SignatureException("No signature for devicelist found");
        }
        boolean verified = false;
        try {
            // Either signed by the device list owner or by the
            // shareOwner
            Signable sKeys = smd.shareKeys.get(pKeys);
            Signable obKeys = smd.obfuscationKeys.get(pKeys);

            verified = SignatureHelper.verify(signature, masterPubKey, list, sKeys, obKeys);
            if (!verified) {
                verified = SignatureHelper.verify(signature, smd.ownerPubSigKey, list, sKeys, obKeys);
            }
        } catch (Exception e) {
            throw new SignatureException("Could not verify signature", e);
        }
        if (!verified) {
            logger.fatal("Could not verify devicelist");
            throw new SignatureException("Could not verify devicelist");
        }
    }

    private void createDLTables(Statement s) throws SQLException {

        // Create generall metadata table, for timestamp
        s.executeUpdate("drop table if exists " + TABLE_METADATA + ";");
        s.executeUpdate("create table " + TABLE_METADATA + " (" + COL_KEY + " string, " + COL_VALUE + " string);");

        // Create shareparticipants table
        s.executeUpdate("drop table if exists " + TABLE_DEVICE_LIST + ";");
        s.executeUpdate(CREATE_DEVICELIST);

        // Create sharekeys table
        s.executeUpdate("drop table if exists " + TABLE_SHARE_KEYS + ";");
        s.executeUpdate(CREATE_SHAREKEYS);

        // Create obfuscationkeys table
        s.executeUpdate("drop table if exists " + TABLE_OBFUSCATION_KEYS + ";");
        s.executeUpdate(CREATE_OBKEYS);

        // Create signatures table
        s.executeUpdate("drop table if exists " + TABLE_SIGNATURES + ";");
        s.executeUpdate("create table " + TABLE_SIGNATURES + " (" + COL_SIGNATURE + " blob);");

    }

    private void initDeviceLists(ShareMetaData smd)
            throws SQLException, InitializaionException, SignatureException, DeviceListException {

        smd.deviceLists = new TreeMap<PublicKey, DeviceList>(Utils.PK_COMPARATOR);

        Connection con = null;
        SharePartList spl = smd.getSharePartList();
        if (spl != null) {
            Iterator<String> it = spl.getAliases();

            while (it.hasNext()) {
                String alias = (String) it.next();
                PublicKey pKey = spl.getPublicKey(alias);
                String fingerprint = DigestUtils.sha256Hex(pKey.getEncoded());
                String url = dbURL + fingerprint + ".db";
                try {
                    con = DriverManager.getConnection(url, p);
                    Statement s = con.createStatement();
                    ResultSet rs = s.executeQuery(DEVICELIST_HAS_TABLES);
                    if (rs.next() && DEVICELIST_NUM_TABLES == rs.getInt(1)) {
                        rs.close();
                        s.close();
                        logger.debug("Tables exist, loading devicelist for user " + alias);
                        loadDeviceList(con, smd, pKey);
                    } else {
                        rs.close();
                        logger.debug("new Volume, creating tables for devicelist for user " + alias);
                        createDLTables(s);
                        s.close();
                    }
                } catch (SQLException e) {
                    logger.error("Error reading device list DB", e);
                    SQLiteErrorCode code = SQLiteErrorCode.getErrorCode(e.getErrorCode());
                    if (code.equals(SQLiteErrorCode.SQLITE_NOTADB) || code.equals(SQLiteErrorCode.SQLITE_CORRUPT)) {
                        // TODO: corrupt DB, consider deleting .db file
                        logger.warn("DB was corrupt, URL: " + dbURL);
                    }
                    continue;
                } finally {
                    if (con != null) {
                        try {
                            con.close();
                        } catch (SQLException e) {
                            logger.warn("Could not close DataBase Connection", e);
                        }
                    }
                }
            }

            // check devicelist signatures
            it = spl.getAliases();
            LinkedList<PublicKey> corruptDeviceList = new LinkedList<>();
            while (it.hasNext()) {
                String alias = (String) it.next();
                PublicKey pKey = spl.getPublicKey(alias);
                DeviceList list = smd.deviceLists.get(pKey);
                try {
                    verifyDeviceList(smd, pKey, list);
                } catch (Exception e) {
                    logger.warn("Could not verifiy device list of user" + alias, e);
                    corruptDeviceList.add(pKey);
                    if (list != null) {
                        for (PublicKey deviceKey : list.getPublicKeys()) {
                            smd.removeObfuscationKey(deviceKey);
                            smd.getShareKeys().removeDevice(deviceKey);
                        }
                    }
                }
            }
            if (!corruptDeviceList.isEmpty()) {
                throw new DeviceListException("Could not verify DeviceList(s)!", corruptDeviceList);
            }
        }
    }

    @Override
    public void store(DeviceList deviceList, ObfuscationKeyDB obKeys, ShareKeyDB shareKeys)
            throws PersistanceException {
        try {
            storeDeviceList(deviceList, obKeys, shareKeys);
        } catch (Exception e) {
            throw new PersistanceException("Could not store DeviceList", e);
        }
    }

    private void storeDeviceList(DeviceList deviceList, ObfuscationKeyDB obKeys, ShareKeyDB shareKeys)
            throws SQLException, PersistanceException {
        Connection con = null;
        PublicKey pKey = deviceList.getMasterSignatureKey();
        String fingerprint = DigestUtils.sha256Hex(pKey.getEncoded());
        String url = dbURL + fingerprint + ".db";
        try {
            con = DriverManager.getConnection(url, p);
            storeDeviceList(con, deviceList, pKey);
            Collection<PublicKey> pKeys = deviceList.getPublicKeys();
            logger.debug("User " + DigestUtils.md2Hex(deviceList.getMasterSignatureKey().getEncoded()) + " has "
                    + pKeys.size() + " devices");
            storeKeys(shareKeys.get(pKeys), obKeys.get(pKeys), con);
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    logger.warn("Could not close DataBase Connection", e);
                }
            }
        }
    }

    protected void storeKeys(ShareKeyDB shareKeys, ObfuscationKeyDB obKeys, Connection con) throws SQLException {
        // Store ShareKeys
        PreparedStatement insert = con.prepareStatement(INSERT_SHAREKEYS);
        Iterator<Integer> entries = shareKeys.getKeyIterator();
        while (entries.hasNext()) {
            int version = entries.next();
            ShareKeyDBEntry entry = shareKeys.getEntry(version);

            insert.setInt(1, version);

            Iterator<PublicKey> keys = entry.getKeyIterator();
            while (keys.hasNext()) {
                PublicKey pKey = (PublicKey) keys.next();
                byte[] encKey = entry.getEncryptedKey(pKey);
                // Store id, time, pkey enckey;
                insert.setBytes(2, pKey.getEncoded());
                insert.setBytes(3, encKey);
                int count = insert.executeUpdate();
                logger.debug("Inserted " + count + " sharekey");
            }
        }
        insert.close();

        // Store ObKeys
        insert = con.prepareStatement(INSERT_OBFUSCATIONKEYS);
        Iterator<PublicKey> keys = obKeys.getKeys();
        while (keys.hasNext()) {
            PublicKey pKey = (PublicKey) keys.next();
            byte[] encKey = obKeys.get(pKey);
            insert.setBytes(1, pKey.getEncoded());
            insert.setBytes(2, encKey);
            int count = insert.executeUpdate();
            logger.debug("Inserted " + count + " obkey");
            insert.clearParameters();
        }
        insert.close();
    }

    private void storeDeviceList(Connection con, DeviceList deviceList, PublicKey pKey)
            throws SQLException, PersistanceException {

        Statement s = con.createStatement();
        try {
            createDLTables(s);
        } catch (SQLException e) {
            logger.error("Could not create DeviceList tables", e);
        } finally {
            s.close();
        }

        PreparedStatement insert = con.prepareStatement(INSERT_DEVICE_LIST);

        Iterator<String> it = deviceList.getAliasIterator();
        while (it.hasNext()) {
            String devAlias = it.next();
            PublicKey devPubKey = deviceList.getPublicKey(devAlias);
            final byte[] encodedPubKey = devPubKey.getEncoded();
            insert.setString(1, devAlias);
            insert.setBytes(2, encodedPubKey);
            int i = insert.executeUpdate();
            logger.debug("Inserted " + i + " rows of devicelist");
        }

        storeSignature(con, deviceList.getSignature());
        if (insert != null) {
            try {
                insert.close();
            } catch (Exception e) {
                logger.warn("Could not close Statement", e);
            }
        }
    }

    private void storeSignature(Connection con, byte[] signature) throws SQLException, PersistanceException {
        PreparedStatement insert = con.prepareStatement(INSERT_SIGNATURE);
        insert.setBytes(1, signature);
        insert.executeUpdate();
        if (insert != null) {
            try {
                insert.close();
            } catch (Exception e) {
                logger.warn("Could not close Statement", e);
            }
        }
    }

    @Override
    public void storeSPL(ShareMetaData smd) throws PersistanceException {

        Connection con = null;
        try {
            con = DriverManager.getConnection(dbURL + Volume.SPL_FILE, p);
            Statement s = con.createStatement();
            createSPLTables(s, smd);
            s.close();

            storeID(con, smd);
            storeAlgorithms(con, smd);
            storeSharePaticipants(con, smd);
        } catch (SQLException e) {
            throw new PersistanceException("Error while trying to store share participants to db", e);
        } finally {
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    logger.warn("Could not close DataBase Connection", e);
                }
            }
        }
    }

    private void storeSharePaticipants(Connection con, ShareMetaData smd)
            throws SQLException, PersistanceException {
        Iterator<String> it = smd.shareParticipants.getAliases();

        Statement s = con.createStatement();
        s.executeUpdate(DROP_SPL);
        s.executeUpdate(CREATE_SPL);
        s.close();
        PreparedStatement insert = con.prepareStatement(INSERT_SPL);
        while (it.hasNext()) {
            String alias = it.next();
            PublicKey pKey = smd.shareParticipants.getPublicKey(alias);
            insert.setString(1, alias);
            insert.setBytes(2, pKey.getEncoded());
            int i = insert.executeUpdate();
            logger.debug("Inserted " + i + " rows of shareparticipants");
            insert.clearParameters();
        }
        if (insert != null) {
            try {
                insert.close();
            } catch (Exception e) {
                logger.warn("Could not close Statement", e);
            }
        }
        storeSignature(con, smd.shareParticipants.getSignature());
    }

    private void storeMetadata(Connection con, String key, String value) throws SQLException, PersistanceException {
        PreparedStatement s = con.prepareStatement(UPDATE_METADATA);
        s.setString(1, value);
        s.setString(2, key);
        // Check update
        int rows = s.executeUpdate();
        if (rows == 0) {
            // Update unsuccessful because element was not in db, insert
            // instead
            s.close();
            s = con.prepareStatement(INSERT_METADATA);
            s.setString(1, key);
            s.setString(2, value);
            int i = s.executeUpdate();
            logger.debug("Inserted " + i + " rows of metadata");
            s.close();
        } else if (rows != 1) {
            throw new PersistanceException(
                    "Updating metadata entry " + key + " affected more than one row:" + rows);
        }
    }

    private void storeID(Connection con, ShareMetaData smd) throws SQLException, PersistanceException {
        storeMetadata(con, KEY_UUID, smd.id.toString());
    }

    private void storeAlgorithms(Connection con, ShareMetaData smd) throws SQLException, PersistanceException {
        storeMetadata(con, KEY_PK_ALGO, smd.publicKeyAlgorithm);
        storeMetadata(con, KEY_SK_ALGO, smd.shareKeyAlgorithm);
        storeMetadata(con, KEY_OK_ALGO, smd.obfuscationKeyAlgorithm);
    }

    @Override
    public boolean exists() {
        return new File(this.dbURL).exists();
    }

}