Skip to content

Commit a2e9dd4

Browse files
authored
Update clients to interact with (new) server encrypted databases (#2113)
This patch enables client to send encryption key header for encrypted databases. The header we are required to send is `x-turso-encryption-key`. To connect with encrypted database, either remote or offline write, you may use the builder pattern to provide the encryption context. Ex.: ```rust let encryption = if let Ok(key) = std::env::var("LIBSQL_ENCRYPTION_KEY") { Some(EncryptionContext { key: EncryptionKey::Base64Encoded(key), }) } else { None }; let db_builder = Builder::new_synced_database(db_path, sync_url, auth_token).remote_encryption(encryption); ``` This patch also comes with a full example: https://github.com/avinassh/libsql/blob/5b95a1928b821855fced3880caf7871ce032c58b/libsql/examples/encryption_sync.rs
2 parents cf0fa74 + 3c42428 commit a2e9dd4

File tree

9 files changed

+237
-10
lines changed

9 files changed

+237
-10
lines changed

libsql/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ sync = [
102102
"stream",
103103
"remote",
104104
"replication",
105+
"dep:base64",
105106
"dep:tower",
106107
"dep:hyper",
107108
"dep:http",
@@ -131,6 +132,7 @@ hrana = [
131132
serde = ["dep:serde"]
132133
remote = [
133134
"hrana",
135+
"dep:base64",
134136
"dep:tower",
135137
"dep:hyper",
136138
"dep:hyper",

libsql/examples/encryption_sync.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Example of using offline writes with encryption
2+
3+
use libsql::{params, Builder};
4+
use libsql::{EncryptionContext, EncryptionKey};
5+
6+
#[tokio::main]
7+
async fn main() {
8+
tracing_subscriber::fmt::init();
9+
10+
// The local database path where the data will be stored.
11+
let db_path = std::env::var("LIBSQL_DB_PATH").unwrap();
12+
13+
// The remote sync URL to use.
14+
let sync_url = std::env::var("LIBSQL_SYNC_URL").unwrap();
15+
16+
// The authentication token for the remote sync server.
17+
let auth_token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or("".to_string());
18+
19+
// Optional encryption key for the database, if provided.
20+
let encryption = if let Ok(key) = std::env::var("LIBSQL_ENCRYPTION_KEY") {
21+
Some(EncryptionContext {
22+
key: EncryptionKey::Base64Encoded(key),
23+
})
24+
} else {
25+
None
26+
};
27+
28+
let db_builder =
29+
Builder::new_synced_database(db_path, sync_url, auth_token).remote_encryption(encryption);
30+
31+
let db = match db_builder.build().await {
32+
Ok(db) => db,
33+
Err(error) => {
34+
eprintln!("Error connecting to remote sync server: {}", error);
35+
return;
36+
}
37+
};
38+
39+
let conn = db.connect().unwrap();
40+
41+
print!("Syncing with remote database...");
42+
db.sync().await.unwrap();
43+
println!(" done");
44+
45+
let mut results = conn.query("SELECT count(*) FROM dummy", ()).await.unwrap();
46+
let count: u32 = results.next().await.unwrap().unwrap().get(0).unwrap();
47+
println!("dummy table has {} entries", count);
48+
49+
conn.execute(
50+
r#"
51+
CREATE TABLE IF NOT EXISTS guest_book_entries (
52+
text TEXT
53+
)"#,
54+
(),
55+
)
56+
.await
57+
.unwrap();
58+
59+
let mut input = String::new();
60+
println!("Please write your entry to the guestbook:");
61+
match std::io::stdin().read_line(&mut input) {
62+
Ok(_) => {
63+
println!("You entered: {}", input);
64+
let params = params![input.as_str()];
65+
conn.execute("INSERT INTO guest_book_entries (text) VALUES (?)", params)
66+
.await
67+
.unwrap();
68+
}
69+
Err(error) => {
70+
eprintln!("Error reading input: {}", error);
71+
}
72+
}
73+
db.sync().await.unwrap();
74+
let mut results = conn
75+
.query("SELECT * FROM guest_book_entries", ())
76+
.await
77+
.unwrap();
78+
println!("Guest book entries:");
79+
while let Some(row) = results.next().await.unwrap() {
80+
let text: String = row.get(0).unwrap();
81+
println!(" {}", text);
82+
}
83+
}

libsql/src/database.rs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub use builder::Builder;
88
pub use libsql_sys::{Cipher, EncryptionConfig};
99

1010
use crate::{Connection, Result};
11+
#[cfg(any(feature = "remote", feature = "sync"))]
12+
use base64::{engine::general_purpose, Engine};
1113
use std::fmt;
1214
use std::sync::atomic::AtomicU64;
1315

@@ -100,6 +102,7 @@ enum DbType {
100102
auth_token: String,
101103
connector: crate::util::ConnectorService,
102104
_bg_abort: Option<std::sync::Arc<crate::sync::DropAbort>>,
105+
remote_encryption: Option<EncryptionContext>,
103106
},
104107
#[cfg(feature = "remote")]
105108
Remote {
@@ -108,6 +111,7 @@ enum DbType {
108111
connector: crate::util::ConnectorService,
109112
version: Option<String>,
110113
namespace: Option<String>,
114+
remote_encryption: Option<EncryptionContext>,
111115
},
112116
}
113117

@@ -214,7 +218,7 @@ cfg_replication! {
214218
endpoint,
215219
auth_token,
216220
https,
217-
encryption_config
221+
encryption_config,
218222
).await
219223
}
220224

@@ -524,7 +528,7 @@ cfg_remote! {
524528
url: impl Into<String>,
525529
auth_token: impl Into<String>,
526530
connector: C,
527-
version: Option<String>
531+
version: Option<String>,
528532
) -> Result<Self>
529533
where
530534
C: tower::Service<http::Uri> + Send + Clone + Sync + 'static,
@@ -544,6 +548,7 @@ cfg_remote! {
544548
connector: crate::util::ConnectorService::new(svc),
545549
version,
546550
namespace: None,
551+
remote_encryption: None
547552
},
548553
max_write_replication_index: Default::default(),
549554
})
@@ -677,6 +682,7 @@ impl Database {
677682
url,
678683
auth_token,
679684
connector,
685+
remote_encryption,
680686
..
681687
} => {
682688
use crate::{
@@ -708,6 +714,7 @@ impl Database {
708714
connector.clone(),
709715
None,
710716
None,
717+
remote_encryption.clone(),
711718
),
712719
read_your_writes: *read_your_writes,
713720
context: db.sync_ctx.clone().unwrap(),
@@ -730,6 +737,7 @@ impl Database {
730737
connector,
731738
version,
732739
namespace,
740+
remote_encryption,
733741
} => {
734742
let conn = std::sync::Arc::new(
735743
crate::hrana::connection::HttpConnection::new_with_connector(
@@ -738,6 +746,7 @@ impl Database {
738746
connector.clone(),
739747
version.as_ref().map(|s| s.as_str()),
740748
namespace.as_ref().map(|s| s.as_str()),
749+
remote_encryption.clone(),
741750
),
742751
);
743752

@@ -781,3 +790,29 @@ impl std::fmt::Debug for Database {
781790
f.debug_struct("Database").finish()
782791
}
783792
}
793+
794+
#[cfg(any(feature = "remote", feature = "sync"))]
795+
#[derive(Debug, Clone)]
796+
pub enum EncryptionKey {
797+
/// The key is a base64-encoded string.
798+
Base64Encoded(String),
799+
/// The key is a byte array.
800+
Bytes(Vec<u8>),
801+
}
802+
803+
#[cfg(any(feature = "remote", feature = "sync"))]
804+
impl EncryptionKey {
805+
pub fn as_string(&self) -> String {
806+
match self {
807+
EncryptionKey::Base64Encoded(s) => s.clone(),
808+
EncryptionKey::Bytes(b) => general_purpose::STANDARD.encode(b),
809+
}
810+
}
811+
}
812+
813+
#[cfg(any(feature = "remote", feature = "sync"))]
814+
#[derive(Debug, Clone)]
815+
pub struct EncryptionContext {
816+
/// The base64-encoded key for the encryption, sent on every request.
817+
pub key: EncryptionKey,
818+
}

0 commit comments

Comments
 (0)