Return-Path: X-Original-To: apmail-db-derby-commits-archive@www.apache.org Delivered-To: apmail-db-derby-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 31D2DD383 for ; Thu, 4 Oct 2012 11:31:52 +0000 (UTC) Received: (qmail 78177 invoked by uid 500); 4 Oct 2012 11:31:50 -0000 Delivered-To: apmail-db-derby-commits-archive@db.apache.org Received: (qmail 78001 invoked by uid 500); 4 Oct 2012 11:31:44 -0000 Mailing-List: contact derby-commits-help@db.apache.org; run by ezmlm Precedence: bulk list-help: list-unsubscribe: List-Post: Reply-To: "Derby Development" List-Id: Delivered-To: mailing list derby-commits@db.apache.org Received: (qmail 77941 invoked by uid 99); 4 Oct 2012 11:31:42 -0000 Received: from athena.apache.org (HELO athena.apache.org) (140.211.11.136) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 04 Oct 2012 11:31:42 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 04 Oct 2012 11:31:39 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id C207D23889D7; Thu, 4 Oct 2012 11:30:55 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1393993 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/reference/ engine/org/apache/derby/iapi/store/raw/ engine/org/apache/derby/iapi/store/raw/data/ engine/org/apache/derby/impl/jdbc/ engine/org/apache/derby/impl/store/raw/... Date: Thu, 04 Oct 2012 11:30:55 -0000 To: derby-commits@db.apache.org From: kristwaa@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20121004113055.C207D23889D7@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: kristwaa Date: Thu Oct 4 11:30:54 2012 New Revision: 1393993 URL: http://svn.apache.org/viewvc?rev=1393993&view=rev Log: DERBY-5792: Make it possible to turn off encryption on an already encrypted database. Added the new connection attribute "decryptDatabase", which will decrypt an encrypted database given the correct boot password or key if set to true. The request is ignored if the database has already been booted. If authentication and authorization are both enabled, only the DBO is allowed to decrypt the database. Enabled the DecryptDatabaseTest, and added new tests to DboPowersTest and CryptoCrashRecoveryTest. A few high-level comments: o EmbedConnection Deals with connection attributes and DBO powers. o RawStore Deals with the meat of the feature; configuring cryptograhic operations (based on connection attributes and service properties), modifying service.properties, and extra crash recovery logic. o RAFContainer This is where we skip encrypting the data pages when writing the containers, effectively decrypting the database. Patch file: derby-5792-3a-decryption_feature.diff, derby-5792-4b-crash_and_dbo.diff Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Attribute.java db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/RawStoreFactory.java db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/data/DataFactory.java db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DboPowersTest.java db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ErrorCodeTest.java db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/CryptoCrashRecoveryTest.java db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Attribute.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Attribute.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Attribute.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/reference/Attribute.java Thu Oct 4 11:30:54 2012 @@ -78,7 +78,11 @@ public interface Attribute { */ String JCC_PROTOCOL = "jdbc:derby:net:"; - + /** + * Attribute name for decrypting an encrypted database. + */ + String DECRYPT_DATABASE = "decryptDatabase"; + /** Attribute name to encrypt the database on disk. If set to true, all user data is stored encrypted on disk. Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/RawStoreFactory.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/RawStoreFactory.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/RawStoreFactory.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/RawStoreFactory.java Thu Oct 4 11:30:54 2012 @@ -30,15 +30,11 @@ import org.apache.derby.iapi.services.pr import org.apache.derby.iapi.store.access.TransactionInfo; import org.apache.derby.iapi.store.raw.xact.TransactionFactory; -import org.apache.derby.iapi.store.raw.log.LogInstant; import org.apache.derby.iapi.error.StandardException; -import org.apache.derby.catalog.UUID; import org.apache.derby.iapi.store.access.DatabaseInstant; -import org.apache.derby.iapi.error.ExceptionSeverity; import java.util.Properties; import java.io.Serializable; -import java.io.File; /** RawStoreFactory implements a single unit of transactional @@ -112,6 +108,9 @@ public interface RawStoreFactory extends /** Derby Store Minor Version (4) **/ public static final int DERBY_STORE_MINOR_VERSION_4 = 4; + /** Derby Store Minor Version (10) **/ + public static final int DERBY_STORE_MINOR_VERSION_10 = 10; + /** Derby 10 Store Major version */ public static final int DERBY_STORE_MAJOR_VERSION_10 = 10; @@ -358,11 +357,13 @@ public interface RawStoreFactory extends "OldEncryptedBootPassword"; - /* - * Following property is used to track the status of the (re)encryption, - * required to bring the database back to state it was before the - * (re) encryption started, id (re) encryption of the database - * is aborted. + /** + * Tracks the status of any database-wide cryptographic operations. + *

+ * The relevant operations are encryption, re-encryption and decryption. + * THe property is required to be able to bring the database back to state + * it was in before the cryptographic operation started in case the + * transformation of the database is aborted. */ public static final String DB_ENCRYPTION_STATUS = "derby.storage.databaseEncryptionStatus"; Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/data/DataFactory.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/data/DataFactory.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/data/DataFactory.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/store/raw/data/DataFactory.java Thu Oct 4 11:30:54 2012 @@ -284,6 +284,15 @@ public interface DataFactory extends Cor throws StandardException ; /** + * Decrypts all the containers in the data segment. + * + * @param t the transaction that is decrypting the container + * @exception StandardException Standard Derby Error Policy + */ + void decryptAllContainers(RawTransaction t) + throws StandardException; + + /** * Encrypt all the containers in the data segment. * @param t the transaction that is encrypting the containers. * @exception StandardException Standard Derby Error Policy Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/impl/jdbc/EmbedConnection.java Thu Oct 4 11:30:54 2012 @@ -261,8 +261,7 @@ public class EmbedConnection implements // upgrade, we need a plain boot, then authenticate, then, if all // is well, boot with (re)encryption or upgrade. Encryption at // create time is not checked. - boolean isTwoPhaseEncryptionBoot = (!createBoot && - isEncryptionBoot(info)); + boolean isTwoPhaseCryptoBoot = (!createBoot && isCryptoBoot(info)); boolean isTwoPhaseUpgradeBoot = (!createBoot && isHardUpgradeBoot(info)); boolean isStartSlaveBoot = isStartReplicationSlaveBoot(info); @@ -281,6 +280,10 @@ public class EmbedConnection implements SQLState.CONFLICTING_BOOT_ATTRIBUTES, Attribute.SHUTDOWN_ATTR + ", " + Attribute.DROP_ATTR); } + // Don't allow conflicting attributes wrt cryptographic operations. + if (isTwoPhaseCryptoBoot) { + checkConflictingCryptoAttributes(info); + } // check that a replication operation is not combined with // other operations @@ -289,7 +292,7 @@ public class EmbedConnection implements if (createBoot || shutdown || dropDatabase || - isTwoPhaseEncryptionBoot || + isTwoPhaseCryptoBoot || isTwoPhaseUpgradeBoot) { throw StandardException. newException(SQLState. @@ -369,12 +372,12 @@ public class EmbedConnection implements { // database already booted by someone else tr.setDatabase(database); - isTwoPhaseEncryptionBoot = false; + isTwoPhaseCryptoBoot = false; isTwoPhaseUpgradeBoot = false; } else if (!shutdown) { - if (isTwoPhaseEncryptionBoot || isTwoPhaseUpgradeBoot) { + if (isTwoPhaseCryptoBoot || isTwoPhaseUpgradeBoot) { savedInfo = info; info = removePhaseTwoProps((Properties)info.clone()); } @@ -464,7 +467,7 @@ public class EmbedConnection implements } } - if (isTwoPhaseEncryptionBoot || + if (isTwoPhaseCryptoBoot || isTwoPhaseUpgradeBoot || isStartSlaveBoot) { @@ -475,8 +478,12 @@ public class EmbedConnection implements if (!usingNoneAuth && getLanguageConnection().usesSqlAuthorization()) { int operation; - if (isTwoPhaseEncryptionBoot) { - operation = OP_ENCRYPT; + if (isTwoPhaseCryptoBoot) { + if (isTrue(savedInfo, Attribute.DECRYPT_DATABASE)) { + operation = OP_DECRYPT; + } else { + operation = OP_ENCRYPT; + } } else if (isTwoPhaseUpgradeBoot) { operation = OP_HARD_UPGRADE; } else { @@ -484,8 +491,8 @@ public class EmbedConnection implements } try { // a failure here leaves database booted, but no - // (re)encryption has taken place and the connection is - // rejected. + // restricted operations have taken place and the + // connection is rejected. checkIsDBOwner(operation); } catch (SQLException sqle) { if (isStartSlaveBoot) { @@ -709,7 +716,7 @@ public class EmbedConnection implements // combination with createFrom/restoreFrom/rollForwardRecoveryFrom // attributes. Re-encryption is not // allowed when restoring from backup. - if (restoreCount != 0 && isEncryptionBoot(p)) { + if (restoreCount != 0 && isCryptoBoot(p)) { throw newSQLException(SQLState.CONFLICTING_RESTORE_ATTRIBUTES); } @@ -755,16 +762,17 @@ public class EmbedConnection implements return isTrue(p, Attribute.DROP_ATTR); } - /** - * Examine boot properties and determine if a boot with the given - * attributes would entail an encryption operation. - * - * @param p the attribute set - * @return true if a boot will encrypt or re-encrypt the database - */ - private boolean isEncryptionBoot(Properties p) - { + /** + * Examines boot properties and determines if a boot with the given + * attributes would entail a cryptographic operation on the database. + * + * @param p the attribute set + * @return {@code true} if a boot will perform a cryptographic operation on + * the database. + */ + private boolean isCryptoBoot(Properties p) { return (isTrue(p, Attribute.DATA_ENCRYPTION) || + isTrue(p, Attribute.DECRYPT_DATABASE) || isSet(p, Attribute.NEW_BOOT_PASSWORD) || isSet(p, Attribute.NEW_CRYPTO_EXTERNAL_KEY)); } @@ -1111,6 +1119,7 @@ public class EmbedConnection implements private Properties removePhaseTwoProps(Properties p) { p.remove(Attribute.DATA_ENCRYPTION); + p.remove(Attribute.DECRYPT_DATABASE); p.remove(Attribute.NEW_BOOT_PASSWORD); p.remove(Attribute.NEW_CRYPTO_EXTERNAL_KEY); p.remove(Attribute.UPGRADE_ATTR); @@ -1361,6 +1370,7 @@ public class EmbedConnection implements private static final int OP_SHUTDOWN = 1; private static final int OP_HARD_UPGRADE = 2; private static final int OP_REPLICATION = 3; + private static final int OP_DECRYPT = 4; /** * Check if actual authenticationId is equal to the database owner's. * @@ -1379,6 +1389,9 @@ public class EmbedConnection implements case OP_ENCRYPT: throw newSQLException(SQLState.AUTH_ENCRYPT_NOT_DB_OWNER, actualId, tr.getDBName()); + case OP_DECRYPT: + throw newSQLException(SQLState.AUTH_DECRYPT_NOT_DB_OWNER, + actualId, tr.getDBName()); case OP_SHUTDOWN: throw newSQLException(SQLState.AUTH_SHUTDOWN_NOT_DB_OWNER, actualId, tr.getDBName()); @@ -3642,4 +3655,31 @@ public class EmbedConnection implements } } + /** + * Examines the boot properties looking for conflicting cryptographic + * options and commands. + * + * @param p boot properties (for instance URL connection attributes) + * @throws SQLException if conflicting crypto attributes are detected + */ + private void checkConflictingCryptoAttributes(Properties p) + throws SQLException { + // Since we cannot detect whether the database is actually encrypted + // at this point we let the store handle attempts to both encrypt and + // decrypt at the same time (see RawStore). + boolean appearsEncrypted = isSet(p, Attribute.CRYPTO_EXTERNAL_KEY) || + isSet(p, Attribute.BOOT_PASSWORD); + if (appearsEncrypted && isTrue(p, Attribute.DECRYPT_DATABASE)) { + if (isSet(p, Attribute.NEW_BOOT_PASSWORD)) { + throw newSQLException(SQLState.CONFLICTING_BOOT_ATTRIBUTES, + Attribute.DECRYPT_DATABASE + ", " + + Attribute.NEW_BOOT_PASSWORD); + } + if (isSet(p, Attribute.NEW_CRYPTO_EXTERNAL_KEY)) { + throw newSQLException(SQLState.CONFLICTING_BOOT_ATTRIBUTES, + Attribute.DECRYPT_DATABASE + ", " + + Attribute.NEW_CRYPTO_EXTERNAL_KEY); + } + } + } } Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/RawStore.java Thu Oct 4 11:30:54 2012 @@ -297,7 +297,7 @@ public final class RawStore implements R if(properties.getProperty( RawStoreFactory.DB_ENCRYPTION_STATUS) !=null) { - handleIncompleteDatabaseEncryption(properties); + handleIncompleteDbCryptoOperation(properties); } transformExistingData = setupEncryptionEngines(create, properties); @@ -1275,6 +1275,8 @@ public final class RawStore implements R Properties properties) throws StandardException { + // Check if user has requested to decrypt the database. + boolean decryptDatabase = isTrue(properties, Attribute.DECRYPT_DATABASE); // Check if user has requested to encrypt the database. boolean encryptDatabase = isTrue(properties, Attribute.DATA_ENCRYPTION); boolean reEncrypt = false; @@ -1300,9 +1302,16 @@ public final class RawStore implements R if (isEncryptedDatabase) { // Check if the user has requested to re-encrypt an // encrypted datbase with a new encryption password/key. + // See also attribute check in EmbedConnection. reEncrypt = isSet(properties, Attribute.NEW_BOOT_PASSWORD) || isSet(properties, Attribute.NEW_CRYPTO_EXTERNAL_KEY); encryptDatabase = reEncrypt; + } else if (encryptDatabase && decryptDatabase) { + // We cannot both encrypt and decrypt at the same time. + throw StandardException.newException( + SQLState.CONFLICTING_BOOT_ATTRIBUTES, + Attribute.DECRYPT_DATABASE + ", " + + Attribute.DATA_ENCRYPTION); } // NOTE: if user specifies Attribute.DATA_ENCRYPTION on the @@ -1321,6 +1330,12 @@ public final class RawStore implements R } } } + // Prevent attempt to decrypt a read-only database. + if (decryptDatabase && isReadOnly()) { + throw StandardException.newException( + SQLState.DATABASE_DECRYPTION_DENIED, + "read-only"); + } } // setup encryption engines. @@ -1427,7 +1442,10 @@ public final class RawStore implements R isEncryptedDatabase = true; } } - return (!create && encryptDatabase); + // We need to transform existing data if we are (re-)encrypting an + // existing database, or decrypting an already encrypted database. + return (!create && + (encryptDatabase || (isEncryptedDatabase && decryptDatabase))); } /** @@ -1621,11 +1639,19 @@ public final class RawStore implements R CipherFactory newCipherFactory) throws StandardException { + boolean decryptDatabase = (isEncryptedDatabase && + isTrue(properties, Attribute.DECRYPT_DATABASE)); + boolean reEncrypt = (isEncryptedDatabase && ( + isSet(properties, Attribute.NEW_BOOT_PASSWORD) || + isSet(properties, Attribute.NEW_CRYPTO_EXTERNAL_KEY))); + if (SanityManager.DEBUG) { + SanityManager.ASSERT(decryptDatabase || reEncrypt || ( + !isEncryptedDatabase && + isSet(properties, Attribute.DATA_ENCRYPTION))); + } - boolean reEncrypt = isEncryptedDatabase; - - // check if the database can be encrypted. - canEncryptDatabase(reEncrypt); + // Check if the cryptographic operation can be performed. + cryptoOperationAllowed(reEncrypt, decryptDatabase); boolean externalKeyEncryption = isSet(properties, Attribute.CRYPTO_EXTERNAL_KEY); @@ -1645,7 +1671,11 @@ public final class RawStore implements R try { - dataFactory.encryptAllContainers(transaction); + if (decryptDatabase) { + dataFactory.decryptAllContainers(transaction); + } else { + dataFactory.encryptAllContainers(transaction); + } // all the containers are (re) encrypted, now mark the database as // encrypted if a plain database is getting configured for encryption @@ -1667,24 +1697,29 @@ public final class RawStore implements R logFactory.checkpoint(this, dataFactory, xactFactory, true); } - // let the log factory know that database is - // (re) encrypted and ask it to flush the log, - // before enabling encryption of the log with - // the new key. - logFactory.setDatabaseEncrypted(true, true); - - // let the log factory and data factory know that + // Let the log factory and data factory know whether the // database is encrypted. - if (!reEncrypt) { - // mark in the raw store that the database is - // encrypted. - isEncryptedDatabase = true; - dataFactory.setDatabaseEncrypted(true); + if (decryptDatabase) { + isEncryptedDatabase = false; + logFactory.setDatabaseEncrypted(false, true); + dataFactory.setDatabaseEncrypted(false); } else { - // switch the encryption/decryption engine to the new ones. - decryptionEngine = newDecryptionEngine; - encryptionEngine = newEncryptionEngine; - currentCipherFactory = newCipherFactory; + // Let the log factory know that database is + // (re-)encrypted and ask it to flush the log + // before enabling encryption of the log with + // the new key. + logFactory.setDatabaseEncrypted(true, true); + + if (reEncrypt) { + // Switch the encryption/decryption engine to the new ones. + decryptionEngine = newDecryptionEngine; + encryptionEngine = newEncryptionEngine; + currentCipherFactory = newCipherFactory; + } else { + // Mark in the raw store that the database is encrypted. + isEncryptedDatabase = true; + dataFactory.setDatabaseEncrypted(true); + } } @@ -1749,8 +1784,15 @@ public final class RawStore implements R properties.put(RawStoreFactory.OLD_ENCRYPTED_KEY, keyString); } - } else - { + } else if (decryptDatabase) { + // We cannot remove the encryption properties here, as we may + // have to revert back to the encrypted database. Instead we set + // dataEncryption to false and leave all other encryption + // attributes unchanged. This requires that Derby doesn't store + // dataEncryption=false for un-encrypted database, otherwise + // handleIncompleteDbCryptoOperation will be confused. + properties.put(Attribute.DATA_ENCRYPTION, "false"); + } else { // save the encryption block size; properties.put(RawStoreFactory.ENCRYPTION_BLOCKSIZE, String.valueOf(encryptionBlockSize)); @@ -1796,8 +1838,10 @@ public final class RawStore implements R // remove the old version of the container files. dataFactory.removeOldVersionOfContainers(false); - if (reEncrypt) - { + if (decryptDatabase) { + // By now we can remove all cryptographic properties. + removeCryptoProperties(properties); + } else if (reEncrypt) { if (externalKeyEncryption) { // remove the saved copy of the verify.key file @@ -1824,12 +1868,15 @@ public final class RawStore implements R transaction.close(); } catch (StandardException se) { - - throw StandardException.newException( - (reEncrypt ? SQLState.DATABASE_REENCRYPTION_FAILED : - SQLState.DATABASE_ENCRYPTION_FAILED), - se, - se.getMessage()); + String sqlState; + if (decryptDatabase) { + sqlState = SQLState.DATABASE_DECRYPTION_FAILED; + } else if (reEncrypt) { + sqlState = SQLState.DATABASE_REENCRYPTION_FAILED; + } else { + sqlState = SQLState.DATABASE_ENCRYPTION_FAILED; + } + throw StandardException.newException(sqlState, se, se.getMessage()); } finally { // clear the new encryption engines. newDecryptionEngine = null; @@ -1856,7 +1903,7 @@ public final class RawStore implements R * @exception StandardException Standard Derby Error Policy * */ - public void handleIncompleteDatabaseEncryption(Properties properties) + public void handleIncompleteDbCryptoOperation(Properties properties) throws StandardException { // find what was the encryption status before database crashed. @@ -1867,6 +1914,9 @@ public final class RawStore implements R dbEncryptionStatus = Integer.parseInt(dbEncryptionStatusStr); boolean reEncryption = false; + boolean decryptionFailed = + isSet(properties, Attribute.DATA_ENCRYPTION) && + !isTrue(properties, Attribute.DATA_ENCRYPTION); // check if engine crashed when (re) encryption was in progress. if (dbEncryptionStatus == RawStoreFactory.DB_ENCRYPTION_IN_PROGRESS) { @@ -1948,8 +1998,7 @@ public final class RawStore implements R // only incase of re-encryption there should // be old verify key file. reEncryption = true; - }else - { + } else if (!decryptionFailed) { // remove the verify key file. if (!privDelete(verifyKeyFile)) throw StandardException.newException( @@ -1979,31 +2028,19 @@ public final class RawStore implements R } } - if (!reEncryption) { - // crash occured when database was getting reconfigured - // for encryption , all encryption properties should be - // removed from service.properties - - // common props for external key or password. - properties.remove(Attribute.DATA_ENCRYPTION); - properties.remove(RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION); - properties.remove(RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION); - properties.remove(RawStoreFactory.ENCRYPTION_BLOCKSIZE); - - // properties specific to password based encryption. - properties.remove(Attribute.CRYPTO_KEY_LENGTH); - properties.remove(Attribute.CRYPTO_PROVIDER); - properties.remove(Attribute.CRYPTO_ALGORITHM); - properties.remove(RawStoreFactory.ENCRYPTED_KEY); - - } - if (SanityManager.DEBUG) { + SanityManager.ASSERT(!(decryptionFailed && reEncryption)); crashOnDebugFlag( TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY, reEncryption); } + if (!decryptionFailed && !reEncryption) { + // Crash occurred when an un-encrypted database was being + // encrypted, all encryption properties should be removed from + // service.properties since we are undoing the attempt. + removeCryptoProperties(properties); + } } // end of UNDO @@ -2041,6 +2078,19 @@ public final class RawStore implements R properties.remove(RawStoreFactory.OLD_ENCRYPTED_KEY); } + // Finalize cleanup for failed decryption attempts. + if (decryptionFailed) { + if (dbEncryptionStatus == RawStoreFactory.DB_ENCRYPTION_IN_UNDO) { + // This action is not idempotent in the sense that once set + // Derby can't detect that what failed was a decryption attempt + // if DB_ENCRYPTION_STATUS is kept unchanged. Too reduce the + // window of opportunity this is done here, but should really + // be atomic with the removal of DB_ENCRYPTION_STATUS. + properties.setProperty(Attribute.DATA_ENCRYPTION, "true"); + } else { + removeCryptoProperties(properties); + } + } // remove the re-encryptin status flag. properties.remove(RawStoreFactory.DB_ENCRYPTION_STATUS); } @@ -2051,28 +2101,41 @@ public final class RawStore implements R /** * checks if the database is in the right state to (re)encrypt it. * - * @param reEncrypt true if the database getting encrypted - * with new password/key. + * @param reEncrypt {@code true} if the database is getting encrypted with + * a new password/key + * @param decrypt {@code true} if the database is getting decrypted * @exception StandardException * if there is global transaction in the prepared state or - * if the database is not at the version 10.2 or above, this - * feature is not supported or + * if the database is not at the required version or above, + * this feature is not supported or * if the log is archived for the database. */ - private void canEncryptDatabase(boolean reEncrypt) + private void cryptoOperationAllowed(boolean reEncrypt, boolean decrypt) throws StandardException { - String feature = (reEncrypt ? - "newBootPassword/newEncryptionKey attribute" : - "dataEncryption attribute on an existing database"); - - // check if the database version is at 10.2 or above. - // encrytpion or re-encryption of the database - // is supported only in version 10.2 or above. - logFactory.checkVersion( + String feature; + if (decrypt) { + feature = Attribute.DECRYPT_DATABASE + " attribute"; + } else if (reEncrypt) { + feature = Attribute.NEW_BOOT_PASSWORD + "/" + + Attribute.NEW_CRYPTO_EXTERNAL_KEY + " attribute"; + } else { + feature = Attribute.DATA_ENCRYPTION + + " attribute on an existing database"; + } + + // Check if database version is sufficient for the requested feature. + // Encryption or re-encryption of the database is supported only in + // version 10.2 or above, decryption is only supported in version + // 10.10 or above. + int requiredMinorVersion = decrypt ? + RawStoreFactory.DERBY_STORE_MINOR_VERSION_10 : + RawStoreFactory.DERBY_STORE_MINOR_VERSION_2; + + logFactory.checkVersion( RawStoreFactory.DERBY_STORE_MAJOR_VERSION_10, - RawStoreFactory.DERBY_STORE_MINOR_VERSION_2, + requiredMinorVersion, feature); // database can not be (re)encrypted if there @@ -2082,12 +2145,17 @@ public final class RawStore implements R // be read once database is reconfigure with new encryption // key. if (xactFactory.hasPreparedXact()) { - if(reEncrypt) + if (decrypt) { + throw StandardException.newException( + SQLState.DATABASE_DECRYPTION_DENIED, + "prepared global transaction"); + } else if (reEncrypt) { throw StandardException.newException( SQLState.REENCRYPTION_PREPARED_XACT_EXIST); - else + } else { throw StandardException.newException( SQLState.ENCRYPTION_PREPARED_XACT_EXIST); + } } @@ -2099,15 +2167,18 @@ public final class RawStore implements R // have some logs encrypted with new key and some with old key // when rollforward recovery is performed. - if (logFactory.logArchived()) - { - if(reEncrypt) + if (logFactory.logArchived()) { + if (decrypt) { + throw StandardException.newException( + SQLState.DATABASE_DECRYPTION_DENIED, + "log archived"); + } else if (reEncrypt) { throw StandardException.newException( SQLState.CANNOT_REENCRYPT_LOG_ARCHIVED_DATABASE); - else + } else { throw StandardException.newException( SQLState.CANNOT_ENCRYPT_LOG_ARCHIVED_DATABASE); - + } } } @@ -2264,6 +2335,24 @@ public final class RawStore implements R requiredMajorVersion, requiredMinorVersion, feature)); } + /** + * Removes properties related to encrypted databases. + * + * @param properties property set to remove from + */ + private void removeCryptoProperties(Properties properties) { + // Common props for external key or password. + properties.remove(Attribute.DATA_ENCRYPTION); + properties.remove(RawStoreFactory.LOG_ENCRYPT_ALGORITHM_VERSION); + properties.remove(RawStoreFactory.DATA_ENCRYPT_ALGORITHM_VERSION); + properties.remove(RawStoreFactory.ENCRYPTION_BLOCKSIZE); + + // Properties specific to password based encryption. + properties.remove(Attribute.CRYPTO_KEY_LENGTH); + properties.remove(Attribute.CRYPTO_PROVIDER); + properties.remove(Attribute.CRYPTO_ALGORITHM); + properties.remove(RawStoreFactory.ENCRYPTED_KEY); + } /* These methods require Priv Blocks when run under a security manager. Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/BaseDataFileFactory.java Thu Oct 4 11:30:54 2012 @@ -2099,8 +2099,14 @@ public class BaseDataFileFactory ciphertext, offset, length, cleartext, outputOffset); } - + /** {@inheritDoc} */ + public void decryptAllContainers(RawTransaction t) + throws StandardException { + containerEncrypter = new EncryptOrDecryptData(this); + containerEncrypter.decryptAllContainers(t); + } + /** {@inheritDoc} */ public void encryptAllContainers(RawTransaction t) throws StandardException { containerEncrypter = new EncryptOrDecryptData(this); Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/impl/store/raw/data/RAFContainer.java Thu Oct 4 11:30:54 2012 @@ -322,12 +322,15 @@ class RAFContainer extends FileContainer } /** - * Update the page array with container header if the page is a first alloc - * page and encrypt the page data if the database is encrypted. + * Updates the page array with container header if the page is a first + * allocation page and encrypts the page data if the database is encrypted. + * * @param pageNumber the page number of the page * @param pageData byte array that has the actual page data. - * @param encryptionBuf buffer that is used to store encryted version of the - * page. + * @param encryptionBuf buffer that is used to store encrypted version of + * the page, or {@code null} if encryption is to be skipped + * @param encryptWithNewEngine whether to use the new encryption engine for + * encryption (only considered if {@code encryptionBuf != null}) * @return byte array of the the page data as it should be on the disk. */ protected byte[] updatePageArray(long pageNumber, @@ -358,7 +361,8 @@ class RAFContainer extends FileContainer } else { - if (dataFactory.databaseEncrypted() || encryptWithNewEngine) + if (encryptionBuf != null && + (dataFactory.databaseEncrypted() || encryptWithNewEngine)) { return encryptPage(pageData, pageSize, @@ -1209,17 +1213,18 @@ class RAFContainer extends FileContainer * Creates encrypted or decrypted version of the container. * * Reads all the pages of the container from the original container - * through the page cache, then either encrypts each page data with the new - * encryption mechanism or decrypts the page data, and finally writes the - * data to the specified new container file. + * through the page cache, then either encrypts page data with the new + * encryption mechanism or leaves the page data un-encrypted, and finally + * writes the data to the specified new container file. *

* The encryption and decryption engines used to carry out the * cryptographic operation(s) are configured through the raw store, and - * accessed via the data factory. + * accessed via the data factory. Note that the pages have already been + * decrypted before being put into the page cache. * * @param handle the container handle * @param newFilePath file to store the new version of the container in - * @param doEncrypt tells whether to encrypt or decrypt + * @param doEncrypt tells whether to encrypt or not * @exception StandardException Derby Standard error policy */ protected void encryptOrDecryptContainer(BaseContainerHandle handle, @@ -1227,10 +1232,6 @@ class RAFContainer extends FileContainer boolean doEncrypt) throws StandardException { - // TEMPORARY FOR DERBY-5792 - if (!doEncrypt) { - throw new UnsupportedOperationException("not yet implemented"); - } BasePage page = null; StorageFile newFile = dataFactory.getStorageFactory().newStorageFile(newFilePath); @@ -1241,19 +1242,21 @@ class RAFContainer extends FileContainer newRaf = privGetRandomAccessFile(newFile); byte[] encryptionBuf = null; - encryptionBuf = new byte[pageSize]; + if (doEncrypt) { + encryptionBuf = new byte[pageSize]; + } - // copy all the pages from the current container to the - // new container file after encryting the pages. + // Copy all the pages from the current container to the new + // container file after processing the pages. for (long pageNumber = FIRST_ALLOC_PAGE_NUMBER; pageNumber <= lastPageNumber; pageNumber++) { page = getLatchedPage(handle, pageNumber); - // update the page array before writing to the disk - // with container header and encrypt it. - + // Update the page array before writing to the disk. + // An update consists of adding the container header, or + // (re-)encrypting the data. byte[] dataToWrite = updatePageArray(pageNumber, page.getPageArray(), encryptionBuf, @@ -1275,7 +1278,8 @@ class RAFContainer extends FileContainer SQLState.FILE_CONTAINER_EXCEPTION, ioe, getIdentity() != null ? getIdentity().toString() : "unknown", - "encrypt", newFilePath); + doEncrypt ? "encrypt" : "decrypt", + newFilePath); } finally { if (page != null) { @@ -1293,8 +1297,9 @@ class RAFContainer extends FileContainer SQLState.FILE_CONTAINER_EXCEPTION, ioe, getIdentity() != null ? getIdentity().toString() : "unknown", - "encrypt-close", newFilePath); - + doEncrypt ? + "encrypt-close" : "decrypt-close", + newFilePath); } } } Modified: db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml (original) +++ db/derby/code/trunk/java/engine/org/apache/derby/loc/messages.xml Thu Oct 4 11:30:54 2012 @@ -460,6 +460,13 @@ Guide. + 08004.C.14 + User '{0}' cannot decrypt database '{1}'. Only the database owner can perform this operation. + authorizationID + databaseName + + + 08006.C A network protocol error was encountered and the connection has been terminated: {0} error @@ -3803,6 +3810,17 @@ Guide. algorithmName + + XBCXX.S + Database decryption failed: {0} + failureMessage + + + + XBCXY.S + Cannot decrypt database because of the database state: {0} + databaseState + Modified: db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java (original) +++ db/derby/code/trunk/java/shared/org/apache/derby/shared/common/reference/SQLState.java Thu Oct 4 11:30:54 2012 @@ -249,6 +249,8 @@ public interface SQLState { String DATABASE_ENCRYPTION_FAILED = "XBCXU.S"; String DATABASE_REENCRYPTION_FAILED = "XBCXV.S"; String DIGEST_NO_SUCH_ALGORITHM = "XBCXW.S"; + String DATABASE_DECRYPTION_FAILED = "XBCXX.S"; + String DATABASE_DECRYPTION_DENIED = "XBCXY.S"; /* ** Cache Service @@ -1656,6 +1658,7 @@ public interface SQLState { String AUTH_DATABASE_CREATE_MISSING_PERMISSION = "08004.C.11"; String NET_CONNECT_SECMEC_INCOMPATIBLE_SCHEME = "08004.C.12"; String AUTH_EMPTY_CREDENTIALS = "08004.C.13"; + String AUTH_DECRYPT_NOT_DB_OWNER = "08004.C.14"; // There can be multiple causes for 08003, which according // to SQL2003 spec means "connection does not exist" Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DboPowersTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DboPowersTest.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DboPowersTest.java (original) +++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DboPowersTest.java Thu Oct 4 11:30:54 2012 @@ -63,6 +63,9 @@ public class DboPowersTest extends BaseJ "authentication", "authentication + sqlAuthorization"}; + final private static boolean ENCRYPT = true; + final private static boolean DECRYPT = false; + /** * Create a new instance of DboPowersTest (for shutdown test) * @@ -78,11 +81,12 @@ public class DboPowersTest extends BaseJ } /** - * Create a new instance of DboPowersTest (for encryption and hard - * upgrade tests). The database owner credentials is needed to - * always be able to perform the restricted operations (when they - * are not under test, but used as part of a test fixture for - * another operation). + * Creates a new instance of DboPowersTest for cryptographic operations + * and hard upgrade tests. + *

+ * The database owner credentials is needed to always be able to perform + * the restricted operations (when they are not under test, but used as + * part of a test fixture for another operation). * * @param name Fixture name * @param authLevel authentication level with which test is run @@ -116,18 +120,16 @@ public class DboPowersTest extends BaseJ TestConfiguration.clientServerDecorator( dboShutdownSuite("suite: shutdown powers, client"))); - /* Database (re)encryption powers - * - * The encryption power tests are not run for JSR169, since Derby - * does not support database encryption for that platform, cf. - * the specification for JSR169 support in DERBY-97. - */ + // Database (re)encryption powers and decryption powers. + // The cryptographic power tests are not run for JSR169, since Derby + // does not support database encryption for that platform, cf. + // the specification for JSR169 support in DERBY-97. if (!JDBC.vmSupportsJSR169()) { suite.addTest( - dboEncryptionSuite("suite: encryption powers, embedded")); + dboCryptoSuite("suite: cryptographic powers, embedded")); suite.addTest( TestConfiguration.clientServerDecorator( - dboEncryptionSuite("suite: encryption powers, client"))); + dboCryptoSuite("suite: cryptographic powers, client"))); } /* Database hard upgrade powers */ @@ -142,7 +144,7 @@ public class DboPowersTest extends BaseJ } /** - * Users used by both dboShutdownSuite and dboEncryptionSuite + * Users used by both dboShutdownSuite and dboCryptoSuite */ final static String[][] users = { /* authLevel == AUTHENTICATION: dbo is APP/APP for db 'wombat', @@ -315,16 +317,17 @@ public class DboPowersTest extends BaseJ /** * - * Construct suite of tests for database encryption action + * Constructs suite of tests for cryptographic actions, that is database + * encryption, re-encryption, and decryption. * * @param framework Derby framework name - * @return A suite containing the test case for encryption + * @return A suite containing the test cases for cryptographic operations * incarnated for the three security levels no authentication, * authentication, and authentication plus sqlAuthorization, The * latter two has an instance for dbo, and one for an ordinary user, * so there are in all five incarnations of tests. */ - private static Test dboEncryptionSuite(String framework) + private static Test dboCryptoSuite(String framework) { Test tests[] = new Test[SQLAUTHORIZATION+1]; // one per authLevel @@ -337,10 +340,10 @@ public class DboPowersTest extends BaseJ new TestSuite("suite: security level=" + secLevelNames[NOAUTHENTICATION]); - for (int tNo = 0; tNo < encryptionTests.length; tNo++) { + for (int tNo = 0; tNo < cryptoTests.length; tNo++) { noauthSuite.addTest( TestConfiguration.singleUseDatabaseDecoratorNoShutdown( - new DboPowersTest(encryptionTests[tNo], NOAUTHENTICATION, + new DboPowersTest(cryptoTests[tNo], NOAUTHENTICATION, "foo", "bar"))); } @@ -351,7 +354,7 @@ public class DboPowersTest extends BaseJ for (int autLev = AUTHENTICATION; autLev <= SQLAUTHORIZATION ; autLev++) { - tests[autLev] = wrapEncryptionUserTests(autLev); + tests[autLev] = wrapCryptoUserTests(autLev); } TestSuite suite = new TestSuite("dboPowers:"+framework); @@ -377,7 +380,7 @@ public class DboPowersTest extends BaseJ * @param autLev security context to use */ - private static Test wrapEncryptionUserTests(int autLev) + private static Test wrapCryptoUserTests(int autLev) { // add decorator for different users authenticated TestSuite usersSuite = @@ -388,9 +391,9 @@ public class DboPowersTest extends BaseJ // use of no teardown / no shutdown decorator variants: // Necessary since framework doesnt know bootPassword for (int userNo = 0; userNo < users.length; userNo++) { - for (int tNo = 0; tNo < encryptionTests.length; tNo++) { + for (int tNo = 0; tNo < cryptoTests.length; tNo++) { Test test = TestConfiguration.changeUserDecorator - (new DboPowersTest(encryptionTests[tNo], + (new DboPowersTest(cryptoTests[tNo], autLev, users[autLev-1][0], // dbo users[autLev-1][0].concat(pwSuffix)), @@ -412,9 +415,10 @@ public class DboPowersTest extends BaseJ } /** - * Enumerates the encryption tests + * Enumerates the cryptographic tests. */ - final static String[] encryptionTests = { "testEncrypt", "testReEncrypt" }; + final static String[] cryptoTests = { + "testEncrypt", "testReEncrypt", "testDecrypt"}; /** * Test database encryption for an already created @@ -446,12 +450,12 @@ public class DboPowersTest extends BaseJ JDBCDataSource.setBeanProperty(ds, "user", user); JDBCDataSource.setBeanProperty(ds, "password", password); - Connection con = null; + Connection con; try { con = ds.getConnection(); - vetEncryptionAttempt(user, null); + vetCryptoAttempt(ENCRYPT, user, null); } catch (SQLException e) { - vetEncryptionAttempt(user, e); + vetCryptoAttempt(ENCRYPT, user, e); bringDbDown(); return; } @@ -505,9 +509,9 @@ public class DboPowersTest extends BaseJ try { ds.getConnection(); - vetEncryptionAttempt(user, null); + vetCryptoAttempt(ENCRYPT, user, null); } catch (SQLException e) { - vetEncryptionAttempt(user, e); + vetCryptoAttempt(ENCRYPT, user, e); bringDbDown(); return; } @@ -518,6 +522,47 @@ public class DboPowersTest extends BaseJ bringDbDown(); } + /** + * Tests that only the DBO can decrypt a database. + */ + public void testDecrypt() + throws SQLException { + println("testDecrypt: auth=" + this._authLevel + + " user=" + getTestConfiguration().getUserName()); + + // make sure db is created + getConnection().close(); + + // shut down database in preparation for encryption + bringDbDown(); + String bootPassword = "conHippo08"; + doEncrypt(bootPassword); + // shut down database in preparation for decryption + bringDbDown(); + + String user = getTestConfiguration().getUserName(); + String password = getTestConfiguration().getUserPassword(); + DataSource ds = JDBCDataSource.getDataSource(); + JDBCDataSource.setBeanProperty(ds, "connectionAttributes", + "bootPassword=" + bootPassword + + ";decryptDatabase=true"); + JDBCDataSource.setBeanProperty(ds, "user", user); + JDBCDataSource.setBeanProperty(ds, "password", password); + + try { + ds.getConnection(); + vetCryptoAttempt(DECRYPT, user, null); + } catch (SQLException sqle) { + vetCryptoAttempt(DECRYPT, user, sqle); + return; + } finally { + bringDbDown(); + } + + // we managed to decrypt: bring db up again to verify + bringDbUp(null); + bringDbDown(); + } /** * Encrypt database, as owner (not testing encryption power here) @@ -556,10 +601,10 @@ public class DboPowersTest extends BaseJ /** - * Boot database back up after encryption using current user, - * should succeed + * Boots database back up after cryptographic operation using current user, + * should succeed. * - * @param bootPassword Boot using this bootPassword + * @param bootPassword boot using this bootPassword, may be {@code null} * @throws SQLException */ private void bringDbUp(String bootPassword) throws SQLException @@ -567,23 +612,26 @@ public class DboPowersTest extends BaseJ String user = getTestConfiguration().getUserName(); String password = getTestConfiguration().getUserPassword(); DataSource ds = JDBCDataSource.getDataSource(); - JDBCDataSource.setBeanProperty( - ds, "connectionAttributes", "bootPassword=" + bootPassword); + if (bootPassword != null) { + JDBCDataSource.setBeanProperty( + ds, "connectionAttributes", "bootPassword=" + bootPassword); + } JDBCDataSource.setBeanProperty(ds, "user", user); JDBCDataSource.setBeanProperty(ds, "password", password); ds.getConnection().close(); } /** - * Decide if the result of trying to (re)encrypt the database is - * compliant with the semantics introduced by DERBY-2264. + * Decides if the result of trying to (re-)encrypt or decrypt the database + * is compliant with the semantics introduced by DERBY-2264. * + * @param encrypt whether we are (re-)encrypting or decrypting * @param user The db user under which we tried to encrypt * @param e Exception caught during attempt, if any */ - private void vetEncryptionAttempt (String user, SQLException e) + private void vetCryptoAttempt(boolean encrypt, String user, SQLException e) { - vetAttempt(user, e, "08004", "(re)encryption"); + vetAttempt(user, e, "08004", encrypt ? "(re)encryption" : "decrypt"); } /** @@ -763,8 +811,10 @@ public class DboPowersTest extends BaseJ assertEquals(operation + ", SQL authorization, db owner", null, e); } else { - assertSQLState(operation +", SQL authorization, not db owner", - state, e); + String msg = operation + ", SQL authorization, not db owner"; + assertNotNull( + msg + ": succeeded unexpectedly without exeption", e); + assertSQLState(msg, state, e); } break; default: @@ -798,7 +848,7 @@ public class DboPowersTest extends BaseJ public static void derby3038Proc() throws SQLException { - // Before fixing DERNY-3038 this connect would fail. + // Before fixing DERBY-3038 this connect would fail. Connection con = java.sql.DriverManager. getConnection("jdbc:default:connection"); con.close(); Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ErrorCodeTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ErrorCodeTest.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ErrorCodeTest.java (original) +++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ErrorCodeTest.java Thu Oct 4 11:30:54 2012 @@ -129,6 +129,7 @@ public final class ErrorCodeTest extends {"08004","Missing permission for user '{0}' to create database '{1}' [{2}].","40000"}, {"08004","Connection authentication failure occurred. Either the supplied credentials were invalid, or the database uses a password encryption scheme not compatible with the strong password substitution security mechanism. If this error started after upgrade, refer to the release note for DERBY-4483 for options.","40000"}, {"08004","Username or password is null or 0 length.","40000"}, + {"08004","User '{0}' cannot decrypt database '{1}'. Only the database owner can perform this operation.","40000"}, {"08006","An error occurred during connect reset and the connection has been terminated. See chained exceptions for details.","40000"}, {"08006","SocketException: '{0}'","40000"}, {"08006","A communications error has been detected: {0}.","40000"}, Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/CryptoCrashRecoveryTest.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/CryptoCrashRecoveryTest.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/CryptoCrashRecoveryTest.java (original) +++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/CryptoCrashRecoveryTest.java Thu Oct 4 11:30:54 2012 @@ -43,8 +43,8 @@ import org.apache.derbyTesting.junit.Tes * the database. *

* Debug flags are used to simulate crashes during the encryption of an - * un-encrypted database and re-encryption of an encrypted database with new - * password/key. + * un-encrypted database, re-encryption of an encrypted database with new + * password/key, and decryption of an encrypted database. *

* Unlike the other recovery tests which do a setup and recovery as different * tests, crash/recovery for cryptographic operations can be simulated in one @@ -84,12 +84,31 @@ public class CryptoCrashRecoveryTest suite = TestConfiguration.embeddedSuite( CryptoCrashRecoveryTest.class); } else { - suite = new TestSuite("disabled due to non-debug build"); - println("test disabled due to non-debug build"); + suite = new TestSuite( + "CryptoCrashRecovery disabled due to non-debug build"); + println("CryptoCrashRecoveryTest disabled due to non-debug build"); } return suite; } + public void testDecryptionWithBootPassword() + throws Exception { + String db = "wombat_pwd_de"; + // Crash recovery during decryption (with password mechanism). + DataSource ds = JDBCDataSource.getDataSource(db); + runCrashRecoveryTestCases(ds, OP_DECRYPT, USE_ENC_PWD); + assertDirectoryDeleted(new File("system", db)); + } + + public void testDecryptionWithEncryptionKey() + throws Exception { + String db = "wombat_key_de"; + // Crash recovery during database decryption (with encryption key). + DataSource ds = JDBCDataSource.getDataSource(db); + runCrashRecoveryTestCases(ds, OP_DECRYPT, USE_ENC_KEY); + assertDirectoryDeleted(new File("system", db)); + } + public void testEncryptionWithBootPassword() throws Exception { String db = "wombat_pwd_en"; @@ -99,7 +118,7 @@ public class CryptoCrashRecoveryTest assertDirectoryDeleted(new File("system", db)); } - public void testEncryptionWitEncryptionKey() + public void testEncryptionWithEncryptionKey() throws Exception { String db = "wombat_key_en"; // Crash recovery during database encryption using the encryption key. @@ -138,12 +157,18 @@ public class CryptoCrashRecoveryTest private void runCrashRecoveryTestCases(DataSource ds, int operation, boolean useEncPwd) throws SQLException { - verifyOperation(operation); - Connection con; - if (operation == OP_REENCRYPT) { - con = createEncryptedDatabase(ds, useEncPwd); - } else { - con = createDatabase(ds); + Connection con = null; // silence the compiler + switch (operation) { + case OP_DECRYPT: + // Fall through. + case OP_REENCRYPT: + con = createEncryptedDatabase(ds, useEncPwd); + break; + case OP_ENCRYPT: + con = createDatabase(ds); + break; + default: + fail("unsupported operation: " + operation); } createTable(con, TEST_TABLE_NAME); @@ -153,7 +178,7 @@ public class CryptoCrashRecoveryTest con.close(); JDBCDataSource.shutdownDatabase(ds); - // Following cases of (re-)encryption should be rolled back. + // Following cases of cryptographic operations should be rolled back. Boolean useNewCredential = (operation == OP_REENCRYPT ? Boolean.FALSE : null); @@ -171,7 +196,7 @@ public class CryptoCrashRecoveryTest crash(ds, operation, useEncPwd, TEST_REENCRYPT_CRASH_AFTER_COMMT); crashInRecovery(ds, useEncPwd, useNewCredential, TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_LOGFILE_DELETE); - // retry (re)encryption and crash. + // Retry operation and crash. crash(ds, operation, useEncPwd, TEST_REENCRYPT_CRASH_AFTER_COMMT); crash(ds, operation, useEncPwd, @@ -187,24 +212,26 @@ public class CryptoCrashRecoveryTest TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY); crashInRecovery(ds, useEncPwd, useNewCredential, TEST_REENCRYPT_CRASH_AFTER_RECOVERY_UNDO_REVERTING_KEY); - // Retry (re-)encryption and crash. + // Retry operation and crash. crash(ds, operation, useEncPwd, TEST_REENCRYPT_CRASH_AFTER_SWITCH_TO_NEWKEY); crashInRecovery(ds, useEncPwd, useNewCredential, TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP); - // Rollowing cases of (re-)encryption should be successful, only - // cleanup is pending. - - // Crash after database is (re-)encrypted, but before cleanup. + // Following cases should be successful, only cleanup is pending. + // Crash after the cryptographic operation has been performed, but + // before cleanup. // If re-encryption is complete, database should be bootable with the - // new password. + // new password. If decryption is complete, database should be bootable + // without specifying a boot password / key. useNewCredential = (operation == OP_REENCRYPT ? Boolean.TRUE : Boolean.FALSE); crash(ds, operation, useEncPwd, TEST_REENCRYPT_CRASH_AFTER_CHECKPOINT); crashInRecovery(ds, useEncPwd, useNewCredential, TEST_REENCRYPT_CRASH_BEFORE_RECOVERY_FINAL_CLEANUP); - + if (operation == OP_DECRYPT) { + useNewCredential = null; + } recover(ds, useEncPwd, useNewCredential); JDBCDataSource.shutdownDatabase(ds); } @@ -225,10 +252,18 @@ public class CryptoCrashRecoveryTest setDebugFlag(debugFlag); try { - if (operation == OP_REENCRYPT) { - reEncryptDatabase(ds, useEncPwd); - } else { - encryptDatabase(ds, useEncPwd); + switch (operation) { + case OP_REENCRYPT: + reEncryptDatabase(ds, useEncPwd); + break; + case OP_ENCRYPT: + encryptDatabase(ds, useEncPwd); + break; + case OP_DECRYPT: + decryptDatabase(ds, useEncPwd); + break; + default: + fail("unsupported operation"); } fail("crypto operation didn't crash as expected"); } catch (SQLException sqle) { @@ -548,6 +583,32 @@ public class CryptoCrashRecoveryTest } /** + * Decrypts an encrypted database. + * + * @param ds database + * @param useEncPwd whether to use boot password or encryption key + * @throws SQLException if any database exception occurs + */ + private Connection decryptDatabase(DataSource ds, boolean useEncPwd) + throws SQLException { + String connAttrs = "decryptDatabase=true;"; + if (useEncPwd) { + connAttrs += "bootPassword=" + OLD_PASSWORD; + } else { + connAttrs += "encryptionKey=" + OLD_KEY; + } + + JDBCDataSource.setBeanProperty(ds, "connectionAttributes", connAttrs); + println("decrypting " + db(ds) + " with " + connAttrs); + //Decrypt the existing database. + try { + return ds.getConnection(); + } finally { + JDBCDataSource.clearStringBeanProperty(ds, "connectionAttributes"); + } + } + + /** * Boots the database. * * @param ds database @@ -584,18 +645,6 @@ public class CryptoCrashRecoveryTest } } - /** Verifies if the operation constant is a known operation. */ - private static void verifyOperation(int operation) { - switch (operation) { - case OP_ENCRYPT: - //case OP_DECRYPT: - case OP_REENCRYPT: - return; - default: - fail("unknown operation constant: " + operation); - } - } - /** Extracts the database name from the data source. */ private static String db(DataSource ds) { try { Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java?rev=1393993&r1=1393992&r2=1393993&view=diff ============================================================================== --- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java (original) +++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/store/_Suite.java Thu Oct 4 11:30:54 2012 @@ -99,6 +99,7 @@ public class _Suite extends BaseTestCase suite.addTest(EncryptionAESTest.suite()); suite.addTest(EncryptDatabaseTest.suite()); suite.addTest(CryptoCrashRecoveryTest.suite()); + suite.addTest(DecryptDatabaseTest.suite()); } return suite;