Skip to content

Commit

Permalink
Make encryption cipher configurable and switch default to SQLCipher (#…
Browse files Browse the repository at this point in the history
…1038)

* libsql: Make encryption cipher configurable

Introduce a `EncryptionConfig` struct to configure both encrytion cipher
and key. Needed to support multiple ciphers.

Fixes #951

* libsql-ffi: Switch to SQLCipher as the default cipher

Fixes #893
  • Loading branch information
penberg authored Feb 19, 2024
1 parent db5d64c commit c4438e0
Show file tree
Hide file tree
Showing 23 changed files with 288 additions and 142 deletions.
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
2 changes: 1 addition & 1 deletion libsql-ffi/bundled/SQLite3MultipleCiphers/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ set(SQLITE3MC_BASE_DEFINITIONS
HAVE_CIPHER_AES_128_CBC=0
HAVE_CIPHER_AES_256_CBC=1
HAVE_CIPHER_CHACHA20=0
HAVE_CIPHER_SQLCIPHER=0
HAVE_CIPHER_SQLCIPHER=1
HAVE_CIPHER_RC4=0
HAVE_CIPHER_ASCON128=0
# $<$<BOOL:${SQLITE_USE_TCL}>:SQLITE_USE_TCL=1>
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::Aes256Cbc));

#[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(&encryption_config.encryption_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
11 changes: 6 additions & 5 deletions libsql-server/src/connection/write_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use libsql_replication::rpc::proxy::{
};
use libsql_replication::rpc::replication::NAMESPACE_METADATA_KEY;
use libsql_sys::wal::{Sqlite3Wal, Sqlite3WalManager};
use libsql_sys::EncryptionConfig;
use parking_lot::Mutex as PMutex;
use tokio::sync::{mpsc, watch, Mutex};
use tokio_stream::StreamExt;
Expand Down Expand Up @@ -44,7 +45,7 @@ pub struct MakeWriteProxyConn {
namespace: NamespaceName,
primary_replication_index: Option<FrameNo>,
make_read_only_conn: MakeLibSqlConn<Sqlite3WalManager>,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<EncryptionConfig>,
}

impl MakeWriteProxyConn {
Expand All @@ -61,7 +62,7 @@ impl MakeWriteProxyConn {
max_total_response_size: u64,
namespace: NamespaceName,
primary_replication_index: Option<FrameNo>,
encryption_key: Option<bytes::Bytes>,
encryption_config: Option<EncryptionConfig>,
) -> crate::Result<Self> {
let client = ProxyClient::with_origin(channel, uri);
let make_read_only_conn = MakeLibSqlConn::new(
Expand All @@ -74,7 +75,7 @@ impl MakeWriteProxyConn {
max_total_response_size,
DEFAULT_AUTO_CHECKPOINT,
applied_frame_no_receiver.clone(),
encryption_key.clone(),
encryption_config.clone(),
)
.await?;

Expand All @@ -87,7 +88,7 @@ impl MakeWriteProxyConn {
namespace,
make_read_only_conn,
primary_replication_index,
encryption_key,
encryption_config,
})
}
}
Expand All @@ -104,7 +105,7 @@ impl MakeConnection for MakeWriteProxyConn {
max_size: Some(self.max_response_size),
max_total_size: Some(self.max_total_response_size),
auto_checkpoint: DEFAULT_AUTO_CHECKPOINT,
encryption_key: self.encryption_key.clone(),
encryption_config: self.encryption_config.clone(),
},
self.namespace.clone(),
self.primary_replication_index,
Expand Down
4 changes: 2 additions & 2 deletions libsql-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ where
max_response_size: self.db_config.max_response_size,
max_total_response_size: self.db_config.max_total_response_size,
checkpoint_interval: self.db_config.checkpoint_interval,
encryption_key: self.db_config.encryption_key.clone(),
encryption_config: self.db_config.encryption_config.clone(),
max_concurrent_connections: Arc::new(Semaphore::new(self.max_concurrent_connections)),
scripted_backup,
max_concurrent_requests: self.db_config.max_concurrent_requests,
Expand Down Expand Up @@ -657,7 +657,7 @@ impl<C: Connector> Replica<C> {
base_path: self.base_path.clone(),
max_response_size: self.db_config.max_response_size,
max_total_response_size: self.db_config.max_total_response_size,
encryption_key: self.db_config.encryption_key.clone(),
encryption_config: self.db_config.encryption_config.clone(),
max_concurrent_connections: Arc::new(Semaphore::new(self.max_concurrent_connections)),
max_concurrent_requests: self.db_config.max_concurrent_requests,
};
Expand Down
11 changes: 8 additions & 3 deletions libsql-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use libsql_server::config::{
use libsql_server::net::AddrIncoming;
use libsql_server::Server;
use libsql_server::{connection::dump::exporter::export_dump, version::Version};
use libsql_sys::{Cipher, EncryptionConfig};

// Use system allocator for now, seems like we are getting too much fragmentation.
// #[global_allocator]
Expand Down Expand Up @@ -340,14 +341,18 @@ fn enable_libsql_logging() {
}

fn make_db_config(config: &Cli) -> anyhow::Result<DbConfig> {
let encryption_config = config.encryption_key.as_ref().map(|key| EncryptionConfig {
cipher: Cipher::Aes256Cbc,
encryption_key: key.clone(),
});
let mut bottomless_replication = config
.enable_bottomless_replication
.then(bottomless::replicator::Options::from_env)
.transpose()?;
// Inherit encryption key for bottomless from the db config, if not specified.
if let Some(ref mut bottomless_replication) = bottomless_replication {
if bottomless_replication.encryption_key.is_none() {
bottomless_replication.encryption_key = config.encryption_key.clone();
if bottomless_replication.encryption_config.is_none() {
bottomless_replication.encryption_config = encryption_config.clone();
}
}
Ok(DbConfig {
Expand All @@ -362,7 +367,7 @@ fn make_db_config(config: &Cli) -> anyhow::Result<DbConfig> {
snapshot_exec: config.snapshot_exec.clone(),
checkpoint_interval: config.checkpoint_interval_s.map(Duration::from_secs),
snapshot_at_shutdown: config.snapshot_at_shutdown,
encryption_key: config.encryption_key.clone(),
encryption_config: encryption_config.clone(),
max_concurrent_requests: config.max_concurrent_requests,
})
}
Expand Down
Loading

0 comments on commit c4438e0

Please sign in to comment.