diff --git a/sqlcipher-2.3-testkey.db b/sqlcipher-2.3-testkey.db new file mode 100644 index 0000000..da27623 Binary files /dev/null and b/sqlcipher-2.3-testkey.db differ diff --git a/src/crypto.c b/src/crypto.c index 2551e6b..ddca0ec 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -89,6 +89,13 @@ int codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLeft, const c CODEC_TRACE(("codec_pragma: entered db=%p iDb=%d pParse=%p zLeft=%s zRight=%s ctx=%p\n", db, iDb, pParse, zLeft, zRight, ctx)); + if( sqlite3StrICmp(zLeft, "cipher_migrate")==0 && !zRight ){ + if(ctx){ + char *migrate_status = sqlite3_mprintf("%d", sqlcipher_codec_ctx_migrate(ctx)); + codec_vdbe_return_static_string(pParse, "sqlcipher_migrate", migrate_status); + sqlite3_free(migrate_status); + } + } else if( sqlite3StrICmp(zLeft, "cipher_provider")==0 && !zRight ){ if(ctx) { codec_vdbe_return_static_string(pParse, "cipher_provider", sqlcipher_codec_get_cipher_provider(ctx)); @@ -110,6 +117,15 @@ int codec_pragma(sqlite3* db, int iDb, Parse *pParse, const char *zLeft, const c if( sqlite3StrICmp(zLeft, "rekey_cipher")==0 && zRight ){ if(ctx) sqlcipher_codec_ctx_set_cipher(ctx, zRight, 1); // change write cipher only }else + if( sqlite3StrICmp(zLeft,"cipher_default_kdf_iter")==0 ){ + if( zRight ) { + sqlcipher_set_default_kdf_iter(atoi(zRight)); // change default KDF iterations + } else { + char *kdf_iter = sqlite3_mprintf("%d", sqlcipher_get_default_kdf_iter()); + codec_vdbe_return_static_string(pParse, "cipher_default_kdf_iter", kdf_iter); + sqlite3_free(kdf_iter); + } + }else if( sqlite3StrICmp(zLeft, "kdf_iter")==0 ){ if(ctx) { if( zRight ) { @@ -412,13 +428,11 @@ int sqlite3_rekey(sqlite3 *db, const void *pKey, int nKey) { void sqlite3CodecGetKey(sqlite3* db, int nDb, void **zKey, int *nKey) { struct Db *pDb = &db->aDb[nDb]; CODEC_TRACE(("sqlite3CodecGetKey: entered db=%p, nDb=%d\n", db, nDb)); - if( pDb->pBt ) { codec_ctx *ctx; sqlite3pager_get_codec(pDb->pBt->pBt->pPager, (void **) &ctx); - if(ctx) { /* if the codec has an attached codec_context user the raw key data */ - sqlcipher_codec_get_pass(ctx, zKey, nKey); + sqlcipher_codec_get_keyspec(ctx, zKey, nKey); } else { *zKey = NULL; *nKey = 0; diff --git a/src/crypto.h b/src/crypto.h index a45b57c..f200377 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -59,7 +59,7 @@ #define CIPHER_READWRITE_CTX 2 #ifndef PBKDF2_ITER -#define PBKDF2_ITER 4000 +#define PBKDF2_ITER 64000 #endif /* possible flags for cipher_ctx->flags */ @@ -149,6 +149,13 @@ static void cipher_hex2bin(const char *hex, int sz, unsigned char *out){ } } +static void cipher_bin2hex(const unsigned char* in, int sz, char *out) { + int i; + for(i=0; i < sz; i++) { + sprintf(out + (i*2), "%02x ", in[i]); + } +} + /* extensions defined in crypto_impl.c */ typedef struct codec_ctx codec_ctx; @@ -167,12 +174,15 @@ int sqlcipher_page_cipher(codec_ctx *, int, Pgno, int, int, unsigned char *, uns void sqlcipher_codec_ctx_set_error(codec_ctx *, int); int sqlcipher_codec_ctx_set_pass(codec_ctx *, const void *, int, int); -void sqlcipher_codec_get_pass(codec_ctx *, void **zKey, int *nKey); +void sqlcipher_codec_get_keyspec(codec_ctx *, void **zKey, int *nKey); int sqlcipher_codec_ctx_set_pagesize(codec_ctx *, int); int sqlcipher_codec_ctx_get_pagesize(codec_ctx *); int sqlcipher_codec_ctx_get_reservesize(codec_ctx *); +void sqlcipher_set_default_kdf_iter(int iter); +int sqlcipher_get_default_kdf_iter(); + int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *, int, int); int sqlcipher_codec_ctx_get_kdf_iter(codec_ctx *ctx, int); @@ -202,6 +212,7 @@ int sqlcipher_codec_ctx_unset_flag(codec_ctx *ctx, unsigned int flag); int sqlcipher_codec_ctx_get_flag(codec_ctx *ctx, unsigned int flag, int for_ctx); const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx); +int sqlcipher_codec_ctx_migrate(codec_ctx *ctx); #endif #endif /* END SQLCIPHER */ diff --git a/src/crypto_impl.c b/src/crypto_impl.c index 1e7fa99..b28bbd7 100644 --- a/src/crypto_impl.c +++ b/src/crypto_impl.c @@ -56,16 +56,19 @@ typedef struct { int pass_sz; int reserve_sz; int hmac_sz; + int keyspec_sz; unsigned int flags; unsigned char *key; unsigned char *hmac_key; char *pass; + char *keyspec; sqlcipher_provider *provider; void *provider_ctx; } cipher_ctx; static unsigned int default_flags = DEFAULT_CIPHER_FLAGS; static unsigned char hmac_salt_mask = HMAC_SALT_MASK; +static int default_kdf_iter = PBKDF2_ITER; static unsigned int sqlcipher_activate_count = 0; static sqlite3_mutex* sqlcipher_provider_mutex = NULL; static sqlcipher_provider *default_provider = NULL; @@ -289,6 +292,7 @@ static void sqlcipher_cipher_ctx_free(cipher_ctx **iCtx) { sqlcipher_free(ctx->key, ctx->key_sz); sqlcipher_free(ctx->hmac_key, ctx->key_sz); sqlcipher_free(ctx->pass, ctx->pass_sz); + sqlcipher_free(ctx->keyspec, ctx->keyspec_sz); sqlcipher_free(ctx, sizeof(cipher_ctx)); } @@ -336,8 +340,9 @@ static int sqlcipher_cipher_ctx_copy(cipher_ctx *target, cipher_ctx *source) { CODEC_TRACE(("sqlcipher_cipher_ctx_copy: entered target=%p, source=%p\n", target, source)); sqlcipher_free(target->pass, target->pass_sz); + sqlcipher_free(target->keyspec, target->keyspec_sz); memcpy(target, source, sizeof(cipher_ctx)); - + target->key = key; //restore pointer to previously allocated key data memcpy(target->key, source->key, CIPHER_MAX_KEY_SZ); @@ -350,31 +355,67 @@ static int sqlcipher_cipher_ctx_copy(cipher_ctx *target, cipher_ctx *source) { target->provider_ctx = provider_ctx; // restore pointer to previouly allocated provider context; target->provider->ctx_copy(target->provider_ctx, source->provider_ctx); - target->pass = sqlcipher_malloc(source->pass_sz); - if(target->pass == NULL) return SQLITE_NOMEM; - memcpy(target->pass, source->pass, source->pass_sz); + if(source->pass && source->pass_sz) { + target->pass = sqlcipher_malloc(source->pass_sz); + if(target->pass == NULL) return SQLITE_NOMEM; + memcpy(target->pass, source->pass, source->pass_sz); + } + if(source->keyspec && source->keyspec_sz) { + target->keyspec = sqlcipher_malloc(source->keyspec_sz); + if(target->keyspec == NULL) return SQLITE_NOMEM; + memcpy(target->keyspec, source->keyspec, source->keyspec_sz); + } + return SQLITE_OK; +} + +/** + * Set the keyspec for the cipher_ctx + * + * returns SQLITE_OK if assignment was successfull + * returns SQLITE_NOMEM if an error occured allocating memory + */ +static int sqlcipher_cipher_ctx_set_keyspec(cipher_ctx *ctx, const unsigned char *key, int key_sz, const unsigned char *salt, int salt_sz) { + + /* free, zero existing pointers and size */ + sqlcipher_free(ctx->keyspec, ctx->keyspec_sz); + ctx->keyspec = NULL; + ctx->keyspec_sz = 0; + + /* establic a hex-formated key specification, containing the raw encryption key and + the salt used to generate it */ + ctx->keyspec_sz = ((key_sz + salt_sz) * 2) + 3; + ctx->keyspec = sqlcipher_malloc(ctx->keyspec_sz); + if(ctx->keyspec == NULL) return SQLITE_NOMEM; + + ctx->keyspec[0] = 'x'; + ctx->keyspec[1] = '\''; + ctx->keyspec[ctx->keyspec_sz - 1] = '\''; + cipher_bin2hex(key, key_sz, ctx->keyspec + 2); + cipher_bin2hex(salt, salt_sz, ctx->keyspec + (key_sz * 2) + 2); return SQLITE_OK; } - /** - * Set the raw password / key data for a cipher context + * Set the passphrase for the cipher_ctx * * returns SQLITE_OK if assignment was successfull * returns SQLITE_NOMEM if an error occured allocating memory - * returns SQLITE_ERROR if the key couldn't be set because the pass was null or size was zero */ static int sqlcipher_cipher_ctx_set_pass(cipher_ctx *ctx, const void *zKey, int nKey) { + + /* free, zero existing pointers and size */ sqlcipher_free(ctx->pass, ctx->pass_sz); - ctx->pass_sz = nKey; - if(zKey && nKey) { + ctx->pass = NULL; + ctx->pass_sz = 0; + + if(zKey && nKey) { /* if new password is provided, copy it */ + ctx->pass_sz = nKey; ctx->pass = sqlcipher_malloc(nKey); if(ctx->pass == NULL) return SQLITE_NOMEM; memcpy(ctx->pass, zKey, nKey); - return SQLITE_OK; - } - return SQLITE_ERROR; + } + return SQLITE_OK; } int sqlcipher_codec_ctx_set_pass(codec_ctx *ctx, const void *zKey, int nKey, int for_ctx) { @@ -415,6 +456,15 @@ const char* sqlcipher_codec_ctx_get_cipher(codec_ctx *ctx, int for_ctx) { return c_ctx->provider->get_cipher(c_ctx->provider_ctx); } +/* set the global default KDF iteration */ +void sqlcipher_set_default_kdf_iter(int iter) { + default_kdf_iter = iter; +} + +int sqlcipher_get_default_kdf_iter() { + return default_kdf_iter; +} + int sqlcipher_codec_ctx_set_kdf_iter(codec_ctx *ctx, int kdf_iter, int for_ctx) { cipher_ctx *c_ctx = for_ctx ? ctx->write_ctx : ctx->read_ctx; int rc; @@ -537,9 +587,9 @@ void* sqlcipher_codec_ctx_get_kdf_salt(codec_ctx *ctx) { return ctx->kdf_salt; } -void sqlcipher_codec_get_pass(codec_ctx *ctx, void **zKey, int *nKey) { - *zKey = ctx->read_ctx->pass; - *nKey = ctx->read_ctx->pass_sz; +void sqlcipher_codec_get_keyspec(codec_ctx *ctx, void **zKey, int *nKey) { + *zKey = ctx->read_ctx->keyspec; + *nKey = ctx->read_ctx->keyspec_sz; } int sqlcipher_codec_ctx_set_pagesize(codec_ctx *ctx, int size) { @@ -601,7 +651,7 @@ int sqlcipher_codec_ctx_init(codec_ctx **iCtx, Db *pDb, Pager *pPager, sqlite3_f } if((rc = sqlcipher_codec_ctx_set_cipher(ctx, CIPHER, 0)) != SQLITE_OK) return rc; - if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, PBKDF2_ITER, 0)) != SQLITE_OK) return rc; + if((rc = sqlcipher_codec_ctx_set_kdf_iter(ctx, default_kdf_iter, 0)) != SQLITE_OK) return rc; if((rc = sqlcipher_codec_ctx_set_fast_kdf_iter(ctx, FAST_PBKDF2_ITER, 0)) != SQLITE_OK) return rc; if((rc = sqlcipher_codec_ctx_set_pass(ctx, zKey, nKey, 0)) != SQLITE_OK) return rc; @@ -750,7 +800,11 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int * Derive an encryption key for a cipher contex key based on the raw password. * * If the raw key data is formated as x'hex' and there are exactly enough hex chars to fill - * the key space (i.e 64 hex chars for a 256 bit key) then the key data will be used directly. + * the key (i.e 64 hex chars for a 256 bit key) then the key data will be used directly. + + * Else, if the raw key data is formated as x'hex' and there are exactly enough hex chars to fill + * the key and the salt (i.e 92 hex chars for a 256 bit key and 16 byte salt) then it will be unpacked + * as the key followed by the salt. * * Otherwise, a key data will be derived using PBKDF2 * @@ -758,7 +812,8 @@ int sqlcipher_page_cipher(codec_ctx *ctx, int for_ctx, Pgno pgno, int mode, int * returns SQLITE_ERROR if the key could't be derived (for instance if pass is NULL or pass_sz is 0) */ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { - CODEC_TRACE(("codec_key_derive: entered c_ctx->pass=%s, c_ctx->pass_sz=%d \ + int rc; + CODEC_TRACE(("cipher_ctx_key_derive: entered c_ctx->pass=%s, c_ctx->pass_sz=%d \ ctx->kdf_salt=%p ctx->kdf_salt_sz=%d c_ctx->kdf_iter=%d \ ctx->hmac_kdf_salt=%p, c_ctx->fast_kdf_iter=%d c_ctx->key_sz=%d\n", c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, c_ctx->kdf_iter, @@ -766,19 +821,26 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { if(c_ctx->pass && c_ctx->pass_sz) { // if pass is not null - if (c_ctx->pass_sz == ((c_ctx->key_sz*2)+3) && sqlite3StrNICmp(c_ctx->pass ,"x'", 2) == 0) { + if (c_ctx->pass_sz == ((c_ctx->key_sz * 2) + 3) && sqlite3StrNICmp(c_ctx->pass ,"x'", 2) == 0) { int n = c_ctx->pass_sz - 3; /* adjust for leading x' and tailing ' */ const char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ - CODEC_TRACE(("codec_key_derive: using raw key from hex\n")); + CODEC_TRACE(("cipher_ctx_key_derive: using raw key from hex\n")); cipher_hex2bin(z, n, c_ctx->key); + } else if (c_ctx->pass_sz == (((c_ctx->key_sz + ctx->kdf_salt_sz) * 2) + 3) && sqlite3StrNICmp(c_ctx->pass ,"x'", 2) == 0) { + const char *z = c_ctx->pass + 2; /* adjust lead offset of x' */ + CODEC_TRACE(("cipher_ctx_key_derive: using raw key from hex\n")); + cipher_hex2bin(z, (c_ctx->key_sz * 2), c_ctx->key); + cipher_hex2bin(z + (c_ctx->key_sz * 2), (ctx->kdf_salt_sz * 2), ctx->kdf_salt); } else { - CODEC_TRACE(("codec_key_derive: deriving key using full PBKDF2 with %d iterations\n", c_ctx->kdf_iter)); + CODEC_TRACE(("cipher_ctx_key_derive: deriving key using full PBKDF2 with %d iterations\n", c_ctx->kdf_iter)); c_ctx->provider->kdf(c_ctx->provider_ctx, (const char*) c_ctx->pass, c_ctx->pass_sz, ctx->kdf_salt, ctx->kdf_salt_sz, c_ctx->kdf_iter, c_ctx->key_sz, c_ctx->key); - } + /* set the context "keyspec" containing the hex-formatted key and salt to be used when attaching databases */ + if((rc = sqlcipher_cipher_ctx_set_keyspec(c_ctx, c_ctx->key, c_ctx->key_sz, ctx->kdf_salt, ctx->kdf_salt_sz)) != SQLITE_OK) return rc; + /* if this context is setup to use hmac checks, generate a seperate and different key for HMAC. In this case, we use the output of the previous KDF as the input to this KDF run. This ensures a distinct but predictable HMAC key. */ @@ -795,7 +857,7 @@ static int sqlcipher_cipher_ctx_key_derive(codec_ctx *ctx, cipher_ctx *c_ctx) { ctx->hmac_kdf_salt[i] ^= hmac_salt_mask; } - CODEC_TRACE(("codec_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations\n", + CODEC_TRACE(("cipher_ctx_key_derive: deriving hmac key from encryption key using PBKDF2 with %d iterations\n", c_ctx->fast_kdf_iter)); @@ -818,12 +880,17 @@ int sqlcipher_codec_key_derive(codec_ctx *ctx) { if(ctx->write_ctx->derive_key) { if(sqlcipher_cipher_ctx_cmp(ctx->write_ctx, ctx->read_ctx) == 0) { - // the relevant parameters are the same, just copy read key + /* the relevant parameters are the same, just copy read key */ if(sqlcipher_cipher_ctx_copy(ctx->write_ctx, ctx->read_ctx) != SQLITE_OK) return SQLITE_ERROR; } else { if(sqlcipher_cipher_ctx_key_derive(ctx, ctx->write_ctx) != SQLITE_OK) return SQLITE_ERROR; } } + + /* TODO: wipe and free passphrase after key derivation */ + sqlcipher_cipher_ctx_set_pass(ctx->read_ctx, NULL, 0); + sqlcipher_cipher_ctx_set_pass(ctx->write_ctx, NULL, 0); + return SQLITE_OK; } @@ -839,5 +906,208 @@ const char* sqlcipher_codec_get_cipher_provider(codec_ctx *ctx) { return ctx->read_ctx->provider->get_provider_name(ctx->read_ctx); } +int sqlcipher_codec_ctx_migrate(codec_ctx *ctx) { + u32 meta; + int rc = 0; + int command_idx = 0; + int password_sz; + int saved_flags; + int saved_nChange; + int saved_nTotalChange; + void (*saved_xTrace)(void*,const char*); + Db *pDb = 0; + sqlite3 *db = ctx->pBt->db; + const char *db_filename = sqlite3_db_filename(db, "main"); + char *migrated_db_filename = sqlite3_mprintf("%s-migrated", db_filename); + char *query_sqlite_master = "SELECT count(*) from sqlite_master;"; + char *pragma_hmac_off = "PRAGMA cipher_use_hmac = OFF;"; + char *pragma_4k_kdf_iter = "PRAGMA kdf_iter = 4000;"; + char *key; + int key_sz; + int upgrade_1x_format = 0; + int upgrade_4k_format = 0; + sqlite3 *test; + char *err = 0; + static const unsigned char aCopy[] = { + BTREE_SCHEMA_VERSION, 1, /* Add one to the old schema cookie */ + BTREE_DEFAULT_CACHE_SIZE, 0, /* Preserve the default page cache size */ + BTREE_TEXT_ENCODING, 0, /* Preserve the text encoding */ + BTREE_USER_VERSION, 0, /* Preserve the user version */ + BTREE_APPLICATION_ID, 0, /* Preserve the application id */ + }; + key_sz = ctx->read_ctx->pass_sz + 1; + key = sqlcipher_malloc(key_sz); + memset(key, 0, key_sz); + memcpy(key, ctx->read_ctx->pass, ctx->read_ctx->pass_sz); + + if(db_filename){ + + char *attach_command = sqlite3_mprintf("ATTACH DATABASE '%s-migrated' as migrate KEY '%s';", + db_filename, key); + + int rc = sqlcipher_check_connection(db_filename, key, key_sz, ""); + if(rc == SQLITE_OK){ + CODEC_TRACE(("No upgrade required - exiting\n")); + goto exit; + } + + // Version 2 - check for 4k with hmac format + rc = sqlcipher_check_connection(db_filename, key, key_sz, pragma_4k_kdf_iter); + if(rc == SQLITE_OK) { + CODEC_TRACE(("Version 2 format found\n")); + upgrade_4k_format = 1; + } + + // Version 1 - check both no hmac and 4k together + char *pragma_1x_and_4k = sqlite3_mprintf("%s%s", pragma_hmac_off, + pragma_4k_kdf_iter); + rc = sqlcipher_check_connection(db_filename, key, key_sz, pragma_1x_and_4k); + sqlite3_free(pragma_1x_and_4k); + if(rc == SQLITE_OK) { + CODEC_TRACE(("Version 1 format found\n")); + upgrade_1x_format = 1; + upgrade_4k_format = 1; + } + + if(upgrade_1x_format == 0 && upgrade_4k_format == 0) { + CODEC_TRACE(("Upgrade format not determined\n")); + goto handle_error; + } + + const char *commands[] = { + upgrade_4k_format == 1 ? pragma_4k_kdf_iter : "", + upgrade_1x_format == 1 ? pragma_hmac_off : "", + attach_command, + "SELECT sqlcipher_export('migrate');", + }; + for(command_idx = 0; command_idx < ArraySize(commands); command_idx++){ + const char *command = commands[command_idx]; + if(strcmp(command, "") == 0){ + continue; + } + rc = sqlite3_exec(db, command, NULL, NULL, NULL); + if(rc != SQLITE_OK){ + break; + } + } + sqlite3_free(attach_command); + sqlcipher_free(key, key_sz); + + if(rc == SQLITE_OK){ + if( !db->autoCommit ){ + CODEC_TRACE(("cannot migrate from within a transaction")); + goto handle_error; + } + if( db->activeVdbeCnt>1 ){ + CODEC_TRACE(("cannot migrate - SQL statements in progress")); + goto handle_error; + } + + /* Save the current value of the database flags so that it can be + ** restored before returning. Then set the writable-schema flag, and + ** disable CHECK and foreign key constraints. */ + saved_flags = db->flags; + saved_nChange = db->nChange; + saved_nTotalChange = db->nTotalChange; + saved_xTrace = db->xTrace; + db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_PreferBuiltin; + db->flags &= ~(SQLITE_ForeignKeys | SQLITE_ReverseOrder); + db->xTrace = 0; + + Btree *pDest = db->aDb[0].pBt; + pDb = &(db->aDb[db->nDb-1]); + Btree *pSrc = pDb->pBt; + + rc = sqlite3_exec(db, "BEGIN;", NULL, NULL, NULL); + rc = sqlite3BtreeBeginTrans(pSrc, 2); + rc = sqlite3BtreeBeginTrans(pDest, 2); + + assert( 1==sqlite3BtreeIsInTrans(pDest) ); + assert( 1==sqlite3BtreeIsInTrans(pSrc) ); + + + sqlite3CodecGetKey(db, db->nDb - 1, (void**)&key, &password_sz); + sqlite3CodecAttach(db, 0, key, password_sz); + + int i = 0; + for(i=0; iflags = saved_flags; + db->nChange = saved_nChange; + db->nTotalChange = saved_nTotalChange; + db->xTrace = saved_xTrace; + db->autoCommit = 1; + if( pDb ){ + sqlite3BtreeClose(pDb->pBt); + pDb->pBt = 0; + pDb->pSchema = 0; + } + sqlite3ResetAllSchemasOfConnection(db); + remove(migrated_db_filename); + sqlite3_free(migrated_db_filename); + } else { + CODEC_TRACE(("*** migration failure** \n\n")); + } + + } + goto exit; + + handle_error: + CODEC_TRACE(("An error occurred attempting to migrate the database\n")); + rc = SQLITE_ERROR; + + exit: + return rc; +} + +int sqlcipher_check_connection(char *filename, char *key, int key_sz, char *sql) { + int rc; + sqlite3 *db; + char *errMsg; + sqlite3_stmt *statement; + char *query_sqlite_master = "SELECT count(*) FROM sqlite_master;"; + + rc = sqlite3_open(filename, &db); + if(rc != SQLITE_OK){ + goto cleanup; + } + rc = sqlite3_key(db, key, key_sz); + if(rc != SQLITE_OK){ + goto cleanup; + } + rc = sqlite3_exec(db, sql, NULL, NULL, NULL); + if(rc != SQLITE_OK){ + goto cleanup; + } + rc = sqlite3_prepare(db, query_sqlite_master, -1, &statement, NULL); + if(rc != SQLITE_OK){ + goto cleanup; + } + if(sqlite3_step(statement) == SQLITE_ROW){ + rc = SQLITE_OK; + } + goto cleanup; + +cleanup: + if(statement){ + sqlite3_finalize(statement); + } + if(db){ + sqlite3_close(db); + } + + exit: + return rc; + +} + #endif /* END SQLCIPHER */ diff --git a/test/crypto.test b/test/crypto.test index 8a58fe0..330a072 100644 --- a/test/crypto.test +++ b/test/crypto.test @@ -4,8 +4,6 @@ # http://zetetic.net # # Copyright (c) 2009, ZETETIC LLC -# All rights reserved. -# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # * Redistributions of source code must retain the above copyright @@ -407,9 +405,32 @@ do_test rekey-delete-and-query-wal-3 { db close file delete -force test.db +# attach an encrypted database +# without specifying key, verify it fails +# even if the source passwords are the same +# because the kdf salts are different +setup test.db "'testkey'" +do_test attach-database-with-default-key { + sqlite_orig db2 test2.db + + execsql { + PRAGMA key = 'testkey'; + CREATE TABLE t2(a,b); + INSERT INTO t2 VALUES ('test1', 'test2'); + } db2 + + catchsql { + ATTACH 'test.db' AS db; + } db2 + +} {1 {file is encrypted or is not a database}} +db2 close +file delete -force test.db +file delete -force test2.db + # attach an encrypted database # where both database have the same -# key +# key explicitly setup test.db "'testkey'" do_test attach-database-with-same-key { sqlite_orig db2 test2.db @@ -422,7 +443,7 @@ do_test attach-database-with-same-key { execsql { SELECT count(*) FROM t2; - ATTACH 'test.db' AS db; + ATTACH 'test.db' AS db KEY 'testkey'; SELECT count(*) FROM db.t1; } db2 @@ -607,7 +628,7 @@ file delete -force test.db # create an unencrypted database, attach a new encrypted volume # copy data between, verify the encypted database is good afterwards -do_test unencryped-attach { +do_test unencrypted-attach { sqlite_orig db test.db execsql { @@ -641,7 +662,7 @@ file delete -force test2.db # create an unencrypted database, attach a new encrypted volume # using a raw key copy data between, verify the encypted # database is good afterwards -do_test unencryped-attach-raw-key { +do_test unencrypted-attach-raw-key { sqlite_orig db test.db execsql { @@ -672,9 +693,45 @@ db2 close file delete -force test.db file delete -force test2.db +# create an encrypted database, attach an default-key encrypted volume +# copy data between, verify the second database +do_test encrypted-attach-default-key { + sqlite_orig db test.db + + execsql { + PRAGMA key='testkey'; + CREATE TABLE t1(a,b); + BEGIN; + } + + for {set i 1} {$i<=1000} {incr i} { + set r [expr {int(rand()*500000)}] + execsql "INSERT INTO t1 VALUES($i,$r);" + } + + execsql { + COMMIT; + ATTACH DATABASE 'test2.db' AS test; + CREATE TABLE test.t1(a,b); + INSERT INTO test.t1 SELECT * FROM t1; + DETACH DATABASE test; + } + + sqlite_orig db2 test2.db + + execsql { + PRAGMA key='testkey'; + SELECT count(*) FROM t1; + } db2 +} {1000} +db close +db2 close +file delete -force test.db +file delete -force test2.db + # create an encrypted database, attach an unencrypted volume # copy data between, verify the unencypted database is good afterwards -do_test encryped-attach-unencrypted { +do_test encrypted-attach-unencrypted { sqlite_orig db test.db execsql { @@ -683,7 +740,7 @@ do_test encryped-attach-unencrypted { sqlite_orig db2 test2.db execsql { - PRAGMA key='testkey'; + PRAGMA key = 'testkey'; CREATE TABLE t1(a,b); BEGIN; } db2 @@ -711,7 +768,7 @@ file delete -force test2.db # create an unencrypted database, attach an unencrypted volume # copy data between, verify the unencypted database is good afterwards -do_test unencryped-attach-unencrypted { +do_test unencrypted-attach-unencrypted { sqlite_orig db test.db execsql { @@ -879,6 +936,7 @@ do_test open-1.1.8-database { execsql { PRAGMA key = 'testkey'; PRAGMA cipher_use_hmac = OFF; + PRAGMA kdf_iter = 4000; SELECT count(*) FROM t1; SELECT * FROM t1; } @@ -893,6 +951,7 @@ do_test attach-and-copy-1.1.8 { execsql { PRAGMA key = 'testkey'; PRAGMA cipher_use_hmac = OFF; + PRAGMA kdf_iter = 4000; ATTACH DATABASE 'test.db' AS db2 KEY 'testkey-hmac'; CREATE TABLE db2.t1(a,b); INSERT INTO db2.t1 SELECT * FROM main.t1; @@ -1350,20 +1409,22 @@ do_test cipher-options-before-keys { db close file delete -force test.db -# open a 1.1.8 database (no HMAC), then +# open a 1.1.8 database (no HMAC, 4K iter), then # try to open another 1.1.8 database. The # attached database should have the same hmac # setting as the original -do_test default-use-hmac-attach { +do_test default-hmac-kdf-attach { file copy -force sqlcipher-1.1.8-testkey.db test.db sqlite_orig db test.db execsql { PRAGMA cipher_default_use_hmac = OFF; + PRAGMA cipher_default_kdf_iter = 4000; PRAGMA key = 'testkey'; SELECT count(*) FROM t1; - ATTACH 'sqlcipher-1.1.8-testkey.db' AS db2; + ATTACH 'sqlcipher-1.1.8-testkey.db' AS db2 KEY 'testkey'; SELECT count(*) from db2.t1; PRAGMA cipher_default_use_hmac = ON; + PRAGMA cipher_default_kdf_iter = 64000; } } {4 4} db close @@ -1378,18 +1439,18 @@ do_test attach-1.1.8-database-from-2.0-fails { catchsql { PRAGMA key = 'testkey'; CREATE table t1(a,b); - ATTACH 'sqlcipher-1.1.8-testkey.db' AS db2; + ATTACH 'sqlcipher-1.1.8-testkey.db' AS db2 KEY 'testkey'; } } {1 {file is encrypted or is not a database}} db close file delete -force test.db -# open a 2.0 database (with HMAC), then +# open a 2.0 database (with HMAC, 4k iter), then # set the default hmac setting to OFF. # try to a 1.1.8 database. this should # succeed now that hmac is off by default # before the attach -do_test change-default-use-hmac-attach { +do_test change-default-hmac-kdf-attach { sqlite_orig db test.db execsql { PRAGMA key = 'testkey'; @@ -1402,9 +1463,11 @@ do_test change-default-use-hmac-attach { PRAGMA key = 'testkey'; SELECT count(*) FROM t1; PRAGMA cipher_default_use_hmac = OFF; - ATTACH 'sqlcipher-1.1.8-testkey.db' AS db2; + PRAGMA cipher_default_kdf_iter = 4000; + ATTACH 'sqlcipher-1.1.8-testkey.db' AS db2 KEY 'testkey'; SELECT count(*) from db2.t1; PRAGMA cipher_default_use_hmac = ON; + PRAGMA cipher_default_kdf_iter = 64000; } } {1 4} db close @@ -1580,12 +1643,25 @@ do_test multipage-schema-autovacuum-shortread-wal { db close file delete -force test.db +# open a 2.3 database with little endian hmac page numbers (default) +# verify it can be opened +do_test open-2.3-le-database { + sqlite_orig db sqlcipher-2.3-testkey.db + execsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM t1; + SELECT * FROM t1; + } +} {4 1 1 one one 1 2 one two} +db close + # open a 2.0 database with little endian hmac page numbers (default) # verify it can be opened do_test open-2.0-le-database { sqlite_orig db sqlcipher-2.0-le-testkey.db execsql { PRAGMA key = 'testkey'; + PRAGMA kdf_iter = 4000; SELECT count(*) FROM t1; SELECT * FROM t1; } @@ -1599,6 +1675,7 @@ do_test open-2.0-be-database { execsql { PRAGMA key = 'testkey'; PRAGMA cipher_hmac_pgno = be; + PRAGMA kdf_iter = 4000; SELECT count(*) FROM t1; SELECT * FROM t1; } @@ -1615,6 +1692,7 @@ do_test be-to-le-migration { execsql { PRAGMA key = 'testkey'; PRAGMA cipher_hmac_pgno = be; + PRAGMA kdf_iter = 4000; ATTACH DATABASE 'test.db' AS db2 KEY 'testkey'; CREATE TABLE db2.t1(a,b); INSERT INTO db2.t1 SELECT * FROM main.t1; @@ -1684,6 +1762,31 @@ do_test verify-pragma-cipher-default-use-hmac-off { db close file delete -force test.db +# verify the pragma default_cipher_kdf_iter +# is set to 64000 by default +do_test verify-pragma-cipher-default-kdf-iter-default { + sqlite_orig db test.db + execsql { + PRAGMA cipher_default_kdf_iter; + } +} {64000} +db close +file delete -force test.db + + +# verify the pragma default_cipher_kdf_ter +# reports changes +do_test verify-pragma-cipher-default-use-hmac-off { + sqlite_orig db test.db + execsql { + PRAGMA cipher_default_kdf_iter = 1000; + PRAGMA cipher_default_kdf_iter; + PRAGMA cipher_default_kdf_iter = 64000; + } +} {1000} +db close +file delete -force test.db + # verify the pragma kdf_iter # reports the default value do_test verify-pragma-kdf-iter-reports-default { @@ -1692,7 +1795,7 @@ do_test verify-pragma-kdf-iter-reports-default { PRAGMA key = 'test'; PRAGMA kdf_iter; } -} {4000} +} {64000} db close file delete -force test.db @@ -1843,6 +1946,7 @@ do_test open-2.0-beta-database { sqlite_orig db sqlcipher-2.0-beta-testkey.db execsql { PRAGMA key = 'testkey'; + PRAGMA kdf_iter = 4000; PRAGMA fast_kdf_iter = 4000; PRAGMA cipher_hmac_salt_mask = "x'00'"; SELECT count(*) FROM t1; @@ -1861,6 +1965,7 @@ do_test 2.0-beta-to-2.0-migration { execsql { PRAGMA key = 'testkey'; PRAGMA cipher_hmac_salt_mask = "x'00'"; + PRAGMA kdf_iter = 4000; PRAGMA fast_kdf_iter = 4000; SELECT count(*) FROM sqlite_master; @@ -1902,4 +2007,40 @@ if_built_with_commoncrypto verify-default-cipher { db close file delete -force test.db +do_test migrate-1.1.8-database-to-3x-format { + file copy -force sqlcipher-1.1.8-testkey.db test.db + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_migrate; + } + db close + + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM sqlite_master; + } +} {1} +db close +file delete -force test.db + +do_test migrate-2-0-le-database-to-3x-format { + file copy -force sqlcipher-2.0-le-testkey.db test.db + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + PRAGMA cipher_migrate; + } + db close + + sqlite_orig db test.db + execsql { + PRAGMA key = 'testkey'; + SELECT count(*) FROM sqlite_master; + } +} {1} +db close +file delete -force test.db + finish_test