Skip to content

Commit cd0951d

Browse files
authored
Enable encryption for attached databases (#2155)
2 parents 6671a31 + b2cd5b0 commit cd0951d

File tree

7 files changed

+157
-0
lines changed

7 files changed

+157
-0
lines changed

.github/workflows/rust.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ jobs:
130130

131131
- name: Run tests
132132
run: cargo nextest run
133+
134+
- name: Run libsql encryption tests
135+
run: cargo test --features encryption --color=always --test encryption test_encryption
133136
# test-custom-pager:
134137
# runs-on: ubuntu-latest
135138
# name: Run Tests

libsql-ffi/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ fn build_multiple_ciphers(out_path: &Path) -> PathBuf {
494494
if cfg!(feature = "wasmtime-bindings") {
495495
config.define("LIBSQL_ENABLE_WASM_RUNTIME", "1");
496496
}
497+
config.define("LIBSQL_ENCRYPTION", "1");
497498

498499
if cfg!(feature = "session") {
499500
config

libsql-ffi/bundled/SQLite3MultipleCiphers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ set(SQLITE3MC_BASE_DEFINITIONS
124124
$<$<BOOL:${LIBSQL_ENABLE_WASM_RUNTIME}>:LIBSQL_ENABLE_WASM_RUNTIME=1>
125125
LIBSQL_EXTRA_PRAGMAS=1
126126
LIBSQL_CUSTOM_PAGER_CODEC=1
127+
LIBSQL_ENCRYPTION=1
127128

128129
SQLITE_ENABLE_DBSTAT_VTAB=1
129130
SQLITE_ENABLE_DBPAGE_VTAB=1

libsql-ffi/bundled/src/sqlite3.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121872,6 +121872,10 @@ SQLITE_PRIVATE int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName){
121872121872
int libsql_handle_extra_attach_params(sqlite3* db, const char* zName, const char* zPath, sqlite3_value* pKey, char** zErrDyn);
121873121873
#endif
121874121874

121875+
#ifdef LIBSQL_ENCRYPTION
121876+
SQLITE_PRIVATE int sqlite3mcHandleAttachKey(sqlite3*, const char*, const char*, sqlite3_value*, char**);
121877+
#endif
121878+
121875121879
/*
121876121880
** An SQL user-function registered to do the work of an ATTACH statement. The
121877121881
** three arguments to the function come directly from an attach statement:
@@ -122031,6 +122035,16 @@ static void attachFunc(
122031122035
rc = libsql_handle_extra_attach_params(db, zName, zPath, argv, &zErrDyn);
122032122036
}
122033122037
#endif
122038+
122039+
#ifdef LIBSQL_ENCRYPTION
122040+
/* If the ATTACH statement came with key parameter, then lets handle it here. */
122041+
if( rc==SQLITE_OK ){
122042+
if( argv != NULL && argv[0] != NULL && argv[1] != NULL && argv[2] != NULL ){
122043+
rc = sqlite3mcHandleAttachKey(db, zName, zPath, argv[2], &zErrDyn);
122044+
}
122045+
}
122046+
#endif
122047+
122034122048
sqlite3_free_filename( zPath );
122035122049

122036122050
/* If the file was opened successfully, read the schema for the new database.

libsql-sqlite3/src/attach.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ int sqlite3DbIsNamed(sqlite3 *db, int iDb, const char *zName){
6161
int libsql_handle_extra_attach_params(sqlite3* db, const char* zName, const char* zPath, sqlite3_value* pKey, char** zErrDyn);
6262
#endif
6363

64+
#ifdef LIBSQL_ENCRYPTION
65+
SQLITE_PRIVATE int sqlite3mcHandleAttachKey(sqlite3*, const char*, const char*, sqlite3_value*, char**);
66+
#endif
67+
6468
/*
6569
** An SQL user-function registered to do the work of an ATTACH statement. The
6670
** three arguments to the function come directly from an attach statement:
@@ -220,6 +224,16 @@ static void attachFunc(
220224
rc = libsql_handle_extra_attach_params(db, zName, zPath, argv, &zErrDyn);
221225
}
222226
#endif
227+
228+
#ifdef LIBSQL_ENCRYPTION
229+
/* If the ATTACH statement came with key parameter, then lets handle it here. */
230+
if( rc==SQLITE_OK ){
231+
if( argv != NULL && argv[0] != NULL && argv[1] != NULL && argv[2] != NULL ){
232+
rc = sqlite3mcHandleAttachKey(db, zName, zPath, argv[2], &zErrDyn);
233+
}
234+
}
235+
#endif
236+
223237
sqlite3_free_filename( zPath );
224238

225239
/* If the file was opened successfully, read the schema for the new database.
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Example of showing using an encrypted local database with libsql. It also shows how to
2+
// attach another encrypted database. The example expects a local `world.db` encrypted database
3+
// to be present in the same directory.
4+
5+
use libsql::{params, Builder};
6+
use libsql::{Cipher, EncryptionConfig};
7+
8+
#[tokio::main]
9+
async fn main() {
10+
tracing_subscriber::fmt::init();
11+
12+
// The local database path where the data will be stored.
13+
let db_path = std::env::var("LIBSQL_DB_PATH").unwrap();
14+
// The encryption key for the database.
15+
let encryption_key = std::env::var("LIBSQL_ENCRYPTION_KEY").unwrap_or("s3cR3t".to_string());
16+
17+
let mut db_builder = Builder::new_local(db_path);
18+
19+
db_builder = db_builder.encryption_config(EncryptionConfig {
20+
cipher: Cipher::Aes256Cbc,
21+
encryption_key: encryption_key.into(),
22+
});
23+
24+
let db = db_builder.build().await.unwrap();
25+
let conn = db.connect().unwrap();
26+
conn.execute(
27+
"CREATE TABLE IF NOT EXISTS guest_book_entries (text TEXT)",
28+
(),
29+
)
30+
.await
31+
.unwrap();
32+
33+
// let's attach another encrypted database and print its contents
34+
conn.execute("ATTACH DATABASE 'world.db' AS world KEY s3cR3t", ())
35+
.await
36+
.unwrap();
37+
38+
let mut attached_results = conn
39+
.query("SELECT * FROM world.guest_book_entries", ())
40+
.await
41+
.unwrap();
42+
43+
println!("attached database guest book entries:");
44+
while let Some(row) = attached_results.next().await.unwrap() {
45+
let text: String = row.get(0).unwrap();
46+
println!(" {}", text);
47+
}
48+
49+
let mut input = String::new();
50+
println!("Please write your entry to the guestbook:");
51+
match std::io::stdin().read_line(&mut input) {
52+
Ok(_) => {
53+
println!("You entered: {}", input);
54+
let params = params![input.as_str()];
55+
conn.execute("INSERT INTO guest_book_entries (text) VALUES (?)", params)
56+
.await
57+
.unwrap();
58+
}
59+
Err(error) => {
60+
eprintln!("Error reading input: {}", error);
61+
}
62+
}
63+
let mut results = conn
64+
.query("SELECT * FROM guest_book_entries", ())
65+
.await
66+
.unwrap();
67+
println!("Guest book entries:");
68+
while let Some(row) = results.next().await.unwrap() {
69+
let text: String = row.get(0).unwrap();
70+
println!(" {}", text);
71+
}
72+
}

libsql/tests/encryption.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use libsql::{params, Builder};
2+
use libsql_sys::{Cipher, EncryptionConfig};
3+
4+
#[tokio::test]
5+
#[cfg(feature = "encryption")]
6+
async fn test_encryption() {
7+
let tempdir = std::env::temp_dir();
8+
let encrypted_path = tempdir.join("encrypted.db");
9+
let base_path = tempdir.join("base.db");
10+
11+
// lets create an encrypted database
12+
{
13+
let mut db_builder = Builder::new_local(&encrypted_path);
14+
db_builder = db_builder.encryption_config(EncryptionConfig {
15+
cipher: Cipher::Aes256Cbc,
16+
encryption_key: "s3cR3t".into(),
17+
});
18+
let db = db_builder.build().await.unwrap();
19+
20+
let conn = db.connect().unwrap();
21+
conn.execute("CREATE TABLE IF NOT EXISTS messages (text TEXT)", ())
22+
.await
23+
.unwrap();
24+
let params = params!["the only winning move is not to play"];
25+
conn.execute("INSERT INTO messages (text) VALUES (?)", params)
26+
.await
27+
.unwrap();
28+
}
29+
30+
// lets test encryption with ATTACH
31+
{
32+
let db = Builder::new_local(&base_path).build().await.unwrap();
33+
let conn = db.connect().unwrap();
34+
let attach_stmt = format!(
35+
"ATTACH DATABASE '{}' AS encrypted KEY 's3cR3t'",
36+
tempdir.join("encrypted.db").display()
37+
);
38+
conn.execute(&attach_stmt, ()).await.unwrap();
39+
let mut attached_results = conn
40+
.query("SELECT * FROM encrypted.messages", ())
41+
.await
42+
.unwrap();
43+
let row = attached_results.next().await.unwrap().unwrap();
44+
let text: String = row.get(0).unwrap();
45+
assert_eq!(text, "the only winning move is not to play");
46+
}
47+
48+
{
49+
let _ = std::fs::remove_file(&encrypted_path);
50+
let _ = std::fs::remove_file(&base_path);
51+
}
52+
}

0 commit comments

Comments
 (0)