feat: add recovery key support for E2EE agents, including configuration and documentation updates
This commit is contained in:
@@ -95,6 +95,7 @@ matrix:
|
||||
store_path: "./agents/<agent-id>/data/crypto/" # SIEMPRE por agente, nunca compartida
|
||||
pickle_key_env: PICKLE_KEY_<ID_UPPER> # env var con clave hex
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_<ID_UPPER> # env var con base58 recovery key
|
||||
```
|
||||
|
||||
**Al crear un nuevo agente con E2EE:**
|
||||
@@ -102,9 +103,23 @@ matrix:
|
||||
2. Añadir a `.env`: `PICKLE_KEY_<ID_UPPER>=<hex>`
|
||||
3. Añadir a `.env.example`: `PICKLE_KEY_<ID_UPPER>=`
|
||||
4. Usar `store_path` propio del agente (no compartir entre agentes)
|
||||
5. Tras arrancar, verificar cross-signing: `go run -tags goolm ./cmd/verify ...`
|
||||
5. Ejecutar `cmd/verify` con `--store` y `--pickle-key` del agente:
|
||||
```bash
|
||||
./bin/verify --homeserver "$MATRIX_HOMESERVER" --username "<id>" \
|
||||
--password "$MATRIX_PASSWORD_<AGENT>" --token "$MATRIX_TOKEN_<AGENT>" \
|
||||
--store "./agents/<id>/data/crypto/" --pickle-key "$PICKLE_KEY_<AGENT>"
|
||||
```
|
||||
6. Guardar el recovery key en `.env` (con comillas por los espacios):
|
||||
```bash
|
||||
SSSS_RECOVERY_KEY_<ID_UPPER>="EsXX YYYY ZZZZ ..."
|
||||
```
|
||||
7. Añadir `recovery_key_env` al config.yaml:
|
||||
```yaml
|
||||
encryption:
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_<ID_UPPER>
|
||||
```
|
||||
|
||||
Ver `docs/e2ee.md` para documentación completa de E2EE.
|
||||
**Sin el recovery key**, el agente arranca pero los mensajes muestran "Encrypted by a device not verified by its owner".
|
||||
|
||||
### 3. `agents/<agent-id>/prompts/system.md` — System prompt
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ PICKLE_KEY_ASSISTANT_BOT=
|
||||
PICKLE_KEY_ASISTENTE_2=
|
||||
PICKLE_KEY_DEVOPS_BOT=
|
||||
|
||||
# ── E2EE SSSS recovery keys (generados por cmd/verify) ──────
|
||||
# Permite al agente importar cross-signing private keys al iniciar.
|
||||
# Sin esto, los mensajes muestran "Encrypted by a device not verified by its owner".
|
||||
SSSS_RECOVERY_KEY_ASSISTANT_BOT=
|
||||
SSSS_RECOVERY_KEY_ASISTENTE_2=
|
||||
SSSS_RECOVERY_KEY_DEVOPS_BOT=
|
||||
|
||||
# ── LLM providers ────────────────────────────────────────────
|
||||
OPENAI_API_KEY=sk-...
|
||||
ANTHROPIC_API_KEY=sk-ant-... # opcional, para cuando añadas el devops-bot con Claude
|
||||
|
||||
@@ -116,13 +116,14 @@ matrix:
|
||||
homeserver: "https://matrix-af2f3d.organic-machine.com"
|
||||
user_id: "@asistente-2:matrix-af2f3d.organic-machine.com"
|
||||
access_token_env: MATRIX_TOKEN_ASISTENTE2
|
||||
device_id: "YBFNMNMJIC"
|
||||
device_id: "XUGTSZJYFQ"
|
||||
|
||||
encryption:
|
||||
enabled: true
|
||||
store_path: "./agents/asistente2/data/crypto/"
|
||||
pickle_key_env: PICKLE_KEY_ASISTENTE_2
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_ASISTENTE_2
|
||||
|
||||
rooms:
|
||||
listen: []
|
||||
|
||||
@@ -117,13 +117,14 @@ matrix:
|
||||
homeserver: "https://matrix-af2f3d.organic-machine.com"
|
||||
user_id: "@assistant-bot:matrix-af2f3d.organic-machine.com"
|
||||
access_token_env: MATRIX_TOKEN_ASSISTANT
|
||||
device_id: "ASSISTANTBOT01"
|
||||
device_id: "SMWMRKMHDH"
|
||||
|
||||
encryption:
|
||||
enabled: true
|
||||
store_path: "./agents/assistant/data/crypto/"
|
||||
pickle_key_env: PICKLE_KEY_ASSISTANT_BOT
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_ASSISTANT_BOT
|
||||
|
||||
rooms:
|
||||
listen: [] # vacío = escucha en todos los rooms donde está invitado
|
||||
|
||||
@@ -56,6 +56,18 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, logger *slog.Logger) (*
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("e2ee init: %w", err)
|
||||
}
|
||||
|
||||
// Auto-fetch cross-signing private keys from SSSS if recovery key is configured.
|
||||
if envName := cfg.Matrix.Encryption.RecoveryKeyEnv; envName != "" {
|
||||
if rk := os.Getenv(envName); rk != "" {
|
||||
if err := matrixClient.FetchCrossSigningKeys(context.Background(), rk); err != nil {
|
||||
logger.Warn("failed to fetch cross-signing keys from SSSS (non-fatal)", "err", err)
|
||||
} else {
|
||||
logger.Info("cross-signing private keys fetched from SSSS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("e2ee ready")
|
||||
}
|
||||
|
||||
|
||||
+28
-9
@@ -9,6 +9,7 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -23,11 +24,12 @@ import (
|
||||
|
||||
func main() {
|
||||
var (
|
||||
homeserver string
|
||||
username string
|
||||
password string
|
||||
token string
|
||||
storePath string
|
||||
homeserver string
|
||||
username string
|
||||
password string
|
||||
token string
|
||||
storePath string
|
||||
pickleKeyHex string
|
||||
)
|
||||
|
||||
root := &cobra.Command{
|
||||
@@ -62,9 +64,18 @@ Requires the bot's access token and password (for UIA during key upload).`,
|
||||
client.DeviceID = whoami.DeviceID
|
||||
fmt.Printf("→ Device ID: %s\n", client.DeviceID)
|
||||
|
||||
// Initialize crypto
|
||||
sum := sha256.Sum256([]byte(token))
|
||||
pickleKey := sum[:]
|
||||
// Initialize crypto — use explicit pickle key if provided, else sha256(token)
|
||||
var pickleKey []byte
|
||||
if pickleKeyHex != "" {
|
||||
var err error
|
||||
pickleKey, err = hex.DecodeString(pickleKeyHex)
|
||||
if err != nil {
|
||||
return fmt.Errorf("decode pickle-key hex: %w", err)
|
||||
}
|
||||
} else {
|
||||
sum := sha256.Sum256([]byte(token))
|
||||
pickleKey = sum[:]
|
||||
}
|
||||
|
||||
dbPath := filepath.Join(storePath, "crypto.db")
|
||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0700); err != nil {
|
||||
@@ -91,7 +102,7 @@ Requires the bot's access token and password (for UIA during key upload).`,
|
||||
}
|
||||
|
||||
fmt.Println("→ Generating and uploading cross-signing keys...")
|
||||
_, _, err = olmMachine.GenerateAndUploadCrossSigningKeysWithPassword(ctx, password, "")
|
||||
recoveryKey, _, err := olmMachine.GenerateAndUploadCrossSigningKeysWithPassword(ctx, password, "")
|
||||
if err != nil {
|
||||
// If keys already exist, try to just sign our device
|
||||
fmt.Printf(" Note: %v\n", err)
|
||||
@@ -101,6 +112,13 @@ Requires the bot's access token and password (for UIA during key upload).`,
|
||||
|
||||
fmt.Println("✓ Cross-signing keys uploaded successfully")
|
||||
fmt.Printf("✓ Device %s is now verified by %s\n", client.DeviceID, userID)
|
||||
fmt.Println()
|
||||
fmt.Println("─── IMPORTANT: Save the recovery key ───")
|
||||
fmt.Printf("SSSS_RECOVERY_KEY_%s=%s\n", strings.ToUpper(strings.ReplaceAll(username, "-", "_")), recoveryKey)
|
||||
fmt.Println()
|
||||
fmt.Println("Add this to your .env file and set recovery_key_env in the agent's config.yaml:")
|
||||
fmt.Println(" encryption:")
|
||||
fmt.Printf(" recovery_key_env: SSSS_RECOVERY_KEY_%s\n", strings.ToUpper(strings.ReplaceAll(username, "-", "_")))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -110,6 +128,7 @@ Requires the bot's access token and password (for UIA during key upload).`,
|
||||
root.Flags().StringVar(&password, "password", "", "Bot password (for UIA auth)")
|
||||
root.Flags().StringVar(&token, "token", "", "Bot access token")
|
||||
root.Flags().StringVar(&storePath, "store", "./data/verify-crypto/", "Crypto store path")
|
||||
root.Flags().StringVar(&pickleKeyHex, "pickle-key", "", "Hex-encoded pickle key (must match agent's pickle key if sharing crypto store)")
|
||||
_ = root.MarkFlagRequired("homeserver")
|
||||
_ = root.MarkFlagRequired("username")
|
||||
_ = root.MarkFlagRequired("password")
|
||||
|
||||
+66
-18
@@ -208,28 +208,67 @@ Esto hace:
|
||||
Sin este paso, los mensajes del bot mostrarán: **"Encrypted by a device not verified by its owner"**.
|
||||
|
||||
```bash
|
||||
go run -tags goolm ./cmd/verify \
|
||||
--homeserver "https://matrix-af2f3d.organic-machine.com" \
|
||||
./bin/verify \
|
||||
--homeserver "$MATRIX_HOMESERVER" \
|
||||
--username "<agent-id>" \
|
||||
--password "<password_del_bot>" \
|
||||
--token "<access_token>" \
|
||||
--store "./agents/<agent-id>/data/crypto/"
|
||||
--password "$MATRIX_PASSWORD_<AGENT>" \
|
||||
--token "$MATRIX_TOKEN_<AGENT>" \
|
||||
--store "./agents/<agent-id>/data/crypto/" \
|
||||
--pickle-key "$PICKLE_KEY_<AGENT>"
|
||||
```
|
||||
|
||||
**Qué hace:**
|
||||
1. Inicializa el crypto helper de mautrix
|
||||
2. Genera claves de cross-signing (master + self-signing)
|
||||
1. Inicializa el crypto helper de mautrix (usando el mismo store y pickle key que el agente)
|
||||
2. Genera claves de cross-signing (master + self-signing + user-signing)
|
||||
3. Las sube al homeserver usando UIA con la password del bot
|
||||
4. Firma el device del bot con la self-signing key
|
||||
4. Las almacena cifradas en SSSS (Server-Side Secret Storage) en el servidor
|
||||
5. Imprime un **recovery key** (base58) que permite recuperar las claves privadas
|
||||
|
||||
**Importante:** Si se cambia la password del bot (admin API), el token anterior se invalida. Hay que:
|
||||
### 5.1 Guardar el recovery key
|
||||
|
||||
El comando imprime algo como:
|
||||
|
||||
```
|
||||
─── IMPORTANT: Save the recovery key ───
|
||||
SSSS_RECOVERY_KEY_MI_BOT=EsXX YYYY ZZZZ ...
|
||||
```
|
||||
|
||||
**Añadir al `.env`** (con comillas, el recovery key tiene espacios):
|
||||
|
||||
```bash
|
||||
SSSS_RECOVERY_KEY_MI_BOT="EsXX YYYY ZZZZ ..."
|
||||
```
|
||||
|
||||
### 5.2 Configurar recovery_key_env en config.yaml
|
||||
|
||||
```yaml
|
||||
encryption:
|
||||
enabled: true
|
||||
store_path: "./agents/<agent-id>/data/crypto/"
|
||||
pickle_key_env: PICKLE_KEY_<AGENT>
|
||||
trust_mode: tofu
|
||||
recovery_key_env: SSSS_RECOVERY_KEY_<AGENT> # ← NUEVO
|
||||
```
|
||||
|
||||
Esto permite que el agente recupere automáticamente las cross-signing private keys desde SSSS cada vez que arranca. Sin esto, las keys solo existen en memoria durante la sesión de verify.
|
||||
|
||||
**Logs esperados al arrancar con recovery key configurado:**
|
||||
```
|
||||
INFO cross-signing private keys fetched from SSSS
|
||||
INFO e2ee ready
|
||||
```
|
||||
|
||||
### 5.3 Si se cambia la password del bot
|
||||
|
||||
Cambiar la password (admin API) invalida el token anterior. Hay que:
|
||||
1. Re-login para obtener nuevo token
|
||||
2. Actualizar `MATRIX_TOKEN_<AGENT>` en `.env`
|
||||
2. Actualizar `MATRIX_TOKEN_<AGENT>` y `MATRIX_PASSWORD_<AGENT>` en `.env`
|
||||
3. Actualizar `device_id` en `config.yaml`
|
||||
4. Borrar el crypto store viejo (`agents/<id>/data/crypto/crypto.db`)
|
||||
5. Re-ejecutar `cmd/verify`
|
||||
5. Re-ejecutar `cmd/verify` → obtener nuevo recovery key
|
||||
6. Actualizar `SSSS_RECOVERY_KEY_<AGENT>` en `.env`
|
||||
|
||||
**Nota:** El pickle key (`PICKLE_KEY_<AGENT>`) NO cambia al rotar el token. Solo se regenera si se pierde. Ver `docs/e2ee.md`.
|
||||
**Nota:** El pickle key (`PICKLE_KEY_<AGENT>`) NO cambia al rotar el token. Solo se regenera si se pierde.
|
||||
|
||||
## Paso 6: Arrancar el agente
|
||||
|
||||
@@ -286,17 +325,24 @@ tail -f run/<agent-id>.log
|
||||
# 5. Avatar y displayname
|
||||
./dev-scripts/avatar.sh <id> static/<imagen>.jpg
|
||||
|
||||
# 6. Verificación E2EE
|
||||
go run -tags goolm ./cmd/verify \
|
||||
# 6. Generar pickle key (si no existe)
|
||||
openssl rand -hex 32 # → guardar como PICKLE_KEY_<AGENT> en .env
|
||||
|
||||
# 7. Verificación E2EE + recovery key
|
||||
./bin/verify \
|
||||
--homeserver "$MATRIX_HOMESERVER" \
|
||||
--username "<id>" \
|
||||
--password "$MATRIX_PASSWORD_<AGENT>" \
|
||||
--token "$MATRIX_TOKEN_<AGENT>"
|
||||
--token "$MATRIX_TOKEN_<AGENT>" \
|
||||
--store "./agents/<id>/data/crypto/" \
|
||||
--pickle-key "$PICKLE_KEY_<AGENT>"
|
||||
# → Guardar SSSS_RECOVERY_KEY_<AGENT> en .env (con comillas)
|
||||
# → Añadir recovery_key_env al config.yaml
|
||||
|
||||
# 7. Arrancar
|
||||
# 8. Arrancar
|
||||
./dev-scripts/start.sh <id>
|
||||
|
||||
# 8. Verificar
|
||||
# 9. Verificar
|
||||
tail -f run/<id>.log
|
||||
```
|
||||
|
||||
@@ -308,7 +354,9 @@ tail -f run/<id>.log
|
||||
| `M_UNKNOWN_TOKEN` | Token invalidado (password cambiada) | Re-login, actualizar `.env` |
|
||||
| `mismatching device ID` | Crypto store con device viejo | Borrar `agents/<id>/data/crypto/crypto.db`, actualizar `device_id` en config |
|
||||
| `olm account not marked as shared` | Crypto store inconsistente | Auto-recovery lo resuelve al reiniciar. Si persiste: borrar crypto.db |
|
||||
| `"Encrypted by device not verified"` | Falta cross-signing | Ejecutar `cmd/verify` |
|
||||
| `"Encrypted by device not verified"` | Falta cross-signing | Ejecutar `cmd/verify` con `--store` y `--pickle-key` del agente, guardar recovery key en `.env` |
|
||||
| `cross-signing private keys not available` | Recovery key no configurada | Ejecutar `cmd/verify`, guardar recovery key, configurar `recovery_key_env` |
|
||||
| `verify recovery key: invalid` | Recovery key incorrecta | Re-ejecutar `cmd/verify` para generar nueva recovery key |
|
||||
| Bot no responde | Reglas no matchean | Verificar que hay regla catch-all para DMs/mentions |
|
||||
| `no rules registered for agent` | ID no está en `rulesRegistry` | Añadir en `cmd/launcher/main.go` |
|
||||
| Bot muere al arrancar | Revisar logs | `tail -f run/<id>.log` |
|
||||
|
||||
@@ -169,10 +169,11 @@ type MatrixCfg struct {
|
||||
}
|
||||
|
||||
type EncryptionCfg struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
StorePath string `yaml:"store_path"`
|
||||
PickleKeyEnv string `yaml:"pickle_key_env"` // env var with hex-encoded 32-byte key
|
||||
TrustMode string `yaml:"trust_mode"` // tofu | cross-signing | manual
|
||||
Enabled bool `yaml:"enabled"`
|
||||
StorePath string `yaml:"store_path"`
|
||||
PickleKeyEnv string `yaml:"pickle_key_env"` // env var with hex-encoded 32-byte key
|
||||
TrustMode string `yaml:"trust_mode"` // tofu | cross-signing | manual
|
||||
RecoveryKeyEnv string `yaml:"recovery_key_env"` // env var with base58 SSSS recovery key for cross-signing
|
||||
}
|
||||
|
||||
type RoomsCfg struct {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"maunium.net/go/mautrix"
|
||||
"maunium.net/go/mautrix/crypto"
|
||||
"maunium.net/go/mautrix/crypto/cryptohelper"
|
||||
"maunium.net/go/mautrix/crypto/ssss"
|
||||
"maunium.net/go/mautrix/event"
|
||||
"maunium.net/go/mautrix/id"
|
||||
|
||||
@@ -108,6 +109,66 @@ func (c *Client) InitCrypto(ctx context.Context, storePath, pickleKeyHex, agentI
|
||||
return closer, nil
|
||||
}
|
||||
|
||||
// ssssKeyFetcher abstracts the SSSS + cross-signing key retrieval for testing.
|
||||
type ssssKeyFetcher interface {
|
||||
GetDefaultKeyData(ctx context.Context) (string, ssssKeyVerifier, error)
|
||||
FetchCrossSigningKeysFromSSSS(ctx context.Context, key *ssss.Key) error
|
||||
}
|
||||
|
||||
// ssssKeyVerifier abstracts the SSSS key metadata verification.
|
||||
type ssssKeyVerifier interface {
|
||||
VerifyRecoveryKey(keyID, recoveryKey string) (*ssss.Key, error)
|
||||
}
|
||||
|
||||
// olmSSSSFetcher adapts *crypto.OlmMachine to the ssssKeyFetcher interface.
|
||||
type olmSSSSFetcher struct {
|
||||
machine *crypto.OlmMachine
|
||||
}
|
||||
|
||||
func (o *olmSSSSFetcher) GetDefaultKeyData(ctx context.Context) (string, ssssKeyVerifier, error) {
|
||||
keyID, keyData, err := o.machine.SSSS.GetDefaultKeyData(ctx)
|
||||
return keyID, keyData, err
|
||||
}
|
||||
|
||||
func (o *olmSSSSFetcher) FetchCrossSigningKeysFromSSSS(ctx context.Context, key *ssss.Key) error {
|
||||
return o.machine.FetchCrossSigningKeysFromSSSS(ctx, key)
|
||||
}
|
||||
|
||||
// FetchCrossSigningKeys retrieves cross-signing private keys from SSSS
|
||||
// (server-side secret storage) using the given base58 recovery key.
|
||||
// This allows the agent to sign its own device, eliminating the
|
||||
// "Encrypted by a device not verified by its owner" warning.
|
||||
func (c *Client) FetchCrossSigningKeys(ctx context.Context, recoveryKey string) error {
|
||||
wrapper, ok := c.raw.Crypto.(*mautrixCryptoWrapper)
|
||||
if !ok || wrapper == nil {
|
||||
return fmt.Errorf("crypto not initialized")
|
||||
}
|
||||
machine := wrapper.Machine()
|
||||
if machine == nil {
|
||||
return fmt.Errorf("olm machine not available")
|
||||
}
|
||||
return fetchCrossSigningKeysCore(ctx, &olmSSSSFetcher{machine}, recoveryKey)
|
||||
}
|
||||
|
||||
// fetchCrossSigningKeysCore contains the testable logic for SSSS key retrieval.
|
||||
func fetchCrossSigningKeysCore(ctx context.Context, fetcher ssssKeyFetcher, recoveryKey string) error {
|
||||
keyID, keyData, err := fetcher.GetDefaultKeyData(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get SSSS default key: %w", err)
|
||||
}
|
||||
|
||||
key, err := keyData.VerifyRecoveryKey(keyID, recoveryKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify recovery key: %w", err)
|
||||
}
|
||||
|
||||
if err := fetcher.FetchCrossSigningKeysFromSSSS(ctx, key); err != nil {
|
||||
return fmt.Errorf("fetch cross-signing keys from SSSS: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// initCryptoCore contains the testable logic: pickle key resolution, store
|
||||
// creation, and auto-recovery on stale crypto.db. Returns (closer, helper, err).
|
||||
func initCryptoCore(ctx context.Context, storePath, pickleKeyHex, accessToken, agentID string, initer cryptoIniter, logger *slog.Logger) (io.Closer, cryptoHelper, error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"maunium.net/go/mautrix/crypto"
|
||||
"maunium.net/go/mautrix/crypto/ssss"
|
||||
"maunium.net/go/mautrix/id"
|
||||
)
|
||||
|
||||
@@ -400,3 +401,86 @@ func TestLogCryptoDiagnosticsCore_FullHappyPath(t *testing.T) {
|
||||
t.Error("expected private keys log")
|
||||
}
|
||||
}
|
||||
|
||||
// --- SSSS key fetcher fakes for testing fetchCrossSigningKeysCore ---
|
||||
|
||||
type fakeSSSSKeyVerifier struct {
|
||||
key *ssss.Key
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeSSSSKeyVerifier) VerifyRecoveryKey(keyID, recoveryKey string) (*ssss.Key, error) {
|
||||
return f.key, f.err
|
||||
}
|
||||
|
||||
type fakeSSSSKeyFetcher struct {
|
||||
keyID string
|
||||
verifier ssssKeyVerifier
|
||||
getErr error
|
||||
fetchErr error
|
||||
}
|
||||
|
||||
func (f *fakeSSSSKeyFetcher) GetDefaultKeyData(ctx context.Context) (string, ssssKeyVerifier, error) {
|
||||
return f.keyID, f.verifier, f.getErr
|
||||
}
|
||||
|
||||
func (f *fakeSSSSKeyFetcher) FetchCrossSigningKeysFromSSSS(ctx context.Context, key *ssss.Key) error {
|
||||
return f.fetchErr
|
||||
}
|
||||
|
||||
func TestFetchCrossSigningKeysCore_Success(t *testing.T) {
|
||||
fetcher := &fakeSSSSKeyFetcher{
|
||||
keyID: "key1",
|
||||
verifier: &fakeSSSSKeyVerifier{key: &ssss.Key{ID: "key1"}},
|
||||
}
|
||||
|
||||
err := fetchCrossSigningKeysCore(context.Background(), fetcher, "valid-recovery-key")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCrossSigningKeysCore_GetDefaultKeyFails(t *testing.T) {
|
||||
fetcher := &fakeSSSSKeyFetcher{
|
||||
getErr: errors.New("no default key"),
|
||||
}
|
||||
|
||||
err := fetchCrossSigningKeysCore(context.Background(), fetcher, "any-key")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "get SSSS default key") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCrossSigningKeysCore_VerifyRecoveryKeyFails(t *testing.T) {
|
||||
fetcher := &fakeSSSSKeyFetcher{
|
||||
keyID: "key1",
|
||||
verifier: &fakeSSSSKeyVerifier{err: errors.New("invalid recovery key")},
|
||||
}
|
||||
|
||||
err := fetchCrossSigningKeysCore(context.Background(), fetcher, "bad-key")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "verify recovery key") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCrossSigningKeysCore_FetchFromSSSSFails(t *testing.T) {
|
||||
fetcher := &fakeSSSSKeyFetcher{
|
||||
keyID: "key1",
|
||||
verifier: &fakeSSSSKeyVerifier{key: &ssss.Key{ID: "key1"}},
|
||||
fetchErr: errors.New("decryption failed"),
|
||||
}
|
||||
|
||||
err := fetchCrossSigningKeysCore(context.Background(), fetcher, "valid-key")
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "fetch cross-signing keys from SSSS") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user