Skip to content

Commit

Permalink
libsql: Make encryption cipher configurable
Browse files Browse the repository at this point in the history
Introduce a `EncryptionConfig` struct to configure both encrytion cipher
and key. Needed to support multiple ciphers.

Fixes #951
  • Loading branch information
penberg committed Feb 16, 2024
1 parent 8b8b1d4 commit eb189b0
Show file tree
Hide file tree
Showing 25 changed files with 272 additions and 147 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 18 additions & 7 deletions bottomless/src/replicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ use aws_sdk_s3::primitives::ByteStream;
use aws_sdk_s3::{Client, Config};
use bytes::{Buf, Bytes};
use chrono::{NaiveDateTime, TimeZone, Utc};
use libsql_sys::{Cipher, EncryptionConfig};
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use tokio::fs::{File, OpenOptions};
Expand Down Expand Up @@ -59,7 +61,7 @@ pub struct Replicator {
pub db_name: String,

use_compression: CompressionKind,
encryption_key: Option<Bytes>,
encryption_config: Option<EncryptionConfig>,
max_frames_per_batch: usize,
s3_upload_max_parallelism: usize,
join_set: JoinSet<()>,
Expand All @@ -85,7 +87,7 @@ pub struct Options {
pub verify_crc: bool,
/// Kind of compression algorithm used on the WAL frames to be sent to S3.
pub use_compression: CompressionKind,
pub encryption_key: Option<Bytes>,
pub encryption_config: Option<EncryptionConfig>,
pub aws_endpoint: Option<String>,
pub access_key_id: Option<String>,
pub secret_access_key: Option<String>,
Expand Down Expand Up @@ -181,6 +183,7 @@ impl Options {
let use_compression =
CompressionKind::parse(&env_var_or("LIBSQL_BOTTOMLESS_COMPRESSION", "zstd"))
.map_err(|e| anyhow!("unknown compression kind: {}", e))?;
let encryption_cipher = env_var("LIBSQL_BOTTOMLESS_ENCRYPTION_CIPHER").ok();
let encryption_key = env_var("LIBSQL_BOTTOMLESS_ENCRYPTION_KEY")
.map(Bytes::from)
.ok();
Expand All @@ -196,12 +199,20 @@ impl Options {
),
};
let s3_max_retries = env_var_or("LIBSQL_BOTTOMLESS_S3_MAX_RETRIES", 10).parse::<u32>()?;
let cipher = match encryption_cipher {
Some(cipher) => Cipher::from_str(&cipher)?,
None => Cipher::default(),
};
let encryption_config = match encryption_key {
Some(key) => Some(EncryptionConfig::new(cipher, key)),
None => None,
};
Ok(Options {
db_id,
create_bucket_if_not_exists: true,
verify_crc,
use_compression,
encryption_key,
encryption_config,
max_batch_interval,
max_frames_per_batch,
s3_upload_max_parallelism,
Expand Down Expand Up @@ -358,7 +369,7 @@ impl Replicator {
snapshot_waiter,
snapshot_notifier: Arc::new(snapshot_notifier),
use_compression: options.use_compression,
encryption_key: options.encryption_key,
encryption_config: options.encryption_config,
max_frames_per_batch: options.max_frames_per_batch,
s3_upload_max_parallelism: options.s3_upload_max_parallelism,
join_set,
Expand Down Expand Up @@ -697,7 +708,7 @@ impl Replicator {
flags,
Sqlite3WalManager::new(),
libsql_sys::connection::NO_AUTOCHECKPOINT, // no checkpointing
self.encryption_key.clone(),
self.encryption_config.clone(),
)?;
Ok(conn)
}
Expand Down Expand Up @@ -1323,12 +1334,12 @@ impl Replicator {
utc_time: Option<NaiveDateTime>,
db_path: &Path,
) -> Result<bool> {
let encryption_key = self.encryption_key.clone();
let encryption_config = self.encryption_config.clone();
let mut injector = libsql_replication::injector::Injector::new(
db_path,
4096,
libsql_sys::connection::NO_AUTOCHECKPOINT,
encryption_key,
encryption_config,
)?;
let prefix = format!("{}-{}/", self.db_name, generation);
let mut page_buf = {
Expand Down
1 change: 1 addition & 0 deletions libsql-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository = "https://github.com/tursodatabase/libsql"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
bytes = "1.5.0"
libsql-wasmtime-bindings = { version = "0.2.1", optional = true }

[build-dependencies]
Expand Down
46 changes: 46 additions & 0 deletions libsql-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_int;
use std::str::FromStr;

#[cfg(feature = "wasmtime-bindings")]
pub use libsql_wasm::{
Expand All @@ -15,6 +16,51 @@ pub use libsql_wasm::{

include!(concat!(env!("OUT_DIR"), "/bindgen.rs"));

#[derive(Clone, Debug)]
pub enum Cipher {
// AES 256 Bit CBC - No HMAC (wxSQLite3)
AES_256_CBC,
}

impl Default for Cipher {
fn default() -> Self {
Cipher::AES_256_CBC
}
}

impl FromStr for Cipher {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"aes256cbc" => Ok(Cipher::AES_256_CBC),
_ => Err(Error::new(SQLITE_MISUSE)),
}
}
}

#[derive(Clone, Debug)]
pub struct EncryptionConfig {
pub cipher: Cipher,
pub encryption_key: bytes::Bytes,
}

impl EncryptionConfig {
pub fn new(cipher: Cipher, encryption_key: bytes::Bytes) -> Self {
Self {
cipher,
encryption_key,
}
}

pub fn cipher_id(&self) -> c_int {
match self.cipher {
Cipher::AES_256_CBC => 2, // CODEC_TYPE_AES256
Cipher::SQLCIPHER => 4, // CODEC_TYPE_SQLCIPHER
}
}
}

#[must_use]
pub fn SQLITE_STATIC() -> sqlite3_destructor_type {
None
Expand Down
4 changes: 2 additions & 2 deletions libsql-replication/src/injector/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ impl Injector {
path: impl AsRef<Path>,
capacity: usize,
auto_checkpoint: u32,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<libsql_sys::EncryptionConfig>,
) -> Result<Self, Error> {
let buffer = FrameBuffer::default();
let wal_manager = InjectorWalManager::new(buffer.clone());
Expand All @@ -56,7 +56,7 @@ impl Injector {
| OpenFlags::SQLITE_OPEN_NO_MUTEX,
wal_manager,
auto_checkpoint,
encryption_key,
encryption_config,
)?;

Ok(Self {
Expand Down
11 changes: 8 additions & 3 deletions libsql-replication/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ pub mod snapshot;

mod error;

use libsql_sys::Cipher;

pub const LIBSQL_PAGE_SIZE: usize = 4096;

#[derive(Debug, Clone)]
Expand All @@ -16,22 +18,25 @@ pub struct FrameEncryptor {
}

impl FrameEncryptor {
pub fn new(key: bytes::Bytes) -> Self {
pub fn new(encryption_config: libsql_sys::EncryptionConfig) -> Self {
#[cfg(feature = "encryption")]
const SEED: u32 = 911;
#[cfg(not(feature = "encryption"))]
let _ = key;
let _ = encryption_config;

use aes::cipher::KeyIvInit;

// TODO: make cipher configurable
assert!(matches!(encryption_config.cipher, Cipher::AES_256_CBC));

#[allow(unused_mut)]
let mut iv: [u8; 16] = [0; 16];
#[allow(unused_mut)]
let mut digest: [u8; 32] = [0; 32];
#[cfg(feature = "encryption")]
libsql_sys::connection::generate_initial_vector(SEED, &mut iv);
#[cfg(feature = "encryption")]
libsql_sys::connection::generate_aes256_key(&key, &mut digest);
libsql_sys::connection::generate_aes256_key(&cfg.key, &mut digest);

let enc = cbc::Encryptor::new((&digest).into(), (&iv).into());
let dec = cbc::Decryptor::new((&digest).into(), (&iv).into());
Expand Down
4 changes: 2 additions & 2 deletions libsql-replication/src/replicator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ impl<C: ReplicatorClient> Replicator<C> {
client: C,
db_path: PathBuf,
auto_checkpoint: u32,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<libsql_sys::EncryptionConfig>,
) -> Result<Self, Error> {
let injector = {
let db_path = db_path.clone();
Expand All @@ -166,7 +166,7 @@ impl<C: ReplicatorClient> Replicator<C> {
db_path,
INJECTOR_BUFFER_CAPACITY,
auto_checkpoint,
encryption_key,
encryption_config,
)
})
.await??
Expand Down
5 changes: 3 additions & 2 deletions libsql-server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;
use anyhow::Context;
use hyper::client::HttpConnector;
use hyper_rustls::HttpsConnector;
use libsql_sys::EncryptionConfig;
use sha256::try_digest;
use tokio::time::Duration;
use tonic::transport::Channel;
Expand Down Expand Up @@ -125,7 +126,7 @@ pub struct DbConfig {
pub snapshot_exec: Option<String>,
pub checkpoint_interval: Option<Duration>,
pub snapshot_at_shutdown: bool,
pub encryption_key: Option<bytes::Bytes>,
pub encryption_config: Option<EncryptionConfig>,
pub max_concurrent_requests: u64,
}

Expand All @@ -143,7 +144,7 @@ impl Default for DbConfig {
snapshot_exec: None,
checkpoint_interval: None,
snapshot_at_shutdown: false,
encryption_key: None,
encryption_config: None,
max_concurrent_requests: 128,
}
}
Expand Down
19 changes: 10 additions & 9 deletions libsql-server/src/connection/libsql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;

use libsql_sys::wal::wrapper::{WrapWal, WrappedWal};
use libsql_sys::wal::{BusyHandler, CheckpointCallback, Wal, WalManager};
use libsql_sys::EncryptionConfig;
use metrics::{histogram, increment_counter};
use once_cell::sync::Lazy;
use parking_lot::{Mutex, RwLock};
Expand Down Expand Up @@ -44,7 +45,7 @@ pub struct MakeLibSqlConn<T: WalManager> {
/// In wal mode, closing the last database takes time, and causes other databases creation to
/// return sqlite busy. To mitigate that, we hold on to one connection
_db: Option<LibSqlConnection<T::Wal>>,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<EncryptionConfig>,
}

impl<T> MakeLibSqlConn<T>
Expand All @@ -63,7 +64,7 @@ where
max_total_response_size: u64,
auto_checkpoint: u32,
current_frame_no_receiver: watch::Receiver<Option<FrameNo>>,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<EncryptionConfig>,
) -> Result<Self> {
let mut this = Self {
db_path,
Expand All @@ -77,7 +78,7 @@ where
_db: None,
state: Default::default(),
wal_manager,
encryption_key,
encryption_config,
};

let db = this.try_create_db().await?;
Expand Down Expand Up @@ -126,7 +127,7 @@ where
max_size: Some(self.max_response_size),
max_total_size: Some(self.max_total_response_size),
auto_checkpoint: self.auto_checkpoint,
encryption_key: self.encryption_key.clone(),
encryption_config: self.encryption_config.clone(),
},
self.current_frame_no_receiver.clone(),
self.state.clone(),
Expand Down Expand Up @@ -235,7 +236,7 @@ pub fn open_conn<T>(
path: &Path,
wal_manager: T,
flags: Option<OpenFlags>,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<EncryptionConfig>,
) -> Result<libsql_sys::Connection<InhibitCheckpoint<T::Wal>>, rusqlite::Error>
where
T: WalManager,
Expand All @@ -252,7 +253,7 @@ where
flags,
wal_manager.wrap(InhibitCheckpointWalWrapper::new(false)),
u32::MAX,
encryption_key,
encryption_config,
)
}

Expand All @@ -262,7 +263,7 @@ pub fn open_conn_active_checkpoint<T>(
wal_manager: T,
flags: Option<OpenFlags>,
auto_checkpoint: u32,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<EncryptionConfig>,
) -> Result<libsql_sys::Connection<T::Wal>, rusqlite::Error>
where
T: WalManager,
Expand All @@ -279,7 +280,7 @@ where
flags,
wal_manager,
auto_checkpoint,
encryption_key,
encryption_config,
)
}

Expand Down Expand Up @@ -579,7 +580,7 @@ impl<W: Wal> Connection<W> {
wal_manager,
None,
builder_config.auto_checkpoint,
builder_config.encryption_key.clone(),
builder_config.encryption_config.clone(),
)?;

// register the lock-stealing busy handler
Expand Down
Loading

0 comments on commit eb189b0

Please sign in to comment.