Skip to content

Commit 955e93f

Browse files
authored
Merge pull request #7 from powersync-ja/sqlite-randomness
Configurable UUID randomness
2 parents 199c67c + dc0a748 commit 955e93f

File tree

7 files changed

+87
-9
lines changed

7 files changed

+87
-9
lines changed

UUID.md

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
### sqlite3 sqlite3_randomness
2+
3+
As part of the extension, we provide `uuid()` and `gen_random_uuid()` functions, which generate a UUIDv4. We want reasonable guarantees that these uuids are unguessable, so we need to use a [cryptographically secure pseudorandom number generator](https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator) (CSPRNG). Additionally, we want this to be fast (generating uuids shouldn't be a bottleneck for inserting rows).
4+
5+
We provide two options:
6+
7+
1. The `getrandom` crate, via the `getrandom` feature (enabled by default).
8+
2. `sqlite3_randomness`, when `getradnom` feature is not enabled.
9+
10+
Everywhere it's available, `getrandom` is recommended. One exception is WASI, where `sqlite3_randomness` is simpler. In that case, make sure the VFS xRandomness function provides secure random values.
11+
12+
## Details
13+
14+
SQLite has a sqlite3_randomness function, that does:
15+
16+
1. Seed using the VFS xRandomness function.
17+
2. Generate a sequence using ChaCha20 (CSPRNG, as long as the seed is sufficiently random).
18+
19+
The VFS implementations differ:
20+
21+
1. For unix (Linux, macOS, Android and iOS), the default VFS uses `/dev/urandom` for the xRandomness seed above. This is generally secure.
22+
2. For Windows, the default VFS uses a some system state such as pid, current time, current tick count for the entropy. This is okay for generating random numbers, but not quite secure (may be possible to guess).
23+
3. For wa-sqlite, it defaults to the unix VFS. Emscripten intercepts the `/dev/urandom` call and provides values using `crypto.getRandomValues()`: https://github.com/emscripten-core/emscripten/blob/2a00e26013b0a02411af09352c6731b89023f382/src/library.js#L2151-L2163
24+
4. Dart sqlite3 WASM uses `Random.secure()` to provide values. This is relatively slow, but only for the seed, so that's fine. This translates to `crypto.getRandomValues()` in JS.
25+
26+
### getrandom crate
27+
28+
The Rust uuid crate uses the getrandom crate for the "v4" feature by default.
29+
30+
Full platform support is listed here: https://docs.rs/getrandom/latest/getrandom/
31+
32+
Summary:
33+
34+
- Linux, Android: getrandom system call if available, falling batch to `/dev/urandom`.
35+
- Windows: [BCryptGenRandom](https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom)
36+
- macOS: [getentropy](https://www.unix.com/man-page/mojave/2/getentropy/)
37+
- iOS: [CCRandomGenerateBytes](https://opensource.apple.com/source/CommonCrypto/CommonCrypto-60074/include/CommonRandom.h.auto.html)
38+
- WASI (used for Dart sqlite3 WASM build): [random_get](https://wasix.org/docs/api-reference/wasi/random_get)
39+
40+
The WASI one may be problematic, since that's not provided in all environments.

crates/core/Cargo.toml

+6-3
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,19 @@ serde = { version = "1.0", default-features = false, features = ["alloc", "deriv
2323
[dependencies.uuid]
2424
version = "1.4.1"
2525
default-features = false
26-
features = [
27-
"v4"
28-
]
26+
features = []
2927

3028

3129
[dev-dependencies]
3230

3331

3432
[features]
33+
default = ["getrandom"]
34+
3535
loadable_extension = ["sqlite_nostd/loadable_extension"]
3636
static = ["sqlite_nostd/static"]
3737
omit_load_extension = ["sqlite_nostd/omit_load_extension"]
38+
# Enable to use the getrandom crate instead of sqlite3_randomness
39+
# Enable for Windows builds; do not enable for WASM
40+
getrandom = ["uuid/v4"]
3841

crates/core/src/operations.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use serde_json as json;
66

77
use sqlite_nostd as sqlite;
88
use sqlite_nostd::{Connection, ResultCode};
9-
use uuid::Uuid;
109
use crate::error::{SQLiteError, PSResult};
1110

1211
use crate::ext::SafeManagedStmt;
@@ -263,7 +262,7 @@ pub fn delete_pending_buckets(
263262
pub fn delete_bucket(
264263
db: *mut sqlite::sqlite3, name: &str) -> Result<(), SQLiteError> {
265264

266-
let id = Uuid::new_v4();
265+
let id = gen_uuid();
267266
let new_name = format!("$delete_{}_{}", name, id.hyphenated().to_string());
268267

269268
// language=SQLite

crates/core/src/util.rs

+20
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use serde_json as json;
88

99
use sqlite::{Connection, ResultCode};
1010
use sqlite_nostd as sqlite;
11+
use uuid::{Builder,Uuid};
1112
use sqlite_nostd::ManagedStmt;
1213

1314
use crate::error::SQLiteError;
@@ -84,6 +85,25 @@ pub fn deserialize_optional_string_to_i64<'de, D>(deserializer: D) -> Result<Opt
8485
}
8586
}
8687

88+
// Use getrandom crate to generate UUID.
89+
// This is not available in all WASM builds - use the default in those cases.
90+
#[cfg(feature = "getrandom")]
91+
pub fn gen_uuid() -> Uuid {
92+
let id = Uuid::new_v4();
93+
id
94+
}
95+
96+
// Default - use sqlite3_randomness to generate UUID
97+
// This uses ChaCha20 PRNG, with /dev/urandom as a seed on unix.
98+
// On Windows, it uses custom logic for the seed, which may not be secure.
99+
// Rather avoid this version for most builds.
100+
#[cfg(not(feature = "getrandom"))]
101+
pub fn gen_uuid() -> Uuid {
102+
let mut random_bytes: [u8; 16] = [0; 16];
103+
sqlite::randomness(&mut random_bytes);
104+
let id = Builder::from_random_bytes(random_bytes).into_uuid();
105+
id
106+
}
87107

88108
pub const MAX_OP_ID: &str = "9223372036854775807";
89109

crates/core/src/uuid.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ use core::slice;
99
use sqlite::ResultCode;
1010
use sqlite_nostd as sqlite;
1111
use sqlite_nostd::{Connection, Context};
12-
use uuid::Uuid;
1312

1413
use crate::create_sqlite_text_fn;
1514
use crate::error::SQLiteError;
15+
use crate::util::*;
16+
1617

1718
fn uuid_v4_impl(
1819
_ctx: *mut sqlite::context,
1920
_args: &[*mut sqlite::value],
2021
) -> Result<String, ResultCode> {
21-
let id = Uuid::new_v4();
22+
let id = gen_uuid();
2223
Ok(id.hyphenated().to_string())
2324
}
2425

crates/loadable/Cargo.toml

+6-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ name = "powersync"
1313
crate-type = ["cdylib"]
1414

1515
[dependencies]
16-
powersync_core = { path="../core" }
1716
sqlite_nostd = { workspace=true }
1817

18+
[dependencies.powersync_core]
19+
path = "../core"
20+
default-features = false
21+
features = []
22+
1923
[features]
20-
default = ["powersync_core/loadable_extension", "sqlite_nostd/loadable_extension"]
24+
default = ["powersync_core/loadable_extension", "sqlite_nostd/loadable_extension", "powersync_core/getrandom"]

crates/loadable/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@ pub fn __rust_alloc_error_handler(_: core::alloc::Layout) -> ! {
3737
core::intrinsics::abort()
3838
}
3939

40+
#[cfg(target_family = "wasm")]
41+
#[no_mangle]
42+
static __rust_alloc_error_handler_should_panic: u8 = 0;
43+
44+
#[cfg(target_family = "wasm")]
45+
#[no_mangle]
46+
static _CLOCK_PROCESS_CPUTIME_ID: i32 = 1;
47+
48+
#[cfg(target_family = "wasm")]
49+
#[no_mangle]
50+
static _CLOCK_THREAD_CPUTIME_ID: i32 = 1;
4051

4152
// Not used, but must be defined in some cases. Most notably when using native sqlite3 and loading
4253
// the extension.

0 commit comments

Comments
 (0)