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
60 lines
1.8 KiB
Rust
60 lines
1.8 KiB
Rust
// SPDX-License-Identifier: Apache-2.0
|
|
//! kei-buddy — KeiBuddy personal-assistant Telegram bot scaffold.
|
|
//!
|
|
//! Module layout (Constructor Pattern — one file, one responsibility):
|
|
//! * `state` — `OnboardState` enum
|
|
//! * `transition` — `StepOutput` output struct
|
|
//! * `extractor` — `LlmExtractor` trait + `MockExtractor` + `OpenAiExtractor` (feature-gated)
|
|
//! * `machine` — `handle_step` — the 11-arm onboarding FSM
|
|
//! * `error` — `BuddyError` error type
|
|
//! * `schema` — buddy-specific SQLite DDL
|
|
//! * `store` — `BuddyStore` trait + `SqliteBuddyStore` impl
|
|
|
|
pub mod chat_log;
|
|
pub(crate) mod command_exec;
|
|
pub mod commands;
|
|
pub mod contacts;
|
|
pub mod contacts_sync;
|
|
pub mod conversational;
|
|
pub mod error;
|
|
pub mod extractor;
|
|
pub mod machine;
|
|
pub(crate) mod machine_helpers;
|
|
pub(crate) mod machine_lang;
|
|
pub mod persona_merge;
|
|
pub mod provision_decrypt;
|
|
pub mod retrieval;
|
|
pub mod schema;
|
|
pub mod state;
|
|
pub mod store;
|
|
pub(crate) mod store_ops;
|
|
pub mod strings;
|
|
pub mod tick;
|
|
pub mod topic_classify;
|
|
pub mod topics;
|
|
pub mod transition;
|
|
|
|
#[cfg(feature = "serve")]
|
|
pub mod serve;
|
|
#[cfg(feature = "serve")]
|
|
pub(crate) mod serve_runner;
|
|
#[cfg(feature = "serve")]
|
|
pub mod serve_telegram;
|
|
#[cfg(feature = "serve")]
|
|
pub mod voice;
|
|
|
|
pub use chat_log::ChatLog;
|
|
pub use commands::{parse_command, execute_command, Command, CommandStores};
|
|
pub use contacts_sync::{sync_from_apple, sync_from_google, SyncReport};
|
|
pub use contacts::Contacts;
|
|
pub use error::BuddyError;
|
|
pub use extractor::LlmExtractor;
|
|
pub use machine::handle_step;
|
|
pub use state::OnboardState;
|
|
pub use store::{BuddyStore, SqliteBuddyStore};
|
|
pub use strings::{Lang, Strings};
|
|
pub use tick::{run_tick, run_tick_with, TickConfig, TickReport};
|
|
pub use topics::Topics;
|
|
pub use transition::StepOutput;
|
|
#[cfg(feature = "serve")]
|
|
pub use voice::VoiceHandler;
|