/* * * 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(); } }