442 lines
12 KiB
Plaintext
442 lines
12 KiB
Plaintext
# SQLCipher
|
|
# codec.test developed by Stephen Lombardo (Zetetic LLC)
|
|
# sjlombardo at zetetic dot net
|
|
# http://zetetic.net
|
|
#
|
|
# Copyright (c) 2018, ZETETIC LLC
|
|
# 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
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the ZETETIC LLC nor the
|
|
# names of its contributors may be used to endorse or promote products
|
|
# derived from this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY ZETETIC LLC ''AS IS'' AND ANY
|
|
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
# DISCLAIMED. IN NO EVENT SHALL ZETETIC LLC BE LIABLE FOR ANY
|
|
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
# This file implements regression tests for SQLite library. The
|
|
# focus of this script is testing code cipher features.
|
|
#
|
|
# NOTE: tester.tcl has overridden the definition of sqlite3 to
|
|
# automatically pass in a key value. Thus tests in this file
|
|
# should explicitly close and open db with sqlite_orig in order
|
|
# to bypass default key assignment.
|
|
|
|
set testdir [file dirname $argv0]
|
|
source $testdir/tester.tcl
|
|
source $testdir/sqlcipher.tcl
|
|
|
|
set old_pending_byte [sqlite3_test_control_pending_byte 0x40000000]
|
|
|
|
# 1. create a database and insert a bunch of data, close the database
|
|
# 2. seek to the middle of the first database page and write some junk
|
|
# 3. Open the database and verify that the database is no longer readable
|
|
do_test hmac-tamper-resistence-first-page {
|
|
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,'value $r');"
|
|
}
|
|
|
|
execsql {
|
|
COMMIT;
|
|
}
|
|
|
|
db close
|
|
|
|
# write some junk into the hmac segment, leaving
|
|
# the page data valid but with an invalid signature
|
|
hexio_write test.db 1000 000000
|
|
|
|
sqlite_orig db test.db
|
|
|
|
catchsql {
|
|
PRAGMA key = 'testkey';
|
|
SELECT count(*) FROM t1;
|
|
}
|
|
|
|
} {1 {file is not a database}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# 1. create a database and insert a bunch of data, close the database
|
|
# 2. seek to the middle of a database page and write some junk
|
|
# 3. Open the database and verify that the database is still readable
|
|
do_test nohmac-not-tamper-resistent {
|
|
sqlite_orig db test.db
|
|
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_use_hmac = OFF;
|
|
PRAGMA cipher_page_size = 1024;
|
|
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,'value $r');"
|
|
}
|
|
|
|
execsql {
|
|
COMMIT;
|
|
}
|
|
|
|
db close
|
|
|
|
# write some junk into the middle of the page
|
|
hexio_write test.db 2560 000000
|
|
|
|
sqlite_orig db test.db
|
|
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_use_hmac = OFF;
|
|
PRAGMA cipher_page_size = 1024;
|
|
SELECT count(*) FROM t1;
|
|
}
|
|
|
|
} {ok 1000}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# 1. create a database and insert a bunch of data, close the database
|
|
# 2. seek to the middle of a database page (not the first page) and write bad data
|
|
# 3. Open the database and verify that the database is no longer readable
|
|
do_test hmac-tamper-resistence {
|
|
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,'value $r');"
|
|
}
|
|
|
|
execsql {
|
|
COMMIT;
|
|
}
|
|
|
|
db close
|
|
|
|
# write some junk into the hmac segment, leaving
|
|
# the page data valid but with an invalid signature
|
|
hexio_write test.db 16500 000000
|
|
|
|
sqlite_orig db test.db
|
|
|
|
catchsql {
|
|
PRAGMA key = 'testkey';
|
|
SELECT count(*) FROM t1;
|
|
}
|
|
|
|
} {1 {database disk image is malformed}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# test that integrity checks work on a pristine
|
|
# newly created database
|
|
do_test integrity-check-clean-database {
|
|
sqlite_orig db test.db
|
|
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
CREATE table t1(a,b);
|
|
BEGIN;
|
|
}
|
|
|
|
for {set i 1} {$i<=10000} {incr i} {
|
|
execsql "INSERT INTO t1 VALUES($i,'value $i');"
|
|
}
|
|
|
|
execsql {
|
|
COMMIT;
|
|
}
|
|
|
|
db close
|
|
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_integrity_check;
|
|
PRAGMA integrity_check;
|
|
SELECT count(*) FROM t1;
|
|
}
|
|
|
|
} {ok ok 10000}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on an in-memory database
|
|
# which should fail because the file doesn't exist
|
|
do_test memory-integrity-check-should-fail {
|
|
sqlite_orig db :memory:
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
CREATE TABLE t1(a,b);
|
|
INSERT INTO t1(a,b) values (1,2);
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok {database file is undefined}}
|
|
db close
|
|
|
|
# try cipher_integrity_check on a valid 1.1.8 database
|
|
# should fail because version 1.0 doesn't use HMAC
|
|
do_test version-1-integrity-check-fail-no-hmac {
|
|
file copy -force $sampleDir/sqlcipher-1.1.8-testkey.db test.db
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_compatibility = 1;
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok {HMAC is not enabled, unable to integrity check}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a valid 2 database
|
|
do_test version-2-integrity-check-valid {
|
|
file copy -force $sampleDir/sqlcipher-2.0-le-testkey.db test.db
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_compatibility = 2;
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a corrupted version 2 database
|
|
do_test version-2-integrity-check-invalid {
|
|
file copy -force $sampleDir/sqlcipher-2.0-le-testkey.db test.db
|
|
hexio_write test.db 8202 000000
|
|
hexio_write test.db 10250 000000
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_compatibility = 2;
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok {HMAC verification failed for page 9} {HMAC verification failed for page 11}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a valid version 3 database
|
|
do_test version-3-integrity-check-valid {
|
|
file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_compatibility = 3;
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a corrupted version 3 database
|
|
do_test version-3-integrity-check-invalid {
|
|
file copy -force $sampleDir/sqlcipher-3.0-testkey.db test.db
|
|
hexio_write test.db 8202 000000
|
|
hexio_write test.db 10250 000000
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_compatibility = 3;
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok {HMAC verification failed for page 9} {HMAC verification failed for page 11}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a valid version 4 database
|
|
do_test version-4-integrity-check-valid {
|
|
file copy -force $sampleDir/sqlcipher-4.0-testkey.db test.db
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a corrupted version 4 database
|
|
do_test version-4-integrity-check-invalid {
|
|
file copy -force $sampleDir/sqlcipher-4.0-testkey.db test.db
|
|
# corrupt page data
|
|
hexio_write test.db 5120 000000
|
|
# corrupt iv
|
|
hexio_write test.db 12208 000000
|
|
# corrupt the mac segment
|
|
hexio_write test.db 16320 000000
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok {HMAC verification failed for page 2} {HMAC verification failed for page 3} {HMAC verification failed for page 4}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# try cipher_integrity_check on a corrupted version 4 database
|
|
do_test version-4-integrity-check-invalid-last-page {
|
|
file copy -force $sampleDir/sqlcipher-4.0-testkey.db test.db
|
|
hexio_write test.db 978944 0000
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
} {ok {page 240 has an invalid size of 2 bytes}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
# verify cipher_integrity_check works on a plaintext header db
|
|
do_test integrity-check-plaintext-header {
|
|
sqlite_orig db test.db
|
|
set rc {}
|
|
|
|
execsql {
|
|
PRAGMA key = 'test';
|
|
PRAGMA cipher_plaintext_header_size = 32;
|
|
CREATE TABLE t1(a,b);
|
|
INSERT INTO t1(a,b) VALUES (1,2);
|
|
}
|
|
|
|
lappend rc [execsql {
|
|
PRAGMA cipher_integrity_check;
|
|
}]
|
|
|
|
lappend rc [string equal [hexio_read test.db 16 5] "1000010150"]
|
|
|
|
hexio_write test.db 120 000000
|
|
hexio_write test.db 5120 000000
|
|
|
|
lappend rc [execsql {
|
|
PRAGMA cipher_integrity_check;
|
|
}]
|
|
} {{} 1 {{HMAC verification failed for page 1} {HMAC verification failed for page 2}}}
|
|
file delete -force test.db
|
|
|
|
# verify that the default page size for the
|
|
# tests is 1024 (via makefile for testfixure. If
|
|
# the default pagesize is different it will breat
|
|
# the following tests
|
|
do_test default-page-size {
|
|
sqlite_orig db test.db
|
|
execsql {
|
|
CREATE TABLE t1(a,b);
|
|
PRAGMA page_size;
|
|
}
|
|
} {1024}
|
|
|
|
# zero out the 65th page, verify the behavior of
|
|
# pragma integrity check on a plaintext database
|
|
do_test integrity-check-one-page-plaintext {
|
|
sqlite_orig db $sampleDir/sqlcipher-4.0-testkey.db
|
|
set rc {}
|
|
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
ATTACH DATABASE 'plain-corrupt.db' AS plain KEY '';
|
|
SELECT sqlcipher_export('plain');
|
|
DETACH DATABASE plain;
|
|
}
|
|
|
|
db close
|
|
|
|
set txt ""
|
|
for {set i 0} {$i < 2048} {incr i} {
|
|
append txt "0"
|
|
}
|
|
|
|
hexio_write plain-corrupt.db 65536 $txt
|
|
|
|
sqlite_orig db plain-corrupt.db
|
|
|
|
set DB [sqlite3_connection_pointer db]
|
|
set VM [sqlite3_prepare $DB {PRAGMA integrity_check;} -1 TAIL]
|
|
while {[sqlite3_step $VM] == "SQLITE_ROW"} {
|
|
lappend rc [sqlite3_column_text $VM 0]
|
|
}
|
|
sqlite3_finalize $VM
|
|
|
|
lappend rc [catchsql {
|
|
PRAGMA integrity_check;
|
|
}]
|
|
} {{*** in database main ***
|
|
Page 65: btreeInitPage() returns error code 11} {1 {database disk image is malformed}}}
|
|
db close
|
|
file delete -force plain-corrupt.db
|
|
|
|
# introduce a corruption in the 17th page then verify that
|
|
# both integrity checks only reports one corrupt page
|
|
# (not that all subsequent pages corrupt). Behavior of
|
|
# PRAGMA integrity check should be consistent with the plaintext database
|
|
# save that the page number will be different because page size for
|
|
# sqlcipher is 4096 under the testfixture instead of 1024
|
|
do_test version-4-integrity-check-one-page {
|
|
file copy -force $sampleDir/sqlcipher-4.0-testkey.db test.db
|
|
|
|
set txt ""
|
|
for {set i 0} {$i < 8192} {incr i} {
|
|
append txt "0"
|
|
}
|
|
|
|
hexio_write test.db 65536 $txt
|
|
|
|
sqlite_orig db test.db
|
|
set rc {}
|
|
lappend rc [
|
|
execsql {
|
|
PRAGMA key = 'testkey';
|
|
PRAGMA cipher_integrity_check;
|
|
}
|
|
]
|
|
|
|
set DB [sqlite3_connection_pointer db]
|
|
set VM [sqlite3_prepare $DB {PRAGMA integrity_check;} -1 TAIL]
|
|
while {[sqlite3_step $VM] == "SQLITE_ROW"} {
|
|
lappend rc [sqlite3_column_text $VM 0]
|
|
}
|
|
sqlite3_finalize $VM
|
|
|
|
lappend rc [
|
|
catchsql {
|
|
PRAGMA integrity_check;
|
|
}
|
|
]
|
|
} {{ok {HMAC verification failed for page 17}} {*** in database main ***
|
|
Page 17: btreeInitPage() returns error code 11} {1 {database disk image is malformed}}}
|
|
db close
|
|
file delete -force test.db
|
|
|
|
sqlite3_test_control_pending_byte $old_pending_byte
|
|
|
|
finish_test
|