Skip to content

Commit 8a97782

Browse files
author
Szymon Zimnowoda
committed
Added tests for SQLCipher functionality
1 parent d9b8f81 commit 8a97782

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

Cargo.toml

+9
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ url = "2.2.2"
154154
rand = "0.8.4"
155155
rand_xoshiro = "0.6.0"
156156
hex = "0.4.3"
157+
tempdir = "0.3.7"
158+
# Needed to test SQLCipher
159+
libsqlite3-sys = { version = "*", default-features = false, features = ["bundled-sqlcipher"] }
160+
157161
#
158162
# Any
159163
#
@@ -206,6 +210,11 @@ name = "sqlite-derives"
206210
path = "tests/sqlite/derives.rs"
207211
required-features = ["sqlite", "macros"]
208212

213+
[[test]]
214+
name = "sqlcipher"
215+
path = "tests/sqlite/sqlcipher.rs"
216+
required-features = ["sqlite"]
217+
209218
#
210219
# MySQL
211220
#

tests/sqlite/sqlcipher.rs

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
use std::str::FromStr;
2+
3+
use sqlx::sqlite::SqliteQueryResult;
4+
use sqlx::{query, Connection, SqliteConnection};
5+
use sqlx::{sqlite::SqliteConnectOptions, ConnectOptions};
6+
use sqlx_rt::fs::File;
7+
use tempdir::TempDir;
8+
9+
async fn new_db_url() -> anyhow::Result<(String, TempDir)> {
10+
let dir = TempDir::new("sqlcipher_test")?;
11+
let filepath = dir.path().join("database.sqlite3");
12+
13+
// Touch the file, so DB driver will not complain it does not exist
14+
File::create(filepath.as_path()).await?;
15+
16+
Ok((format!("sqlite://{}", filepath.display()), dir))
17+
}
18+
19+
async fn fill_db(conn: &mut SqliteConnection) -> anyhow::Result<SqliteQueryResult> {
20+
conn.transaction(|tx| {
21+
Box::pin(async move {
22+
query(
23+
"
24+
CREATE TABLE Company(
25+
Id INT PRIMARY KEY NOT NULL,
26+
Name TEXT NOT NULL,
27+
Salary REAL
28+
);
29+
",
30+
)
31+
.execute(&mut *tx)
32+
.await?;
33+
34+
query(
35+
r#"
36+
INSERT INTO Company(Id, Name, Salary)
37+
VALUES
38+
(1, "aaa", 111),
39+
(2, "bbb", 222)
40+
"#,
41+
)
42+
.execute(tx)
43+
.await
44+
})
45+
})
46+
.await
47+
.map_err(|e| e.into())
48+
}
49+
50+
#[sqlx_macros::test]
51+
async fn it_encrypts() -> anyhow::Result<()> {
52+
let (url, _dir) = new_db_url().await?;
53+
54+
let mut conn = SqliteConnectOptions::from_str(&url)?
55+
.pragma("key", "the_password")
56+
.connect()
57+
.await?;
58+
59+
fill_db(&mut conn).await?;
60+
61+
// Create another connection without key, query should fail
62+
let mut conn = SqliteConnectOptions::from_str(&url)?.connect().await?;
63+
64+
assert!(conn
65+
.transaction(|tx| {
66+
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(tx).await })
67+
})
68+
.await
69+
.is_err());
70+
71+
Ok(())
72+
}
73+
74+
#[sqlx_macros::test]
75+
async fn it_can_store_and_read_encrypted_data() -> anyhow::Result<()> {
76+
let (url, _dir) = new_db_url().await?;
77+
78+
let mut conn = SqliteConnectOptions::from_str(&url)?
79+
.pragma("key", "the_password")
80+
.connect()
81+
.await?;
82+
83+
fill_db(&mut conn).await?;
84+
85+
// Create another connection with valid key
86+
let mut conn = SqliteConnectOptions::from_str(&url)?
87+
.pragma("key", "the_password")
88+
.connect()
89+
.await?;
90+
91+
let result = conn
92+
.transaction(|tx| {
93+
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(tx).await })
94+
})
95+
.await?;
96+
97+
assert!(result.len() > 0);
98+
99+
Ok(())
100+
}
101+
102+
#[sqlx_macros::test]
103+
async fn it_fails_if_password_is_incorrect() -> anyhow::Result<()> {
104+
let (url, _dir) = new_db_url().await?;
105+
106+
let mut conn = SqliteConnectOptions::from_str(&url)?
107+
.pragma("key", "the_password")
108+
.connect()
109+
.await?;
110+
111+
fill_db(&mut conn).await?;
112+
113+
// Connection with invalid key should not allow to execute queries
114+
let mut conn = SqliteConnectOptions::from_str(&url)?
115+
.pragma("key", "BADBADBAD")
116+
.connect()
117+
.await?;
118+
119+
assert!(conn
120+
.transaction(|tx| {
121+
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(tx).await })
122+
})
123+
.await
124+
.is_err());
125+
126+
Ok(())
127+
}
128+
129+
#[sqlx_macros::test]
130+
async fn it_honors_order_of_encryption_pragmas() -> anyhow::Result<()> {
131+
let (url, _dir) = new_db_url().await?;
132+
133+
// Make call of cipher configuration mixed with other pragmas,
134+
// it should have no effect, encryption related pragmas should be
135+
// executed first and allow to establish valid connection
136+
let mut conn = SqliteConnectOptions::from_str(&url)?
137+
.pragma("cipher_kdf_algorithm", "PBKDF2_HMAC_SHA1")
138+
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
139+
.pragma("cipher_page_size", "1024")
140+
.pragma("key", "the_password")
141+
.foreign_keys(true)
142+
.pragma("kdf_iter", "64000")
143+
.auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Incremental)
144+
.pragma("cipher_hmac_algorithm", "HMAC_SHA1")
145+
.connect()
146+
.await?;
147+
148+
fill_db(&mut conn).await?;
149+
150+
let mut conn = SqliteConnectOptions::from_str(&url)?
151+
.pragma("dummy", "pragma")
152+
// The cipher configuration set on first connection is
153+
// version 3 of SQLCipher, so for second it's enough to set
154+
// the compatibility mode.
155+
.pragma("cipher_compatibility", "3")
156+
.pragma("key", "the_password")
157+
.connect()
158+
.await?;
159+
160+
let result = conn
161+
.transaction(|tx| {
162+
Box::pin(async move { query("SELECT * FROM COMPANY;").fetch_all(tx).await })
163+
})
164+
.await?;
165+
166+
assert!(result.len() > 0);
167+
168+
Ok(())
169+
}
170+
171+
#[sqlx_macros::test]
172+
async fn it_allows_to_rekey_the_db() -> anyhow::Result<()> {
173+
let (url, _dir) = new_db_url().await?;
174+
175+
let mut conn = SqliteConnectOptions::from_str(&url)?
176+
.pragma("key", "the_password")
177+
.connect()
178+
.await?;
179+
180+
fill_db(&mut conn).await?;
181+
182+
// The 'pragma rekey' can be called at any time
183+
query("PRAGMA rekey = new_password;")
184+
.execute(&mut conn)
185+
.await?;
186+
187+
let mut conn = SqliteConnectOptions::from_str(&url)?
188+
.pragma("dummy", "pragma")
189+
.pragma("key", "new_password")
190+
.connect()
191+
.await?;
192+
193+
let result = conn
194+
.transaction(|tx| {
195+
Box::pin(async move { query("SELECT * FROM COMPANY;").fetch_all(tx).await })
196+
})
197+
.await?;
198+
199+
assert!(result.len() > 0);
200+
201+
Ok(())
202+
}

0 commit comments

Comments
 (0)