Skip to content

Commit

Permalink
Merge pull request #17 from Amnesic-Systems/add-reproducible-builds
Browse files Browse the repository at this point in the history
Add more tooling for remote attestation.
  • Loading branch information
NullHypothesis authored Nov 11, 2024
2 parents 308a61e + 8e56483 commit 3b2199d
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 57 deletions.
138 changes: 118 additions & 20 deletions cmd/veil-verify/main.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
package main

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

"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/httputil"
"github.com/Amnesic-Systems/veil/internal/nonce"
"github.com/Amnesic-Systems/veil/internal/util"
)

type config struct {
Addr string
Testing bool
addr string
verbose bool
testing bool
pcrs enclave.PCR
}

func parseFlags(out io.Writer, args []string) (*config, error) {
func parseFlags(out io.Writer, args []string) (_ *config, err error) {
defer errs.Wrap(&err, "failed to parse flags")

fs := flag.NewFlagSet("veil-verify", flag.ContinueOnError)
fs.SetOutput(out)

Expand All @@ -33,19 +43,84 @@ func parseFlags(out io.Writer, args []string) (*config, error) {
"",
"Address of the enclave, e.g.: https://example.com:8443",
)
pcrs := fs.String(
"pcrs",
"",
"JSON-encoded enclave image measurements as emitted by 'nitro-cli build'",
)
verbose := fs.Bool(
"verbose",
false,
"Enable verbose logging",
)
testing := fs.Bool(
"insecure",
false,
"Enable testing by disabling attestation",
)

if err := fs.Parse(args); err != nil {
return nil, fmt.Errorf("failed to parse flags: %w", err)
return nil, err
}

// Ensure that required arguments are set.
if *addr == "" {
return nil, errors.New("flag -addr must be provided")
}
if *pcrs == "" {
return nil, errors.New("flag -pcrs must be provided")
}

// Convert the given JSON-encoded enclave measurements to a PCR struct.
pcr, err := toPCR([]byte(*pcrs))
if err != nil {
return nil, err
}

return &config{
Addr: *addr,
Testing: *testing,
addr: *addr,
testing: *testing,
verbose: *verbose,
pcrs: pcr,
}, nil
}

func toPCR(jsonMsmts []byte) (_ enclave.PCR, err error) {
defer errs.Wrap(&err, "failed to convert measurements to PCR")

// 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
}

Expand All @@ -57,26 +132,23 @@ func run(ctx context.Context, out *os.File, args []string) error {
if err != nil {
return err
}
if cfg.Addr == "" {
return fmt.Errorf("missing addr argument")
}

return attestEnclave(ctx, cfg)
}

func attestEnclave(ctx context.Context, cfg *config) (err error) {
defer errs.Wrap(&err, "failed to attest enclave")

// 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 care about HTTPS
// Request the enclave's attestation document. We don't verify HTTPS
// certificates because authentication is happening via the attestation
// document.
client := httputil.NewNoAuthHTTPClient()
url := cfg.Addr + "/enclave/attestation?nonce=" + nonce.URLEncode()
url := cfg.addr + "/enclave/attestation?nonce=" + nonce.URLEncode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
Expand All @@ -95,24 +167,50 @@ func attestEnclave(ctx context.Context, cfg *config) (err error) {
return err
}
defer resp.Body.Close()
var doc enclave.RawDocument
if err := json.Unmarshal(body, &doc); err != nil {
var rawDoc enclave.RawDocument
if err := json.Unmarshal(body, &rawDoc); err != nil {
return err
}

// Finally, verify the attestation document.
// 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 {
if cfg.testing {
attester = noop.NewAttester()
}
_, err = attester.Verify(&doc, nonce)
return err
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 !cfg.pcrs.Equal(doc.PCRs) {
log.Println("Enclave's code DOES NOT match local code!")
if cfg.verbose {
log.Printf("Expected\n%sbut got\n%s", cfg.pcrs, doc.PCRs)
}
} else {
log.Println("Enclave's code matches local code!")
}

return nil
}

func main() {
ctx := context.Background()
if err := run(ctx, os.Stdout, os.Args[1:]); err != nil {
log.Fatalf("Failed to run verifier: %v", err)
}
log.Println("Attestation successful")
}
2 changes: 1 addition & 1 deletion internal/enclave/attester.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type Document struct {
ModuleID string `cbor:"module_id" json:"module_id"`
Timestamp uint64 `cbor:"timestamp" json:"timestamp"`
Digest string `cbor:"digest" json:"digest"`
PCRs pcr `cbor:"pcrs" json:"pcrs"`
PCRs PCR `cbor:"pcrs" json:"pcrs"`
Certificate []byte `cbor:"certificate" json:"certificate"`
CABundle [][]byte `cbor:"cabundle" json:"cabundle"`
AuxInfo
Expand Down
7 changes: 0 additions & 7 deletions internal/enclave/nitro/verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import (
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -330,12 +329,6 @@ func verify(data []byte, options verifyOptions) (_ *Result, err error) {
return nil, errors.New("payload's signature does not match signature from certificate")
}

b, err := json.Marshal(doc.PCRs)
if err != nil {
return nil, err
}
fmt.Println(string(b))

return &Result{
Document: &doc,
Certificates: certificates,
Expand Down
27 changes: 16 additions & 11 deletions internal/enclave/pcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,39 @@ package enclave
import (
"bytes"
"crypto/sha512"
"fmt"
)

var emptyPCR = make([]byte, sha512.Size384)

// pcr represents the enclave's platform configuration register (PCR) values.
type pcr map[uint][]byte
// PCR represents the enclave's platform configuration register (PCR) values.
type PCR map[uint][]byte

// FromDebugMode returns true if the given PCR map was generated by an enclave
// in debug mode.
func (p pcr) FromDebugMode() bool {
func (p PCR) FromDebugMode() bool {
// PCRs 0, 1, and 2 are set at compile time but will be unset if the enclave
// is running in debug mode.
return bytes.Equal(p[0], emptyPCR) &&
bytes.Equal(p[1], emptyPCR) &&
bytes.Equal(p[2], emptyPCR)
}

// Equal returns true if (and only if) the two given PCR maps are identical.
func (ours pcr) Equal(theirs pcr) bool {
if len(ours) != len(theirs) {
return false
// String returns a human-readable representation of the PCR map.
func (p PCR) String() string {
s := ""
for key, val := range p {
s += fmt.Sprintf("%d: %x\n", key, val)
}
return s
}

// Equal returns true if (and only if) the two given PCR maps are identical.
func (ours PCR) Equal(theirs PCR) bool {
for i, ourValue := range ours {
// PCR4 contains a hash over the parent's instance ID. If horizontal
// scaling is enabled, enclaves run on different parent instances, so
// PCR4 will differ:
// https://docs.aws.amazon.com/enclaves/latest/user/set-up-attestation.html
// PCR4 contains a hash over the parent's instance ID, which is known at
// runtime. We ignore it for now, until we have a better solution on
// how to handle this.
if i == 4 {
continue
}
Expand Down
Loading

0 comments on commit 3b2199d

Please sign in to comment.