-
Notifications
You must be signed in to change notification settings - Fork 2
/
sync_leader.go
142 lines (128 loc) · 3.34 KB
/
sync_leader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package internal
import (
"bytes"
cryptoRand "crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"golang.org/x/crypto/nacl/box"
)
var (
errExpectedEmptyKeys = errors.New("expected encrypted keys to be unset")
)
// leaderSync holds the state and code that we need for a one-off sync with a
// worker enclave.
type leaderSync struct {
attester
keys *enclaveKeys
}
// asLeader returns a new leaderSync struct.
func asLeader(keys *enclaveKeys, a attester) *leaderSync {
return &leaderSync{
attester: a,
keys: keys,
}
}
// syncWith makes the leader initiate key synchronization with the given worker
// enclave.
func (s *leaderSync) syncWith(worker *url.URL) (err error) {
var (
reqBody attstnBody
encrypted []byte
)
defer func() {
if err == nil {
elog.Printf("Successfully synced with worker %s.", worker.Host)
} else {
elog.Printf("Error syncing with worker %s: %v", worker.Host, err)
}
}()
// Step 1: Create a nonce that the worker must embed in its attestation
// document, to prevent replay attacks.
nonce, err := newNonce()
if err != nil {
return err
}
// Step 2: Request the worker's attestation document, and provide the
// previously-generated nonce.
reqURL := *worker
reqURL.RawQuery = fmt.Sprintf("nonce=%x", nonce)
resp, err := newUnauthenticatedHTTPClient().Get(reqURL.String())
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errNo200(resp.StatusCode)
}
// Step 3: Verify the worker's attestation document and extract its
// auxiliary information.
maxReadLen := base64.StdEncoding.EncodedLen(maxAttstnBodyLen)
jsonBody, err := io.ReadAll(newLimitReader(resp.Body, maxReadLen))
if err != nil {
return err
}
defer resp.Body.Close()
if err := json.Unmarshal(jsonBody, &reqBody); err != nil {
return err
}
if len(reqBody.EncryptedKeys) != 0 {
return errExpectedEmptyKeys
}
attstnDoc, err := base64.StdEncoding.DecodeString(reqBody.Document)
if err != nil {
return err
}
aux, err := s.verifyAttstn(attstnDoc, nonce)
if err != nil {
return err
}
workerAux := aux.(*workerAuxInfo)
// Step 4: Encrypt the leader's enclave keys with the ephemeral public key
// that the worker put into its auxiliary information.
pubKey := &[boxKeyLen]byte{}
copy(pubKey[:], workerAux.PublicKey[:])
jsonKeys, err := json.Marshal(s.keys.copy())
if err != nil {
return err
}
encrypted, err = box.SealAnonymous(nil, jsonKeys, pubKey, cryptoRand.Reader)
if err != nil {
return err
}
// Step 5: Create the leader's auxiliary information, consisting of the
// worker's nonce and a hash of the encrypted enclave keys.
hash := sha256.Sum256(encrypted)
leaderAux := &leaderAuxInfo{
WorkersNonce: workerAux.WorkersNonce,
HashOfEncrypted: hash[:],
}
attstnDoc, err = s.createAttstn(leaderAux)
if err != nil {
return err
}
// Step 6: Send the leader's attestation document to the worker.
jsonBody, err = json.Marshal(&attstnBody{
Document: base64.StdEncoding.EncodeToString(attstnDoc),
EncryptedKeys: base64.StdEncoding.EncodeToString(encrypted),
})
if err != nil {
return err
}
resp, err = newUnauthenticatedHTTPClient().Post(
worker.String(),
"text/plain",
bytes.NewReader(jsonBody),
)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errNo200(resp.StatusCode)
}
return nil
}