sqlx/tests/sqlite/sqlcipher.rs
Austin Bonander b5312c3b6f Break drivers out into separate crates, clean up some technical debt (#2039)
* WIP rt refactors

* refactor: break drivers out into separate crates

also cleans up significant technical debt
2023-02-21 13:25:25 -08:00

204 lines
5.5 KiB
Rust

use std::str::FromStr;
use sqlx::sqlite::SqliteQueryResult;
use sqlx::{query, Connection, SqliteConnection};
use sqlx::{sqlite::SqliteConnectOptions, ConnectOptions};
use tempdir::TempDir;
async fn new_db_url() -> anyhow::Result<(String, TempDir)> {
let dir = TempDir::new("sqlcipher_test")?;
let filepath = dir.path().join("database.sqlite3");
Ok((format!("sqlite://{}", filepath.display()), dir))
}
async fn fill_db(conn: &mut SqliteConnection) -> anyhow::Result<SqliteQueryResult> {
conn.transaction(|tx| {
Box::pin(async move {
query(
"
CREATE TABLE Company(
Id INT PRIMARY KEY NOT NULL,
Name TEXT NOT NULL,
Salary REAL
);
",
)
.execute(&mut **tx)
.await?;
query(
r#"
INSERT INTO Company(Id, Name, Salary)
VALUES
(1, "aaa", 111),
(2, "bbb", 222)
"#,
)
.execute(&mut **tx)
.await
})
})
.await
.map_err(|e| e.into())
}
#[sqlx_macros::test]
async fn it_encrypts() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// Create another connection without key, query should fail
let mut conn = SqliteConnectOptions::from_str(&url)?.connect().await?;
assert!(conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(&mut **tx).await })
})
.await
.is_err());
Ok(())
}
#[sqlx_macros::test]
async fn it_can_store_and_read_encrypted_data() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// Create another connection with valid key
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.connect()
.await?;
let result = conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(&mut **tx).await })
})
.await?;
assert!(result.len() > 0);
Ok(())
}
#[sqlx_macros::test]
async fn it_fails_if_password_is_incorrect() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// Connection with invalid key should not allow to execute queries
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "BADBADBAD")
.connect()
.await?;
assert!(conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM Company;").fetch_all(&mut **tx).await })
})
.await
.is_err());
Ok(())
}
#[sqlx_macros::test]
async fn it_honors_order_of_encryption_pragmas() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
// Make call of cipher configuration mixed with other pragmas,
// it should have no effect, encryption related pragmas should be
// executed first and allow to establish valid connection
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("cipher_kdf_algorithm", "PBKDF2_HMAC_SHA1")
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
.pragma("cipher_page_size", "1024")
.pragma("key", "the_password")
.foreign_keys(true)
.pragma("kdf_iter", "64000")
.auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Incremental)
.pragma("cipher_hmac_algorithm", "HMAC_SHA1")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("dummy", "pragma")
// The cipher configuration set on first connection is
// version 3 of SQLCipher, so for second it's enough to set
// the compatibility mode.
.pragma("cipher_compatibility", "3")
.pragma("key", "the_password")
.connect()
.await?;
let result = conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM COMPANY;").fetch_all(&mut **tx).await })
})
.await?;
assert!(result.len() > 0);
Ok(())
}
#[sqlx_macros::test]
async fn it_allows_to_rekey_the_db() -> anyhow::Result<()> {
let (url, _dir) = new_db_url().await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("key", "the_password")
.create_if_missing(true)
.connect()
.await?;
fill_db(&mut conn).await?;
// The 'pragma rekey' can be called at any time
query("PRAGMA rekey = new_password;")
.execute(&mut conn)
.await?;
let mut conn = SqliteConnectOptions::from_str(&url)?
.pragma("dummy", "pragma")
.pragma("key", "new_password")
.connect()
.await?;
let result = conn
.transaction(|tx| {
Box::pin(async move { query("SELECT * FROM COMPANY;").fetch_all(&mut **tx).await })
})
.await?;
assert!(result.len() > 0);
Ok(())
}