Mirrors keisei-marketplace/src/lib/crypto-box.ts::sealBoxToVps.
Two new subcommands on kei-buddy bin:
- genkeys --key <path> → writes PKCS#8 PEM x25519 priv,
prints standard-base64 pub (44 char)
- decrypt-and-export --vps-key <pem> --blob <json> --env-out <env>
→ ECDH(vps_priv, ephPub) → HKDF-SHA256
info=keibuddy-token-v1 → XChaCha20-Poly1305
decrypt → append BOT_TOKEN/TELEGRAM_BOT_TOKEN
to env file (replaces stale, keeps other lines)
Cloud-init in hetzner.ts already calls these. Without this commit the
VPS could decode its own pubkey but had no way to recover the sealed
bot-token blob — the bot would never log into Telegram.
Crypto stack (mirror of @noble in TS):
- x25519-dalek 2 (static_secrets feature)
- chacha20poly1305 0.10 (XChaCha20Poly1305)
- hkdf 0.12, sha2 0.10
- base64 0.22 (accepts URL_SAFE_NO_PAD + STANDARD)
- zeroize 1 for priv-key wipe
Tests (6/6 pass):
- roundtrip_seal_then_decrypt — re-implement marketplace sealing in Rust,
verify our decryption recovers plaintext
- decrypt_and_export_writes_env_file — full e2e through CLI surface
- decrypt_and_export_replaces_existing_token — stale BOT_TOKEN replaced,
other env lines preserved
- decrypt_rejects_wrong_key — XChaCha20 AEAD tag fails on wrong key
- pem_roundtrip — write_pkcs8 + parse_pkcs8 round-trip
- b64decode_accepts_urlsafe_and_standard — handles both encodings
Cross-verified end-to-end:
$ node marketplace_seal.mjs <pub> <token> → /tmp/blob.json
$ kei-buddy decrypt-and-export --vps-key ... → BOT_TOKEN matches input
Constructor Pattern: 1 file (provision_decrypt.rs, 344 LOC), 1 module,
1 responsibility (token-blob decryption + key generation).
=== STATUS-TRUTH MARKER ===
shipped: functional
stubs: 0
cargo-check: PASS
behaviour-verified: yes (e2e marketplace-seal → kei-buddy-decrypt round-trip)
follow-up-required:
- none
75 lines
2.6 KiB
TOML
75 lines
2.6 KiB
TOML
[package]
|
|
name = "kei-buddy"
|
|
version = "0.1.0"
|
|
edition.workspace = true
|
|
rust-version.workspace = true
|
|
description = "KeiBuddy personal-assistant Telegram bot — onboarding state-machine + skeleton driver. Concept-level scaffold."
|
|
authors.workspace = true
|
|
license.workspace = true
|
|
|
|
[[bin]]
|
|
name = "kei-buddy"
|
|
path = "src/bin/kei-buddy.rs"
|
|
|
|
[[bin]]
|
|
name = "kei-buddy-tick"
|
|
path = "src/bin/kei-buddy-tick.rs"
|
|
|
|
[lib]
|
|
name = "kei_buddy"
|
|
path = "src/lib.rs"
|
|
|
|
[dependencies]
|
|
serde = { workspace = true, features = ["derive"] }
|
|
serde_json = { workspace = true }
|
|
thiserror = { workspace = true }
|
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "net"] }
|
|
tracing = "0.1"
|
|
clap = { workspace = true, features = ["derive"] }
|
|
async-trait = { workspace = true }
|
|
rusqlite = { workspace = true }
|
|
reqwest = { workspace = true }
|
|
anyhow = { workspace = true }
|
|
kei-memory-sqlite = { path = "../kei-memory-sqlite" }
|
|
kei-chat-store = { path = "../kei-chat-store" }
|
|
kei-social-store = { path = "../kei-social-store" }
|
|
kei-sage = { path = "../kei-sage" }
|
|
kei-contacts-google = { path = "../kei-contacts-google" }
|
|
kei-contacts-apple = { path = "../kei-contacts-apple" }
|
|
chrono = { workspace = true }
|
|
|
|
# provision-crypto: x25519 ECDH + HKDF-SHA256 + XChaCha20-Poly1305
|
|
# Mirrors marketplace/src/lib/crypto-box.ts so VPS can decrypt the
|
|
# bot-token blob emitted by the browser.
|
|
x25519-dalek = { version = "2", features = ["static_secrets"] }
|
|
chacha20poly1305 = { version = "0.10", features = ["alloc"] }
|
|
hkdf = "0.12"
|
|
sha2 = "0.10"
|
|
base64 = "0.22"
|
|
rand_core = "0.6"
|
|
zeroize = "1"
|
|
|
|
# serve feature deps
|
|
axum = { version = "0.7", features = ["json", "http1", "tokio"], optional = true }
|
|
kei-telegram-webhook = { path = "../kei-telegram-webhook", optional = true }
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true }
|
|
kei-stt = { path = "../kei-stt", default-features = false, features = ["whisper-local"], optional = true }
|
|
|
|
[dev-dependencies]
|
|
wiremock = { workspace = true }
|
|
tokio = { workspace = true }
|
|
|
|
[features]
|
|
default = ["serve"]
|
|
# HTTP server — axum router + webhook handler + Telegram send_message.
|
|
serve = ["axum", "kei-telegram-webhook", "tracing-subscriber", "kei-stt"]
|
|
# Enables OpenAiExtractor — real HTTP to LiteLLM proxy using reqwest.
|
|
# Off by default; tests use MockExtractor which has no extra deps.
|
|
extractor-openai = []
|
|
# future: pulls in kei-notify-telegram for real Telegram transport
|
|
telegram = []
|
|
|
|
[package.metadata.keisei]
|
|
maturity = "concept"
|
|
description = "KeiBuddy personal-assistant: onboarding FSM + bot driver scaffold"
|
|
authors = ["Denis Parfionovich <parfionovich@keilab.io>"]
|