-
Notifications
You must be signed in to change notification settings - Fork 9
/
sync_worker.go
231 lines (207 loc) · 6.37 KB
/
sync_worker.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package main
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"time"
"golang.org/x/crypto/nacl/box"
)
var (
errInProgress = errors.New("key sync already in progress")
errFailedToDecrypt = errors.New("error decrypting enclave keys")
errHashNotInAttstn = errors.New("hash of encrypted keys not in attestation document")
)
// workerSync holds the state and code that we need for a one-off sync with a
// leader enclave. workerSync implements the http.Handler interface because the
// sync protocol requires two endpoints on the worker.
type workerSync struct {
attester
setupWorker func(*enclaveKeys) error
ephemeralKeys chan *boxKey
nonce chan nonce
}
// asWorker returns a new workerSync object.
func asWorker(
setupWorker func(*enclaveKeys) error,
a attester,
) *workerSync {
return &workerSync{
attester: a,
setupWorker: setupWorker,
nonce: make(chan nonce, 1),
ephemeralKeys: make(chan *boxKey, 1),
}
}
type heartbeatRequest struct {
HashedKeys string `json:"hashed_keys"`
WorkerHostname string `json:"worker_hostname"`
}
// registerWith registers the given worker with the given leader enclave.
func (s *workerSync) registerWith(leader, worker *url.URL) error {
elog.Println("Attempting to sync with leader.")
errChan := make(chan error)
register := func(e chan error) {
body, err := json.Marshal(heartbeatRequest{WorkerHostname: worker.Host})
if err != nil {
e <- err
return
}
resp, err := newUnauthenticatedHTTPClient().Post(leader.String(), "text/plain", bytes.NewBuffer(body))
if err != nil {
e <- err
return
}
if resp.StatusCode != http.StatusOK {
e <- fmt.Errorf("leader returned HTTP code %d", resp.StatusCode)
return
}
e <- nil
}
go register(errChan)
// Keep on trying every five seconds, for a minute.
retry := time.NewTicker(5 * time.Second)
timeout := time.NewTicker(time.Minute)
for {
select {
case err := <-errChan:
if err == nil {
elog.Println("Successfully registered with leader.")
return nil
}
elog.Printf("Error registering with leader: %v", err)
case <-timeout.C:
return errors.New("timed out syncing with leader")
case <-retry.C:
go register(errChan)
}
}
}
func (s *workerSync) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
s.initSync(w, r)
} else if r.Method == http.MethodPost {
s.finishSync(w, r)
}
}
// initSync responds to the leader's request for initiating key synchronization.
func (s *workerSync) initSync(w http.ResponseWriter, r *http.Request) {
elog.Println("Received leader's request to initiate key sync.")
// There must not be more than one key synchronization attempt at any given
// time. Abort if we get another request while key synchronization is still
// in progress.
if len(s.ephemeralKeys) > 0 {
http.Error(w, errInProgress.Error(), http.StatusTooManyRequests)
return
}
// Extract the leader's nonce from the URL, which must look like this:
// https://example.com/enclave/sync?nonce=[HEX-ENCODED-NONCE]
leadersNonce, err := getNonceFromReq(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
// Create the worker's nonce and store it in our channel, so we can later
// verify it.
workersNonce, err := newNonce()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.nonce <- workersNonce
// Create an ephemeral key that the leader is going to use to encrypt
// its enclave keys.
boxKey, err := newBoxKey()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
s.ephemeralKeys <- boxKey
// Create and return the worker's Base64-encoded attestation document.
attstnDoc, err := s.createAttstn(&workerAuxInfo{
WorkersNonce: workersNonce,
LeadersNonce: leadersNonce,
PublicKey: boxKey.pubKey[:],
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
respBody, err := json.Marshal(&attstnBody{
Document: base64.StdEncoding.EncodeToString(attstnDoc),
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
fmt.Fprintln(w, string(respBody))
}
// finishSync responds to the leader's final request before key synchronization
// is complete.
func (s *workerSync) finishSync(w http.ResponseWriter, r *http.Request) {
var (
reqBody attstnBody
keys enclaveKeys
)
elog.Println("Received leader's request to complete key sync.")
// Read the leader's Base64-encoded attestation document.
maxReadLen := base64.StdEncoding.EncodedLen(maxAttstnBodyLen)
jsonBody, err := io.ReadAll(newLimitReader(r.Body, maxReadLen))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := json.Unmarshal(jsonBody, &reqBody); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
}
attstnDoc, err := base64.StdEncoding.DecodeString(reqBody.Document)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Verify attestation document and obtain its auxiliary information.
aux, err := s.verifyAttstn(attstnDoc, <-s.nonce)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
leaderAux := aux.(*leaderAuxInfo)
encrypted, err := base64.StdEncoding.DecodeString(reqBody.EncryptedKeys)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Make sure that the hash of the encrypted key material is present in the
// attestation document.
hash := sha256.Sum256(encrypted)
if !bytes.Equal(hash[:], leaderAux.HashOfEncrypted) {
http.Error(w, errHashNotInAttstn.Error(), http.StatusBadRequest)
return
}
ephemeralKey := <-s.ephemeralKeys
// Decrypt the leader's enclave keys, which are encrypted with the
// public key that we provided earlier.
decrypted, ok := box.OpenAnonymous(
nil,
encrypted,
ephemeralKey.pubKey,
ephemeralKey.privKey)
if !ok {
http.Error(w, errFailedToDecrypt.Error(), http.StatusBadRequest)
return
}
// Install the leader's enclave keys.
if err := json.Unmarshal(decrypted, &keys); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if err := s.setupWorker(&keys); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
elog.Fatalf("Failed to install enclave keys: %v", err)
}
elog.Printf("Successfully synced keys %s with leader.", keys.hashAndB64())
}