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

Handle all of attestation in veil-verify. #32

Merged
merged 7 commits into from
Nov 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
.vscode
cmd/veil/veil
cmd/veil-verify/veil-verify
cover.html
cover.out
veil.tar
veil.eif
*.tar
*.eif
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
prog = veil
prog_dir = cmd/veil
verify_prog = veil-verify
verify_prog_dir = cmd/veil-verify
godeps = go.mod go.sum $(shell find cmd internal -name "*.go" -type f)

image_tag := $(prog)
Expand Down Expand Up @@ -81,10 +83,15 @@ $(prog): $(godeps)
-ldflags="-s -w" \
-buildvcs=false \
-o $(prog)
@echo "$(prog_dir)/$(prog)"
@-sha1sum "$(prog_dir)/$(prog)"

$(verify_prog): $(godeps)
@go build -C $(verify_prog_dir) -o $(verify_prog)
@-sha1sum "$(verify_prog_dir)/$(verify_prog)"

.PHONY: clean
clean:
rm -f $(prog_dir)/$(prog)
rm -f $(verify_prog_dir)/$(verify_prog)
rm -f $(cover_out) $(cover_html)
rm -f $(image_tar) $(image_eif)
145 changes: 145 additions & 0 deletions cmd/veil-verify/attestation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package main

import (
"bytes"
"context"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"strings"

"github.com/fatih/color"

"github.com/Amnesic-Systems/veil/internal/enclave"
"github.com/Amnesic-Systems/veil/internal/enclave/nitro"
"github.com/Amnesic-Systems/veil/internal/enclave/noop"
"github.com/Amnesic-Systems/veil/internal/errs"
"github.com/Amnesic-Systems/veil/internal/httpx"
"github.com/Amnesic-Systems/veil/internal/nonce"
"github.com/Amnesic-Systems/veil/internal/util"
)

var (
errFailedToAttest = errors.New("failed to attest enclave")
errFailedToConvert = errors.New("failed to convert measurements to PCR")
)

func attestEnclave(
ctx context.Context,
cfg *config,
pcrs enclave.PCR,
) (err error) {
defer errs.WrapErr(&err, errFailedToAttest)

// Generate a nonce to ensure that the attestation document is fresh.
nonce, err := nonce.New()
if err != nil {
return err
}

// Request the enclave's attestation document. We don't verify HTTPS
// certificates because authentication is happening via the attestation
// document.
client := httpx.NewUnauthClient()
url := cfg.addr + "/enclave/attestation?nonce=" + nonce.URLEncode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("got status %d from enclave", resp.StatusCode)
}

// Parse the attestation document.
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
defer resp.Body.Close()
var rawDoc enclave.RawDocument
if err := json.Unmarshal(body, &rawDoc); err != nil {
return err
}

// Verify the attestation document, which provides assurance that we are
// talking to an enclave. The nonce provides assurance that we are talking
// to an alive enclave (instead of a replayed attestation document).
var attester enclave.Attester = nitro.NewAttester()
if cfg.testing {
attester = noop.NewAttester()
}
doc, err := attester.Verify(&rawDoc, nonce)
if err != nil {
return err
}

// Delete empty PCR values from the attestation document. This is not
// ideal; we should either have the rest of the code tolerate empty PCR
// values or fix the nsm package, so it doesn't return empty PCR values.
empty := make([]byte, sha512.Size384)
for i, pcr := range doc.PCRs {
if bytes.Equal(pcr, empty) {
delete(doc.PCRs, i)
}
}

// Verify the attestation document's PCR values, which provide assurance
// that the remote enclave's image and kernel match the local copy.
if !pcrs.Equal(doc.PCRs) {
log.Printf("Expected PCRs:\n%sbut got PCRs:\n%s", pcrs, doc.PCRs)
color.Red("Enclave's code DOES NOT match local code!")
} else {
color.Green("Enclave's code matches local code!")
}

return nil
}

func toPCR(jsonMsmts []byte) (_ enclave.PCR, err error) {
defer errs.WrapErr(&err, errFailedToConvert)

// This structs represents the JSON-encoded measurements of the enclave
// image. The JSON tags must match the output of the nitro-cli command
// line tool. An example:
//
// {
// "Measurements": {
// "HashAlgorithm": "Sha384 { ... }",
// "PCR0": "8b927cf0bbf2d668a8c24c69afd23bff2dda713b4f0d70195205950f9a5a1fbb7089ad937e3025ee8d5a084f3d6c9126",
// "PCR1": "4b4d5b3661b3efc12920900c80e126e4ce783c522de6c02a2a5bf7af3a2b9327b86776f188e4be1c1c404a129dbda493",
// "PCR2": "22d2194eb27a7cda42e66dd5b91ef13e5a153d797c04ae179e59bef1c93438d6ad0365c175c119230e36d0f8d6b6b59e"
// }
// }
m := struct {
Measurements struct {
HashAlgorithm string `json:"HashAlgorithm"`
PCR0 string `json:"PCR0"`
PCR1 string `json:"PCR1"`
PCR2 string `json:"PCR2"`
} `json:"Measurements"`
}{}
if err := json.Unmarshal(jsonMsmts, &m); err != nil {
return nil, err
}

const want = "sha384"
got := strings.ToLower(m.Measurements.HashAlgorithm)
if !strings.HasPrefix(got, want) {
return nil, fmt.Errorf("expected hash algorithm %q but got %q", want, got)
}

return enclave.PCR{
0: util.Must(hex.DecodeString(m.Measurements.PCR0)),
1: util.Must(hex.DecodeString(m.Measurements.PCR1)),
2: util.Must(hex.DecodeString(m.Measurements.PCR2)),
}, nil
}
62 changes: 62 additions & 0 deletions cmd/veil-verify/attestation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package main

import (
"testing"

"github.com/Amnesic-Systems/veil/internal/enclave"
"github.com/stretchr/testify/require"
)

func TestToPCR(t *testing.T) {
cases := []struct {
name string
in []byte
wantPCRs enclave.PCR
wantErr bool
}{
{
name: "invalid json",
in: []byte("invalid"),
wantErr: true,
},
{
name: "invalid hash",
in: []byte(`{
"Measurements": {
"HashAlgorithm": "Sha512 { ... }",
"PCR0": "616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161",
"PCR1": "626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262626262",
"PCR2": "636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363636363"
}
}`),
wantErr: true,
},
{
name: "invalid PCR value",
in: []byte(`{
"Measurements": {
"HashAlgorithm": "Sha512 { ... }",
"PCR0": "foobar",
}
}`),
wantErr: true,
},
{
name: "valid",
in: []byte(validPCRs),
wantPCRs: enclave.PCR{
0: []byte("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
1: []byte("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
2: []byte("cccccccccccccccccccccccccccccccccccccccccccccccc"),
},
},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
gotPCRs, err := toPCR(c.in)
require.Equal(t, c.wantErr, err != nil)
require.True(t, gotPCRs.Equal(c.wantPCRs))
})
}
}
Loading