Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement key synchronization. #32

Merged
merged 99 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
990abad
Implement mechanism for leader designation.
NullHypothesis Aug 4, 2023
718214e
Implement mechanism to register worker enclaves.
NullHypothesis Aug 4, 2023
b19e1f7
Add handlers for key (re-)synchronization.
NullHypothesis Aug 8, 2023
e724427
Improve key sync protocol.
Aug 11, 2023
904468f
Move log message to the correct place.
Aug 11, 2023
f8f6e89
Remove debug code.
Aug 11, 2023
efeeb8b
Improve debug messages.
Aug 11, 2023
0b35752
Improve registration process.
Aug 11, 2023
75c059a
Refactor and test key synchronization.
Aug 15, 2023
2aa6d45
Fix proxy.go
Aug 15, 2023
bc8eb82
Add crude heartbeat mechanism.
Aug 15, 2023
f656393
Revise heartbeat mechanism.
Aug 15, 2023
099e2c9
Polish heartbeat mechanism.
Aug 15, 2023
9f444ca
Make worker initiate re-synchronization.
Aug 16, 2023
c72be54
Refactoring.
Aug 16, 2023
cb6b360
Add scripts for testing sync outside of enclaves.
Aug 16, 2023
060ca35
Update test scripts.
Aug 16, 2023
e9a4fb9
Remove unused constant.
Aug 16, 2023
4c79050
Terminate if enclave keys cannot be installed.
Aug 16, 2023
d306826
Remove annoying error messages.
Aug 16, 2023
be940b9
Address linter error.
Aug 16, 2023
4cd624a
Add log message.
Aug 16, 2023
6303f03
Fix bug.
Aug 16, 2023
269ec4d
Actually fix bug this time.
Aug 16, 2023
3eb556a
Improve test coverage.
Aug 16, 2023
ca9c01c
Improve log message.
Aug 16, 2023
d403d4b
Use goroutines for execution.
Aug 16, 2023
dbd88ea
Rename struct.
Aug 16, 2023
cc0636e
Fix capitalization.
Aug 16, 2023
0591be7
Tidy up dependencies.
Aug 16, 2023
697f033
Merge heartbeat and registration handler.
Aug 16, 2023
56a9745
Use self-signed certificates for tests to pass.
Aug 16, 2023
4a83286
Delete unused code block.
Aug 16, 2023
ee82653
Don't block on forAll.
Aug 16, 2023
165d947
Fix data race.
Aug 17, 2023
359336b
Run the "designate leader" code only once.
Aug 18, 2023
e27377f
Make attestation handler use attester interface.
Aug 18, 2023
33bcf39
More refactoring and shuffling code around.
Aug 18, 2023
d52948e
Remove debug message.
Aug 18, 2023
0557bda
Fix linter warning.
Aug 18, 2023
632f83a
Add work-in-progress documentation.
Aug 18, 2023
25f5427
Elaborate on key synchronization.
Aug 21, 2023
bc27aa4
Elaborate on heartbeat mechanism.
Aug 21, 2023
a6d22c8
Add installation of HTTPS certificate.
Aug 21, 2023
ba7b3e3
Shuffle variables around.
Aug 22, 2023
b3cf4c2
Remove TODO item.
Aug 22, 2023
d67f006
Use AWS metadata service to get worker hostname.
Aug 22, 2023
b0a7b76
Fix conditional address extraction.
Aug 22, 2023
fb34e56
Obtain hostname manually from IMDSv2.
Aug 23, 2023
689ab28
Add a way to determine hostname outside of enclaves.
Aug 23, 2023
3f80620
Simplify getWorker.
Aug 23, 2023
58fb3b5
Log worker's hostname.
Aug 23, 2023
4ce1d08
Fix incorrect function invocation.
Aug 23, 2023
9fa0f61
Remove debug message.
Aug 23, 2023
8b769eb
Make log message more descriptive.
Aug 23, 2023
c888027
Fix unit tests.
Aug 23, 2023
e36b808
Elaborate on image IDs.
Aug 28, 2023
35d6911
Use nitro attester by default.
Aug 28, 2023
0f42e6b
Elaborate on security considerations.
Aug 29, 2023
a1c1559
Test leader designation via self-probing.
Aug 29, 2023
f3f9167
Cleaning up previous commit.
Aug 29, 2023
e609422
Fix deadlock and add log messages.
Aug 29, 2023
36ff042
Re-attempt talking to leader designation endpoint.
Aug 29, 2023
2e0fba3
Raise read limit as 5K wasn't enough.
Aug 30, 2023
62c3dd1
Back off for a second before re-trying.
Aug 30, 2023
72ed9c4
Print differing PCR values.
Aug 30, 2023
308f43e
Start heartbeat loop after key synchronization.
Aug 30, 2023
374812b
Ignore PCR4 when comparing PCR values.
Aug 30, 2023
8990920
Dereference argument to fix bug.
Aug 30, 2023
d9605b0
Pad public key field if unused.
Aug 30, 2023
817cc81
Fix if condition.
Aug 30, 2023
aaf3c72
Transmit encrypted key material separately.
Aug 30, 2023
6e7cc50
Remove unused endpoint.
Aug 31, 2023
f3099fb
Implement Darnell's suggestion for state handlers.
Aug 31, 2023
18afc7a
Update tooling.
Aug 31, 2023
d13fb9f
Add tests for revised state handlers.
Aug 31, 2023
ae0fd4c
Rename helper functions for clarity.
Aug 31, 2023
436ed32
Add unit test.
Aug 31, 2023
277491e
Delete unused context.
Aug 31, 2023
193b2bd
Replace context with stop channel.
Aug 31, 2023
3bad177
Add draft of HTTP API docs.
Aug 31, 2023
a206598
Replace status code 410 with 403.
Sep 1, 2023
2a8f019
Add unit test.
Sep 11, 2023
e5245c9
Clean up and delete unnecessary code.
Sep 11, 2023
f66886c
Add test and clean up code.
Sep 11, 2023
2552574
Improve clarity and remove unnecessary function calls.
Sep 11, 2023
8638ab6
Use sync.Once and change return code after first run.
Sep 11, 2023
fba9cb7
Remove unused function.
Sep 11, 2023
fe82053
Remove comment.
Sep 11, 2023
2091010
Fix comment.
Sep 11, 2023
cd09cee
Use sync.Mutex instead.
Sep 11, 2023
44487e6
Only expose endpoint if necessary.
Sep 11, 2023
138580f
Expose Prometheus metrics for heartbeats.
Sep 11, 2023
1d6f59f
Minor improvements to clarity.
Sep 11, 2023
0fe0922
Remove unnecessary newline.
Sep 11, 2023
f1594eb
Update docs to match protocol.
Sep 11, 2023
eb37930
Limit the # of bytes we're willing to read.
Sep 11, 2023
94a9d73
Register heartbeat metrics with Prometheus.
Sep 25, 2023
a5fd697
Also run 'go vet' and govulncheck.
Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestAttestationHashes(t *testing.T) {
rec := httptest.NewRecorder()
buf := bytes.NewBufferString(base64.StdEncoding.EncodeToString(appKeyHash[:]))
req := httptest.NewRequest(http.MethodPost, pathHash, buf)
e.privSrv.Handler.ServeHTTP(rec, req)
e.intSrv.Handler.ServeHTTP(rec, req)

s := e.hashes.Serialize()
expectedLen := sha256.Size*2 + len(hashPrefix)*2 + len(hashSeparator)
Expand Down
173 changes: 173 additions & 0 deletions attester.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package main

import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"

"github.com/hf/nitrite"
"github.com/hf/nsm"
"github.com/hf/nsm/request"
)

// attester defines functions for the creation and verification of attestation
// documents. Making this an interface helps with testing: It allows us to
// implement a dummy attester that works without the AWS Nitro hypervisor.
type attester interface {
createAttstn(auxInfo) ([]byte, error)
verifyAttstn(doc []byte, isOurNonce func(string) bool) (auxInfo, error)
}

type auxInfo interface{}

// workerAuxInfo holds the auxiliary information of the worker's attestation
// document.
type workerAuxInfo struct {
WorkersNonce nonce `json:"workers_nonce"`
LeadersNonce nonce `json:"leaders_nonce"`
PublicKey []byte `json:"public_key"`
}

func (w workerAuxInfo) String() string {
return fmt.Sprintf("Worker's auxiliary info:\n"+
"Worker's nonce: %x\nLeader's nonce: %x\nPublic key: %x",
w.WorkersNonce, w.LeadersNonce, w.PublicKey)
}

// leaderAuxInfo holds the auxiliary information of the leader's attestation
// document.
type leaderAuxInfo struct {
WorkersNonce nonce `json:"workers_nonce"`
EnclaveKeys []byte `json:"enclave_keys"`
}

func (l leaderAuxInfo) String() string {
return fmt.Sprintf("Leader's auxiliary info:\n"+
"Worker's nonce: %x\nEnclave keys: %x",
l.WorkersNonce, l.EnclaveKeys)
}

// dummyAttester helps with local testing. The interface simply turns
// auxiliary information into JSON, and does not do any cryptography.
type dummyAttester struct{}

func (*dummyAttester) createAttstn(aux auxInfo) ([]byte, error) {
return json.Marshal(aux)
}

func (*dummyAttester) verifyAttstn(doc []byte, isOurNonce func(string) bool) (auxInfo, error) {
var w workerAuxInfo
var l leaderAuxInfo

// First, assume we're dealing with a worker's auxiliary information.
if err := json.Unmarshal(doc, &w); err != nil {
return nil, err
}
if len(w.WorkersNonce) == nonceLen && len(w.LeadersNonce) == nonceLen && w.PublicKey != nil {
if !isOurNonce(w.LeadersNonce.B64()) {
return nil, errors.New("leader nonce not in cache")
}
elog.Println(w)
return &w, nil
}

// Next, let's assume it's a leader.
if err := json.Unmarshal(doc, &l); err != nil {
return nil, err
}
if len(l.WorkersNonce) == nonceLen && l.EnclaveKeys != nil {
if !isOurNonce(l.WorkersNonce.B64()) {
return nil, errors.New("worker nonce not in cache")
}
elog.Println(l)
return &l, nil
}

return nil, errors.New("invalid auxiliary information")
}

// nitroAttester implements production functions for the creation and
// verification of attestation documents.
type nitroAttester struct{}

func (*nitroAttester) createAttstn(aux auxInfo) ([]byte, error) {
var nonce, userData, publicKey []byte

// Prepare our auxiliary information.
switch v := aux.(type) {
case workerAuxInfo:
nonce = v.WorkersNonce[:]
userData = v.LeadersNonce[:]
publicKey = v.PublicKey
case leaderAuxInfo:
nonce = v.WorkersNonce[:]
userData = v.EnclaveKeys
}

s, err := nsm.OpenDefaultSession()
if err != nil {
return nil, err
}
defer func() {
if err = s.Close(); err != nil {
elog.Printf("Attestation: Failed to close default NSM session: %s", err)
}
}()

res, err := s.Send(&request.Attestation{
Nonce: nonce,
UserData: userData,
PublicKey: publicKey,
})
if err != nil {
return nil, err
}
if res.Attestation == nil || res.Attestation.Document == nil {
return nil, errors.New("NSM device did not return an attestation")
}

return res.Attestation.Document, nil
}

func (*nitroAttester) verifyAttstn(doc []byte, isOurNonce func(string) bool) (auxInfo, error) {
errStr := "error verifying attestation document"
// Verify the remote enclave's attestation document before doing anything
// with it.
opts := nitrite.VerifyOptions{CurrentTime: currentTime()}
their, err := nitrite.Verify(doc, opts)
if err != nil {
return nil, fmt.Errorf("%s: %w", errStr, err)
}

// Verify that the remote enclave's PCR values (e.g., the image ID) are
// identical to ours.
ourPCRs, err := getPCRValues()
if err != nil {
return nil, fmt.Errorf("%s: %w", errStr, err)
}
if !arePCRsIdentical(ourPCRs, their.Document.PCRs) {
return nil, fmt.Errorf("%s: PCR values of remote enclave not identical to ours", errStr)
}

// Verify that the remote enclave's attestation document contains the nonce
// that we asked it to embed.
b64Nonce := base64.StdEncoding.EncodeToString(their.Document.Nonce)
if !isOurNonce(b64Nonce) {
return nil, fmt.Errorf("%s: nonce %s not in cache", errStr, b64Nonce)
}

// If the "public key" field is unset, we know that we're dealing with a
// worker's auxiliary information.
if their.Document.PublicKey != nil {
return &workerAuxInfo{
WorkersNonce: nonce(their.Document.Nonce),
LeadersNonce: nonce(their.Document.UserData),
PublicKey: their.Document.PublicKey,
}, nil
}
return &leaderAuxInfo{
WorkersNonce: nonce(their.Document.Nonce),
EnclaveKeys: their.Document.UserData,
}, nil
}
28 changes: 28 additions & 0 deletions certcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,39 @@ package main

import (
"context"
"crypto/tls"
"errors"
"sync"

"golang.org/x/crypto/acme/autocert"
)

// certRetriever stores an HTTPS certificate and implements the GetCertificate
// function signature, which allows our Web servers to retrieve the
// certificate when clients connect:
// https://pkg.go.dev/crypto/tls#Config
type certRetriever struct {
sync.RWMutex
cert *tls.Certificate
}

func (c *certRetriever) set(cert *tls.Certificate) {
c.Lock()
defer c.Unlock()

c.cert = cert
}

func (c *certRetriever) get(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
c.RLock()
defer c.RUnlock()

if c.cert == nil {
return nil, errors.New("certificate not yet initialized")
}
return c.cert, nil
}

// certCache implements the autocert.Cache interface.
type certCache struct {
sync.RWMutex
Expand Down
Loading