diff --git a/src/crypto.c b/src/crypto.c index 4581939..45cbdc8 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -111,18 +111,39 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else #endif #ifdef SQLCIPHER_TEST - if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ + if( sqlite3StrICmp(zLeft,"cipher_test_on")==0 ){ if( zRight ) { - if(sqlite3StrICmp(zRight, "fail_next_encrypt")) { - sqlcipher_set_test_flags(sqlcipher_get_test_flags() ^ TEST_FAIL_NEXT_ENCRYPT); + unsigned int flags = sqlcipher_get_test_flags(); + if(sqlite3StrICmp(zRight, "fail_encrypt")==0) { + flags |= TEST_FAIL_ENCRYPT; } else - if(sqlite3StrICmp(zRight, "fail_next_decrypt")) { - sqlcipher_set_test_flags(sqlcipher_get_test_flags() ^ TEST_FAIL_NEXT_DECRYPT); - } - } else { - char *flags = sqlite3_mprintf("%d", sqlcipher_get_test_flags()); - codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); + if(sqlite3StrICmp(zRight, "fail_decrypt")==0) { + flags |= TEST_FAIL_DECRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_migrate")==0) { + flags |= TEST_FAIL_MIGRATE; + } + sqlcipher_set_test_flags(flags); } + } else + if( sqlite3StrICmp(zLeft,"cipher_test_off")==0 ){ + if( zRight ) { + unsigned int flags = sqlcipher_get_test_flags(); + if(sqlite3StrICmp(zRight, "fail_encrypt")==0) { + flags &= ~TEST_FAIL_ENCRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_decrypt")==0) { + flags &= ~TEST_FAIL_DECRYPT; + } else + if(sqlite3StrICmp(zRight, "fail_migrate")==0) { + flags &= ~TEST_FAIL_MIGRATE; + } + sqlcipher_set_test_flags(flags); + } + } else + if( sqlite3StrICmp(zLeft,"cipher_test")==0 ){ + char *flags = sqlite3_mprintf("%i", sqlcipher_get_test_flags()); + codec_vdbe_return_string(pParse, "cipher_test", flags, P4_DYNAMIC); }else #endif if( sqlite3StrICmp(zLeft, "cipher_fips_status")== 0 && !zRight ){ @@ -157,8 +178,12 @@ int sqlcipher_codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLef } else if( sqlite3StrICmp(zLeft, "cipher_migrate")==0 && !zRight ){ if(ctx){ - char *migrate_status = sqlite3_mprintf("%d", sqlcipher_codec_ctx_migrate(ctx)); + int status = sqlcipher_codec_ctx_migrate(ctx); + char *migrate_status = sqlite3_mprintf("%d", status); codec_vdbe_return_string(pParse, "cipher_migrate", migrate_status, P4_DYNAMIC); + if(status != SQLITE_OK) { + sqlcipher_codec_ctx_set_error(ctx, status); + } } } else if( sqlite3StrICmp(zLeft, "cipher_provider")==0 && !zRight ){ @@ -709,7 +734,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_DECRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_NEXT_ENCRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_ENCRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*) buffer+offset, 0, page_sz-offset); @@ -734,7 +759,7 @@ static void* sqlite3Codec(void *iCtx, void *data, Pgno pgno, int mode) { } rc = sqlcipher_page_cipher(ctx, cctx, pgno, CIPHER_ENCRYPT, page_sz - offset, pData + offset, (unsigned char*)buffer + offset); #ifdef SQLCIPHER_TEST - if((sqlcipher_get_test_flags() & TEST_FAIL_NEXT_DECRYPT) > 0) rc = SQLITE_ERROR; + if((sqlcipher_get_test_flags() & TEST_FAIL_DECRYPT) > 0) rc = SQLITE_ERROR; #endif if(rc != SQLITE_OK) { /* clear results of failed cipher operation and set error */ sqlcipher_memset((unsigned char*)buffer+offset, 0, page_sz-offset); diff --git a/src/crypto.h b/src/crypto.h index f0aee2d..3aaf238 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -193,10 +193,11 @@ static int cipher_isHex(const unsigned char *hex, int sz){ /* possible flags for simulating specific test conditions */ #ifdef SQLCIPHER_TEST -#define TEST_FAIL_NEXT_ENCRYPT (1ul << 0) /* 1 */ -#define TEST_FAIL_NEXT_DECRYPT (1ul << 1) /* 2 */ -int sqlcipher_get_test_flags(void); -void sqlcipher_set_test_flags(int); +#define TEST_FAIL_ENCRYPT 0x01 +#define TEST_FAIL_DECRYPT 0x02 +#define TEST_FAIL_MIGRATE 0x04 +unsigned int sqlcipher_get_test_flags(void); +void sqlcipher_set_test_flags(unsigned int); #endif /* extensions defined in crypto_impl.c */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 78fe69b..d1f89b6 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -45,7 +45,13 @@ #endif #ifdef SQLCIPHER_TEST -static volatile int cipher_test_flags = 0; +static volatile unsigned int cipher_test_flags = 0; +unsigned int sqlcipher_get_test_flags() { + return cipher_test_flags; +} +void sqlcipher_set_test_flags(unsigned int flags) { + cipher_test_flags = flags; +} #endif /* Generate code to return a string value */ @@ -402,16 +408,6 @@ char* sqlcipher_version() { return version; } -#ifdef SQLCIPHER_TEST -int sqlcipher_get_test_flags() { - return cipher_test_flags; -} - -void sqlcipher_set_test_flags(int flags) { - cipher_test_flags = flags; -} -#endif - /** * Initialize new cipher_ctx struct. This function will allocate memory * for the cipher context and for the key @@ -1380,7 +1376,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { pass = sqlcipher_malloc(pass_sz+1); memset(pass, 0, pass_sz+1); memcpy(pass, ctx->read_ctx->pass, pass_sz); - + /* Version 4 - current, no upgrade required, so exit immediately */ rc = sqlcipher_check_connection(db_filename, pass, pass_sz, "", &user_version, &journal_mode); if(rc == SQLITE_OK){ @@ -1398,6 +1394,7 @@ int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { if(pragma_compat) sqlcipher_free(pragma_compat, sqlite3Strlen30(pragma_compat)); pragma_compat = NULL; } + /* if we exit the loop normally we failed to determine the version, this is an error */ CODEC_TRACE("Upgrade format not determined\n"); goto handle_error; @@ -1445,6 +1442,14 @@ migrate: goto handle_error; } +#ifdef SQLCIPHER_TEST + if((sqlcipher_get_test_flags() & TEST_FAIL_MIGRATE) > 0) { + rc = SQLITE_ERROR; + CODEC_TRACE("simulated migrate failure, error code %d\n", rc); + goto handle_error; + } +#endif + rc = sqlite3_exec(db, set_user_version, NULL, NULL, NULL); if(rc != SQLITE_OK){ CODEC_TRACE("set user version failed, error code %d\n", rc); @@ -1537,7 +1542,6 @@ migrate: handle_error: CODEC_TRACE("An error occurred attempting to migrate the database - last error %d\n", rc); - rc = SQLITE_ERROR; cleanup: if(pass) sqlcipher_free(pass, pass_sz); diff --git a/test/sqlcipher-codecerror.test b/test/sqlcipher-codecerror.test index 927e8d1..12cafbd 100644 --- a/test/sqlcipher-codecerror.test +++ b/test/sqlcipher-codecerror.test @@ -66,7 +66,7 @@ do_test codec-error-journal-delete { catchsql { PRAGMA key = 'testkey'; - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_on = fail_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -74,7 +74,7 @@ do_test codec-error-journal-delete { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_off = fail_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -92,7 +92,7 @@ do_test codec-error-journal-wal { catchsql { PRAGMA key = 'testkey'; - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_on = fail_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -100,7 +100,7 @@ do_test codec-error-journal-wal { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_off = fail_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -120,7 +120,7 @@ do_test codec-error-journal-wal-transaction { PRAGMA key = 'testkey'; BEGIN; UPDATE t1 SET b = 'success' WHERE a = 1; - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_on = fail_encrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; COMMIT; } @@ -129,7 +129,7 @@ do_test codec-error-journal-wal-transaction { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_encrypt; + PRAGMA cipher_test_off = fail_encrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; @@ -149,7 +149,7 @@ do_test codec-error-journal-wal-read { catchsql { PRAGMA key = 'testkey'; SELECT count(*) FROM sqlite_schema; - PRAGMA cipher_test = fail_next_decrypt; + PRAGMA cipher_test_on = fail_decrypt; UPDATE t1 SET b = 'fail' WHERE a = 5000; } @@ -157,7 +157,7 @@ do_test codec-error-journal-wal-read { sqlite_orig db test.db execsql { - PRAGMA cipher_test = fail_next_decrypt; + PRAGMA cipher_test_off = fail_decrypt; PRAGMA key = 'testkey'; PRAGMA cipher_integrity_check; PRAGMA integrity_check; diff --git a/test/sqlcipher-compatibility.test b/test/sqlcipher-compatibility.test index 416062d..965ef0a 100644 --- a/test/sqlcipher-compatibility.test +++ b/test/sqlcipher-compatibility.test @@ -360,7 +360,8 @@ file delete -force test.db # open a 1.1.8 database without hmac, then copy the data do_test attach-and-copy-1.1.8 { - sqlite_orig db $sampleDir/sqlcipher-1.1.8-testkey.db + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db test.db + sqlite_orig db test.db execsql { PRAGMA key = 'testkey'; @@ -368,14 +369,14 @@ do_test attach-and-copy-1.1.8 { PRAGMA kdf_iter = 4000; PRAGMA cipher_page_size = 1024; PRAGMA cipher_kdf_algorithm = PBKDF2_HMAC_SHA1; - ATTACH DATABASE 'test.db' AS db2 KEY 'testkey-hmac'; + ATTACH DATABASE 'test-new.db' AS db2 KEY 'testkey-hmac'; CREATE TABLE db2.t1(a,b); INSERT INTO db2.t1 SELECT * FROM main.t1; DETACH DATABASE db2; } db close - sqlite_orig db test.db + sqlite_orig db test-new.db execsql { PRAGMA key = 'testkey-hmac'; SELECT count(*) FROM t1; @@ -384,7 +385,7 @@ do_test attach-and-copy-1.1.8 { } {ok 75709 1 1 one one 1 2 one two 1 2} db close file delete -force test.db - +file delete -force test-new.db # open a standard database, then attach a new # database with completely different options. @@ -681,7 +682,7 @@ file delete -force test-vacuum.db # setting as the original do_test default-hmac-kdf-attach { file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db test.db - file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db; + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db sqlite_orig db test.db execsql { PRAGMA cipher_default_use_hmac = OFF; @@ -707,7 +708,7 @@ file delete -force sqlcipher-1.1.8-testkey.db # fail because the hmac setting for the # attached database is not compatible do_test attach-1.1.8-database-from-2.0-fails { - file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db; + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db sqlite_orig db test.db catchsql { PRAGMA key = 'testkey'; @@ -725,7 +726,7 @@ file delete -force sqlcipher-1.1.8-testkey.db # succeed now that hmac is off by default # before the attach do_test change-default-hmac-kdf-attach { - file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db; + file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db sqlcipher-1.1.8-testkey.db sqlite_orig db test.db execsql { PRAGMA key = 'testkey'; @@ -1087,19 +1088,22 @@ file delete -force test.db test.db-migrated test.db-journal do_test migrate-3-0-database-to-current-format { file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db sqlite_orig db test.db - execsql { + set rc {} + + lappend rc [execsql { PRAGMA key = 'testkey'; PRAGMA cipher_migrate; - } + SELECT count(*) FROM sqlite_schema; + }] db close sqlite_orig db test.db - execsql { + lappend rc [execsql { PRAGMA key = 'testkey'; SELECT count(*) FROM sqlite_schema; PRAGMA journal_mode; - } -} {ok 1 delete} + }] +} {{ok 0 1} {ok 1 delete}} db close file delete -force test.db @@ -1133,6 +1137,58 @@ do_test migrate-wal-database-to-current { db close file delete -force test.db +# test original database is left untouched after +# a failed migration e.g. due to low disk space +do_test migrate-failure { + file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db + sqlite_orig db test.db + + set rc {} + + lappend rc [execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_migrate; + }] + db close + + sqlite_orig db test.db + lappend rc [execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_compatibility = 3; + SELECT count(*) FROM sqlite_schema; + }] +} {{ok 1} {ok 1}} +db close +file delete -force test.db test.db-migrated + +# leave database is a readable state after a +# a failed migration +do_test migrate-failure-readable { + file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db + sqlite_orig db test.db + + set rc {} + lappend rc [execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_migrate; + }] + + lappend rc [catchsql { + SELECT count(*) FROM sqlite_schema; + }] + db close + + sqlite_orig db test.db + lappend rc [execsql { + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_test; + }] +} {{ok 1} {1 {SQL logic error}} 0} +db close +file delete -force test.db test.db-migrated do_test key-database-by-name { sqlite_orig db test.db @@ -1259,6 +1315,7 @@ do_test can-migrate-with-raw-hex-key { PRAGMA key = "x'2DD29CA851E7B56E4697B0E1F08507293D761A05CE4D1B628663F411A8086D99'"; PRAGMA cipher_migrate; } + db close sqlite_orig db test.db execsql { diff --git a/test/sqlcipher-core.test b/test/sqlcipher-core.test index 185a0ff..72d4528 100644 --- a/test/sqlcipher-core.test +++ b/test/sqlcipher-core.test @@ -801,4 +801,54 @@ db close dba close file delete -force test.db +do_test test_flags_fail_encrypt { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_encrypt; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_encrypt; + PRAGMA cipher_test; + } +} {0 1 0} +db close + +do_test test_flags_fail_decrypt { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_decrypt; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_decrypt; + PRAGMA cipher_test; + } +} {0 2 0} +db close + +do_test test_flags_fail_migrate { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_test; + } +} {0 4 0} +db close + +do_test test_flags_combo { + sqlite_orig db :memory: + execsql { + PRAGMA cipher_test; + PRAGMA cipher_test_on = fail_encrypt; + PRAGMA cipher_test_on = fail_migrate; + PRAGMA cipher_test; + PRAGMA cipher_test_off = fail_encrypt; + PRAGMA cipher_test_off = fail_migrate; + PRAGMA cipher_test; + } +} {0 5 0} +db close + finish_test