carlos / contra Public

Clone
git clone https://amadan.net/carlos/contra

Plain git — no account needed to clone.

Download

Download this file

1package vault
2
3import (
4 "encoding/hex"
5 "net/http"
6
7 "github.com/carlosframework/contra/internal/contra/crypto"
8)
9
10// RegenerateRecovery replaces the recovery branch: a new wrap of RK sealed
11// under a KEK derived from a code the client just minted, and the login public
12// key for that same code.
13//
14// # This is the one destructive write in the app, and it is meant to be
15//
16// The recovery code is not stored — only its wrap is — so a lost sheet cannot
17// be reprinted, only reissued. That necessarily invalidates the old one, and
18// the alternative is worse: a "reprint" that left the old code live would
19// leave two working keys to the vault with one of them lost somewhere. The
20// page in front of this says so before the person commits.
21//
22// Everything that decides anything happened in the browser: the code was
23// minted there, RK was opened there under the passphrase and Account Key, and
24// what arrives here is a KCV, a public key and ciphertext. A server that
25// wanted to hand somebody a working recovery code cannot, because it never
26// sees RK — the worst it can do is refuse, or store a wrap that opens nothing,
27// and the KCV the client wrote is what catches the second.
28func (v *Vault) RegenerateRecovery(w http.ResponseWriter, r *http.Request) {
29 acct, ok := accountID(r)
30 if !ok {
31 notFound(w)
32 return
33 }
34 var req struct {
35 WrapIDHex string `json:"wrap_id_hex"`
36 KCVHex string `json:"kcv_hex"`
37 KDFParams string `json:"kdf_params"`
38 PublicKeyHex string `json:"login_public_key_hex"`
39 Sealed sealedJSON `json:"sealed"`
40 }
41 if !readJSON(w, r, &req) {
42 return
43 }
44 wrapID, e1 := hex.DecodeString(req.WrapIDHex)
45 kcv, e2 := hex.DecodeString(req.KCVHex)
46 publicKey, e3 := hex.DecodeString(req.PublicKeyHex)
47 sealed, e4 := req.Sealed.decode()
48 if e1 != nil || e2 != nil || e3 != nil || e4 != nil ||
49 len(publicKey) != crypto.PublicKeyLen {
50 writeJSON(w, http.StatusBadRequest,
51 map[string]string{"error": "bad recovery wrap"})
52 return
53 }
54
55 if err := v.store.ReplaceRecoveryWrap(r.Context(), acct, wrapID,
56 req.KDFParams, 1, kcv, sealed, publicKey); err != nil {
57 writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
58 return
59 }
60 writeJSON(w, http.StatusOK, map[string]string{"wrap_id_hex": req.WrapIDHex})
61}
62