Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8792db9424 | ||
|
|
a6dd6d5dd8 | ||
|
|
78317b9cac | ||
|
|
f51402a867 |
6 changed files with 325 additions and 18 deletions
240
Cargo.lock
generated
240
Cargo.lock
generated
|
|
@ -2,6 +2,18 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "accessory"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87537f9ae7cfa78d5b8ebd1a1db25959f5e737126be4d8eb44a5452fc4b63cde"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
|
|
@ -373,6 +385,15 @@ dependencies = [
|
|||
"cipher 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bs58"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
|
|
@ -648,6 +669,15 @@ dependencies = [
|
|||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "4.1.3"
|
||||
|
|
@ -773,6 +803,18 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "delegate-display"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
|
|
@ -927,6 +969,18 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fancy_constructor"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07b19d0e43eae2bfbafe4931b5e79c73fb1a849ca15cd41a761a7b8587f9a1a2"
|
||||
dependencies = [
|
||||
"macroific",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.4.1"
|
||||
|
|
@ -1136,6 +1190,19 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gloo-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "growable-bloom-filter"
|
||||
version = "2.1.1"
|
||||
|
|
@ -1682,6 +1749,23 @@ dependencies = [
|
|||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexed_db_futures"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43315957678a70eb21fb0d2384fe86dde0d6c859a01e24ce127eb65a0143d28c"
|
||||
dependencies = [
|
||||
"accessory",
|
||||
"cfg-if",
|
||||
"delegate-display",
|
||||
"fancy_constructor",
|
||||
"js-sys",
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
|
|
@ -1883,6 +1967,53 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "macroific"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd"
|
||||
dependencies = [
|
||||
"macroific_attr_parse",
|
||||
"macroific_core",
|
||||
"macroific_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_attr_parse"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_core"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "macroific_macro"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae"
|
||||
dependencies = [
|
||||
"macroific_attr_parse",
|
||||
"macroific_core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
|
|
@ -1982,6 +2113,7 @@ dependencies = [
|
|||
"language-tags",
|
||||
"matrix-sdk-base",
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-indexeddb",
|
||||
"matrix-sdk-sqlite",
|
||||
"mime",
|
||||
"mime2ext",
|
||||
|
|
@ -2022,6 +2154,7 @@ dependencies = [
|
|||
"futures-util",
|
||||
"growable-bloom-filter",
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-store-encryption",
|
||||
"once_cell",
|
||||
"regex",
|
||||
|
|
@ -2054,9 +2187,81 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-crypto"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61bf6c3195de301c98339283413a4e9d9d63c4f214ef8955147643caab161256"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aquamarine",
|
||||
"as_variant",
|
||||
"async-trait",
|
||||
"bs58",
|
||||
"byteorder",
|
||||
"cfg-if",
|
||||
"ctr",
|
||||
"eyeball",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"itertools 0.14.0",
|
||||
"js_option",
|
||||
"matrix-sdk-common",
|
||||
"pbkdf2",
|
||||
"rand 0.8.6",
|
||||
"rmp-serde",
|
||||
"ruma",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"subtle",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"ulid",
|
||||
"url",
|
||||
"vodozemac",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-indexeddb"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2752015e69b6b56a8df72e52f91a834771259f755eb1c65232b77348ffb16b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"getrandom 0.2.17",
|
||||
"gloo-utils",
|
||||
"hkdf",
|
||||
"indexed_db_futures",
|
||||
"js-sys",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-store-encryption",
|
||||
"rmp-serde",
|
||||
"ruma",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matrix-sdk-sqlite"
|
||||
version = "0.14.0"
|
||||
|
|
@ -2068,6 +2273,7 @@ dependencies = [
|
|||
"deadpool-sqlite",
|
||||
"itertools 0.14.0",
|
||||
"matrix-sdk-base",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-store-encryption",
|
||||
"num_cpus",
|
||||
"rmp-serde",
|
||||
|
|
@ -2854,6 +3060,7 @@ dependencies = [
|
|||
"getrandom 0.2.17",
|
||||
"http",
|
||||
"indexmap",
|
||||
"js-sys",
|
||||
"js_int",
|
||||
"konst",
|
||||
"percent-encoding",
|
||||
|
|
@ -3112,6 +3319,17 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.19"
|
||||
|
|
@ -3465,10 +3683,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3477,6 +3697,16 @@ version = "0.1.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.8.3"
|
||||
|
|
@ -3801,6 +4031,16 @@ version = "1.8.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e36a83ea2b3c704935a01b4642946aadd445cea40b10935e3f8bd8052b8193d6"
|
||||
|
||||
[[package]]
|
||||
name = "ulid"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe"
|
||||
dependencies = [
|
||||
"rand 0.9.4",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
|
|
|
|||
|
|
@ -63,5 +63,6 @@ matrix-sdk = { version = "0.14", default-features = false, features = [
|
|||
"rustls-tls",
|
||||
"sqlite",
|
||||
"markdown",
|
||||
"e2e-encryption",
|
||||
] }
|
||||
futures-util = "0.3"
|
||||
|
|
|
|||
|
|
@ -119,8 +119,22 @@ Initial rollout settings:
|
|||
engage). The absent
|
||||
`yes_i_am_very_very_sure_…_open_registration_…` flag keeps the
|
||||
server closed to anyone without the token.
|
||||
- `allow_encryption = false` per operator call; E2EE re-enabling is
|
||||
deferred to a follow-up.
|
||||
- `allow_encryption` — server-side E2EE switch, sourced from
|
||||
`services.hyperhive.matrix.allowEncryption` (**default `false`**, opt-in).
|
||||
Off by default because on the hive-internal homeserver the operator
|
||||
already controls the transport; turn it on for encrypted rooms on
|
||||
external / federated homeservers or to keep contents opaque to the
|
||||
homeserver admin. **The agent matrix client always supports decryption
|
||||
regardless of this flag** — it uses the `e2e-encryption` feature of
|
||||
`matrix-sdk` so it can read encrypted rooms it is invited to even when
|
||||
this homeserver doesn't permit room encryption. Crypto keys are stored
|
||||
in the per-agent sqlite store under the state dir and persist across
|
||||
restarts (lost on `--purge`). `read_room` decrypts via
|
||||
`room.messages()` — UTD events surface as `event_type =
|
||||
"m.room.encrypted"` with `body = "[unable to decrypt]"`.
|
||||
Cross-signing and automatic key backup are not enabled for the first
|
||||
pass: static bearer-token bot accounts can't bootstrap cross-signing
|
||||
without MSC3967.
|
||||
|
||||
## Hive Matrix Space
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@
|
|||
//! UIAA flow already minted the token + user/device, hive-c0re just
|
||||
//! handed us the bearer in a file. matrix-sdk's `restore_session` with
|
||||
//! a constructed `MatrixSession` skips the login flow entirely.
|
||||
//!
|
||||
//! E2EE is enabled via `with_encryption_settings(EncryptionSettings::default())`.
|
||||
//! Crypto keys are persisted in the sqlite store under `state_dir`
|
||||
//! (survives container restarts, lost on `--purge`). Cross-signing and
|
||||
//! automatic key backup are deliberately left at their defaults (disabled)
|
||||
//! for the first pass: bot accounts authenticated with a static bearer token
|
||||
//! can't bootstrap cross-signing without MSC3967 on the server side.
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
|
|
@ -16,6 +23,7 @@ use anyhow::{Context, Result, anyhow};
|
|||
use matrix_sdk::{
|
||||
Client, SessionMeta, SessionTokens,
|
||||
authentication::matrix::MatrixSession,
|
||||
encryption::EncryptionSettings,
|
||||
ruma::{OwnedDeviceId, OwnedUserId},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -108,6 +116,7 @@ pub async fn build_and_restore(
|
|||
let client = Client::builder()
|
||||
.homeserver_url(homeserver)
|
||||
.sqlite_store(state_dir, None)
|
||||
.with_encryption_settings(EncryptionSettings::default())
|
||||
.build()
|
||||
.await
|
||||
.with_context(|| format!("build matrix client for {homeserver}"))?;
|
||||
|
|
|
|||
|
|
@ -116,6 +116,24 @@ fn extract_body(event: &matrix_sdk::ruma::events::AnyTimelineEvent) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Same as [`extract_body`] but for the `AnySyncTimelineEvent` variant
|
||||
/// returned by `TimelineEvent::raw()` (the sdk-level decrypted form from
|
||||
/// `room.messages()`).
|
||||
fn extract_body_sync(event: &matrix_sdk::ruma::events::AnySyncTimelineEvent) -> String {
|
||||
use matrix_sdk::ruma::events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent};
|
||||
match event {
|
||||
AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(ev)) => ev
|
||||
.as_original()
|
||||
.map_or_else(String::new, |orig| match &orig.content.msgtype {
|
||||
MessageType::Text(t) => t.body.clone(),
|
||||
MessageType::Notice(n) => n.body.clone(),
|
||||
MessageType::Emote(e) => format!("* {}", e.body),
|
||||
_ => String::new(),
|
||||
}),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn send_message(client: &Client, room_ref: &str, body: &str) -> DaemonResponse {
|
||||
let room = match resolve_room(client, room_ref).await {
|
||||
Ok(r) => r,
|
||||
|
|
@ -408,32 +426,37 @@ pub async fn invite_user(client: &Client, room_ref: &str, user_id: &str) -> Daem
|
|||
}
|
||||
|
||||
pub async fn read_room(client: &Client, room_ref: &str, limit: Option<usize>) -> DaemonResponse {
|
||||
use matrix_sdk::ruma::api::Direction;
|
||||
use matrix_sdk::ruma::api::client::message::get_message_events;
|
||||
use matrix_sdk::room::MessagesOptions;
|
||||
|
||||
let room = match resolve_room(client, room_ref).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return e,
|
||||
};
|
||||
let limit = limit.unwrap_or(50).min(200);
|
||||
let mut req =
|
||||
get_message_events::v3::Request::new(room.room_id().to_owned(), Direction::Backward);
|
||||
req.limit = matrix_sdk::ruma::UInt::try_from(limit as u64)
|
||||
.unwrap_or(matrix_sdk::ruma::UInt::from(50u32));
|
||||
let resp = match client.send(req).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return DaemonResponse::error(format!("get_message_events: {e}")),
|
||||
let limit_val = u32::try_from(limit.unwrap_or(50).min(200)).unwrap_or(50);
|
||||
let mut opts = MessagesOptions::backward();
|
||||
opts.limit = matrix_sdk::ruma::UInt::from(limit_val);
|
||||
// room.messages() transparently decrypts events in encrypted rooms.
|
||||
// UTD (unable to decrypt) events surface via `ev.kind.is_utd()` and
|
||||
// get a sentinel body so claude knows decryption failed rather than
|
||||
// seeing the raw encrypted blob.
|
||||
let msgs = match room.messages(opts).await {
|
||||
Ok(m) => m,
|
||||
Err(e) => return DaemonResponse::error(format!("messages: {e}")),
|
||||
};
|
||||
let events: Vec<TimelineEvent> = resp
|
||||
let events: Vec<TimelineEvent> = msgs
|
||||
.chunk
|
||||
.iter()
|
||||
.filter_map(|raw| {
|
||||
let parsed = raw.deserialize().ok()?;
|
||||
.filter_map(|ev| {
|
||||
let parsed = ev.raw().deserialize().ok()?;
|
||||
let event_id = parsed.event_id().to_string();
|
||||
let sender = parsed.sender().to_string();
|
||||
let origin_server_ts: i64 = parsed.origin_server_ts().0.into();
|
||||
let event_type = parsed.event_type().to_string();
|
||||
let body = extract_body(&parsed);
|
||||
let body = if ev.kind.is_utd() {
|
||||
"[unable to decrypt]".to_owned()
|
||||
} else {
|
||||
extract_body_sync(&parsed)
|
||||
};
|
||||
Some(TimelineEvent {
|
||||
event_id,
|
||||
sender,
|
||||
|
|
|
|||
|
|
@ -241,6 +241,25 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
allowEncryption = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Server-side switch for matrix end-to-end encryption — sets
|
||||
tuwunel's `allow_encryption`. Off by default: on the hive-internal
|
||||
homeserver the operator already controls the transport, so server
|
||||
E2EE adds key-management overhead (cross-signing, device
|
||||
verification, undecryptable-message recovery) without a clear
|
||||
threat-model win for the common single-hive case. Turn on when
|
||||
agents join encrypted rooms on external / federated homeservers,
|
||||
or when the operator wants message contents opaque to the
|
||||
homeserver admin. Independent of the agent matrix client, which
|
||||
always supports decryption so it can read encrypted rooms it is
|
||||
invited to regardless of this flag; this option only governs
|
||||
whether THIS homeserver permits room encryption.
|
||||
'';
|
||||
};
|
||||
|
||||
gui = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
|
|
@ -403,8 +422,9 @@ in
|
|||
# LoadCredential below copies the host file into a
|
||||
# 0400 dynamic-user-owned path; tuwunel reads from there.
|
||||
registration_token_file = "/run/credentials/tuwunel.service/registration_token";
|
||||
# E2EE disabled in initial rollout; tracked in the issue tracker.
|
||||
allow_encryption = false;
|
||||
# Server-side E2EE is opt-in (default off); the agent matrix
|
||||
# client always supports decryption regardless.
|
||||
allow_encryption = cfg.allowEncryption;
|
||||
# Tuwunel's default suffix is " 💕" — suppress it so agent
|
||||
# display names are clean (just the agent name, no emoji).
|
||||
new_user_displayname_suffix = "";
|
||||
|
|
|
|||
Loading…
Reference in a new issue