# Seif (/ro/docs/api/gateway/vault)

Seif de credențiale zero-knowledge: sincronizare criptată, broker pentru agenți, aprobări și chei de dispozitiv.

codai Vault îți păstrează credențialele criptate pe propriile dispozitive: clienții sigilează fiecare element (XChaCha20-Poly1305, chei împachetate cu hibridul post-cuantic X-Wing) înainte de încărcare, deci `/v1/vault/*` stochează și sincronizează doar text cifrat. Agenții nu primesc niciodată valori secrete. Ei văd un catalog de handle-uri (`{{vault:<slug>}}`), cer o decizie de politică (niveluri `t0`-`t4` sau `deny`), așteaptă aprobarea ta când nivelul o cere și apelează endpoint-uri HTTPS intermediate al căror răspuns este redactat.

SDK-urile expun toată suprafața ca `codai.vault` (TypeScript și Python), iar cheile de dispozitiv sub `codai.vault.devices`.

## Chei de dispozitiv și confirmare dovedită [#chei-de-dispozitiv-și-confirmare-dovedită]

O aprobare dovedește cine a decis. Pe lângă semnătura v1 a cheii seifului, o decizie poate purta o **dovadă** v2 de la o **cheie de dispozitiv** protejată hardware (Android Keystore pe telefon și ceas, Windows Hello pe desktop) sau de la un **passkey**. Cheile de dispozitiv sunt doar chei publice; cheia privată nu părăsește niciodată hardware-ul securizat al dispozitivului.

### Înrolarea [#înrolarea]

1. `POST /v1/vault/devices/challenge` întoarce un `nonce` de unică folosință (32 de octeți aleatori, base64url), valabil 5 minute.
2. Dispozitivul își creează cheia, iar cheia seifului și cheia dispozitivului semnează amândouă mesajul de înrolare:
   `codai-vault/device-enroll/v1|<device_id>|<kind>|<spki_sha256>|<nonce>`, unde `spki_sha256` este base64url(SHA-256(SubjectPublicKeyInfo DER)).
3. `POST /v1/vault/devices` trimite `device_id` (`android-…`, `wear-…` sau `desktop-…`), `kind` (`android_keystore` sau `windows_hello`), `name`, `surface` (`phone`, `wear` sau `desktop`), `public_key`, `nonce`, `vault_signature`, `device_signature` și, când există, o `attestation`. Răspunsul este `201 { device }`.

Reînrolarea aceluiași `device_id` îi înlocuiește cheia. `GET /v1/vault/devices` listează cheile tale (inclusiv cele revocate), `PATCH /v1/vault/devices/{id}` redenumește una, iar `DELETE /v1/vault/devices/{id}` o revocă imediat: următoarea ei dovadă primește 403 `device_revoked`.

Toate valorile binare sunt base64url fără padding. Android semnează cu ECDSA P-256 SHA-256 (DER); Windows Hello semnează cu RSASSA-PKCS1-v1_5 SHA-256.

### Tipuri de atestare [#tipuri-de-atestare]

| `attestation` | Semnificație |
| --- | --- |
| `android-strongbox` | Atestarea Google a cheii verificată, cheia ținută într-un element securizat StrongBox. |
| `android-tee` | Atestarea Google a cheii verificată, cheia ținută în TEE. |
| `tpm` | O atestare TPM a cheii Windows Hello, verificată față de rădăcinile TPM Microsoft. |
| `hello-unattested` | O cheie Windows Hello fără atestare TPM. |

Cheile Android trebuie să trimită `attestation: { android_chain: [...] }` (frunza prima); provocarea de atestare este formată din octeții nonce-ului, iar cheia atestată trebuie să fie egală cu `public_key`. Cheile Windows Hello pot trimite `attestation: { tpm: { attestation, cert_chain } }`; fără ea cheia este `hello-unattested`, cu una invalidă înrolarea eșuează. Erori: 400 `invalid_nonce`, `possession_failed`, `attestation_required`, `attestation_invalid`; 403 `invalid_signature`; 409 `device_id_taken`; 503 `revocation_unavailable` (lista de revocare Google nu a putut fi verificată, reîncearcă mai târziu).

### Mesajele de aprobare [#mesajele-de-aprobare]

- v1 (cheia seifului): `codai-vault/approve/v1|<approval_id>|<action_hash>|<decision>|<scope>`, trimis ca `signature`.
- v2 (cheie de dispozitiv sau passkey): `codai-vault/approve/v2|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>`, trimis ca `proof: { type: 'device', device_id, signature }`.
- v3 (cheie de dispozitiv sau passkey + potrivirea numărului): `codai-vault/approve/v3|<approval_id>|<action_hash>|<decision>|<scope>|<device_id>|<preview_digest>|<match_number>`, trimis cu `match_number` lângă `proof`. `preview_digest` este base64url(SHA-256) al textului canonic al `request.preview` (`codai-vault/preview/v1`, apoi `title`, `agent`, `mode`, `host`, `action`, `purpose`, `task` ca linii `cheie=valoare`, cu `\`, linie nouă și CR escapate), deci semnătura acoperă exact ce a văzut aprobatorul.
- passkey: provocarea WebAuthn este base64url(SHA-256(mesajul v2 cu id-ul de dispozitiv `passkey`)), trimisă ca `proof: { type: 'passkey', response }`.

Trimite cel mult unul dintre `signature` și `proof`. `step_up_acr` este încă acceptat, dar ignorat: o confirmare declarată de client nu dovedește nimic.

### Reguli pe niveluri [#reguli-pe-niveluri]

- **Refuzul** nu cere dovadă (butonul Refuză din notificare este suficient).
- **Aprobarea T3** acceptă o semnătură v1 validă, o dovadă de dispozitiv sau o dovadă passkey.
- **Aprobarea T4** cere o dovadă de dispozitiv sau passkey; o semnătură v1 primește 403 `step_up_required`. Toate tipurile de atestare de mai sus sunt acceptate astăzi.
- **Ceasul Wear aprobă doar T3**: o cheie de ceas nu poate fi atestată ca legată de biometrie, deci o dovadă de la un dispozitiv `wear` pe o aprobare T4 primește 403 `wear_t3_only`.
- **Potrivirea numărului**: fiecare cerere T3/T4 primește un `match_number` din 2 cifre, întors doar agentului (`/vault/use`, `/vault/http`, `/vault/resolve`). Aprobatorul vede `request.preview` și trei `request.match_choices`, niciodată numărul. O aprobare T4 pe o astfel de cerere cere `match_number` cu o dovadă v3: fără număr primește 403 `match_required`; un număr greșit refuză imediat cererea și primește 403 `match_mismatch`. Aprobările T3 și refuzurile acceptă în continuare v1/v2.

Alte erori de dovadă (403): `invalid_signature`, `unknown_device`, `device_revoked`, `passkey_invalid`.

<OpenAPIPage document="./openapi/ro/gateway.yaml" operations={[{path: '/v1/vault/devices/challenge',method: 'post'},{path: '/v1/vault/devices',method: 'post'},{path: '/v1/vault/devices',method: 'get'},{path: '/v1/vault/devices/{id}',method: 'patch'},{path: '/v1/vault/devices/{id}',method: 'delete'},{path: '/v1/vault/approvals/{id}/decide',method: 'post'}]} />
