Trusty: provenance library workspace (initial import)

This commit is contained in:
2026-09-09 18:31:00 -04:00
commit 693f3bef1e
73 changed files with 26295 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
//! Trusty as a SQLite loadable extension (`cdylib`).
//!
//! Load into any SQLite database to get tamper-evidence without changing your
//! schema or language stack. Exposes SQL scalar functions backed by a shared,
//! lazily-initialized [`Trusty`](trusty_sqlite::Trusty) instance.
//!
//! # Loading
//! ```sql
//! .load ./trusty_sqlite_ext
//! SELECT trusty_init('/path/to/trusty.db', 'hex-encoded-secret-key');
//! ```
//!
//! # SQL functions
//! - `trusty_init(db_path, secret_key_hex)` -- initialize (call once)
//! - `trusty_append(entity_id, data_json, source_url)` -- append a record
//! - `trusty_verify(entity_id)` -- verify chain integrity, returns JSON report
//! - `trusty_anchor(entity_id)` -- write a SHA-256 anchor commitment
//! - `trusty_version()` -- returns crate version string
//!
//! # Safety invariants
//! Never panics across the FFI boundary. All fallible paths use rusqlite error
//! propagation. The only permitted unwraps are `Mutex::lock()` (poisoning is
//! terminal by design) and `serde_json::to_string` of always-serializable types.
// - Global Trusty state guarded by a Mutex; append is mutable.
// - Extension is non-persistent (re-registers per connection).
// ============================================================
use std::os::raw::{c_char, c_int};
use std::sync::Mutex;
use rusqlite::ffi;
use rusqlite::functions::FunctionFlags;
use rusqlite::{Connection, Error, Result};
use trusty_core::SecretKey;
use trusty_sqlite::Trusty;
// ------------------------------------------------------------
// Global extension state.
// One Trusty per process. Mutex because SQLite function callbacks
// are invoked in arbitrary threading contexts and append mutates.
// ------------------------------------------------------------
static STATE: Mutex<Option<Trusty>> = Mutex::new(None);
// ------------------------------------------------------------
// Extension entry point.
//
// SQLite's auto-loader for a library named `trusty_sqlite_ext`
// looks for `sqlite3_trustysqliteext_init` (underscores stripped,
// lowercased). We hand-write the entry point rather than rely on
// a macro that varies across rusqlite versions.
// ------------------------------------------------------------
/// # Safety
/// Called by sqlite3 at load time with valid, non-null `db` and
/// `p_api` pointers. `_pz_err_msg` is unused — we return
/// SQLITE_ERROR on failure and let sqlite3 surface a generic msg.
#[no_mangle]
pub unsafe extern "C" fn sqlite3_trustysqliteext_init(
db: *mut ffi::sqlite3,
_pz_err_msg: *mut *mut c_char,
p_api: *mut ffi::sqlite3_api_routines,
) -> c_int {
extension_entry(db, p_api)
}
/// Alias entry point so the extension also loads under the short
/// name `trusty_ext` (e.g. if a user renames the artifact). SQLite
/// would derive `sqlite3_trustyext_init` from `trusty_ext`.
///
/// # Safety
/// Same contract as [`sqlite3_trustysqliteext_init`].
#[no_mangle]
pub unsafe extern "C" fn sqlite3_trustyext_init(
db: *mut ffi::sqlite3,
_pz_err_msg: *mut *mut c_char,
p_api: *mut ffi::sqlite3_api_routines,
) -> c_int {
extension_entry(db, p_api)
}
/// Shared entry-point body. rusqlite 0.31's `extension_init2`
/// installs the SQLITE_EXTENSION_INIT2 api-routines table and
/// returns a non-owning `Connection`. We then register our
/// functions on it. The connection is `forget`-ed because SQLite
/// owns the underlying handle — dropping would double-close.
///
/// # Safety
/// `db` and `p_api` must be the valid pointers passed by SQLite at
/// extension-load time.
unsafe fn extension_entry(
db: *mut ffi::sqlite3,
p_api: *mut ffi::sqlite3_api_routines,
) -> c_int {
match Connection::extension_init2(db, p_api) {
Ok(conn) => match register_functions(&conn) {
Ok(()) => {
std::mem::forget(conn);
ffi::SQLITE_OK
}
Err(_) => ffi::SQLITE_ERROR,
},
Err(_) => ffi::SQLITE_ERROR,
}
}
// ------------------------------------------------------------
// Function registration.
// ------------------------------------------------------------
fn register_functions(db: &Connection) -> Result<()> {
// trusty_init(db_path TEXT, secret_key_hex TEXT) -> TEXT
db.create_scalar_function(
"trusty_init",
2,
FunctionFlags::SQLITE_UTF8,
|ctx| {
let path: String = ctx.get(0)?;
let key_hex: String = ctx.get(1)?;
let key_bytes = hex_decode_16(&key_hex).map_err(sqlite_err)?;
let key = SecretKey::new(key_bytes);
let trusty = Trusty::open(&path, key).map_err(sqlite_err)?;
*STATE.lock().unwrap() = Some(trusty);
Ok("ok".to_string())
},
)?;
// trusty_append(entity_id TEXT, data_json TEXT, source_url TEXT) -> TEXT
// NOT deterministic — each call mutates chain state.
db.create_scalar_function(
"trusty_append",
3,
FunctionFlags::SQLITE_UTF8,
|ctx| {
let entity_id: String = ctx.get(0)?;
let data_json: String = ctx.get(1)?;
let source_url: String = ctx.get(2)?;
let data: serde_json::Value =
serde_json::from_str(&data_json).map_err(sqlite_err)?;
let mut guard = STATE.lock().unwrap();
let trusty = guard.as_mut().ok_or_else(not_initialized)?;
let result = trusty
.append(&entity_id, &data, &source_url)
.map_err(sqlite_err)?;
// AppendResult is defined by trusty-sqlite and is always
// serializable; unwrap is documented-safe.
Ok(serde_json::to_string(&result).unwrap())
},
)?;
// trusty_verify(entity_id TEXT) -> TEXT
// Deterministic for a given chain state (read-only).
db.create_scalar_function(
"trusty_verify",
1,
FunctionFlags::SQLITE_UTF8,
|ctx| {
let entity_id: String = ctx.get(0)?;
let guard = STATE.lock().unwrap();
let trusty = guard.as_ref().ok_or_else(not_initialized)?;
let report = trusty.verify(&entity_id).map_err(sqlite_err)?;
Ok(serde_json::to_string(&report).unwrap())
},
)?;
// trusty_anchor(entity_id TEXT) -> TEXT
// Returns JSON of AnchorInfo, or the literal string "null" if
// no chain exists for entity_id.
db.create_scalar_function(
"trusty_anchor",
1,
FunctionFlags::SQLITE_UTF8,
|ctx| {
let entity_id: String = ctx.get(0)?;
let guard = STATE.lock().unwrap();
let trusty = guard.as_ref().ok_or_else(not_initialized)?;
let info = trusty.anchor(&entity_id).map_err(sqlite_err)?;
Ok(serde_json::to_string(&info).unwrap())
},
)?;
// trusty_version() -> TEXT
db.create_scalar_function(
"trusty_version",
0,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
|_ctx| Ok(env!("CARGO_PKG_VERSION").to_string()),
)?;
Ok(())
}
// ------------------------------------------------------------
// Helpers.
// ------------------------------------------------------------
/// Decode a 32-character hex string into a 16-byte key.
fn hex_decode_16(s: &str) -> std::result::Result<[u8; 16], String> {
if s.len() != 32 {
return Err(format!(
"secret key hex must be 32 chars, got {}",
s.len()
));
}
let mut out = [0u8; 16];
for i in 0..16 {
out[i] = u8::from_str_radix(&s[2 * i..2 * i + 2], 16)
.map_err(|e| format!("invalid hex at byte {}: {}", i, e))?;
}
Ok(out)
}
/// Wrap any Display error into a rusqlite user-function error.
fn sqlite_err<E: std::fmt::Display>(e: E) -> Error {
Error::UserFunctionError(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
e.to_string(),
)))
}
fn not_initialized() -> Error {
sqlite_err("trusty_init must be called before using trusty functions")
}