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

key attestation verification for Nitro #32

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 246 additions & 0 deletions arc/cmd/verify_kat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
// Copyright 2023 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0
package cmd

import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"strings"
"time"

"github.com/hf/nitrite"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

var (
verifyKatInput string
verifyKatAttesterType string
verifyKatRefValues string
verifyKatEndorsements string
verifyKatClockSkew time.Duration
)

var verifyKatCmd = NewVerifyKatCmd()

func NewVerifyKatCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "verify-kat [flags] <KAT file>",
Short: "verify a key attestation of a EAR signing key",
Long: `Verify a key attestation of a EAR signing key using optional
endorsements and reference values.

The following example verifies the key (and platform) attestation of a
Veraison deployment that runs in a AWS Nitro enclave. This assumes the key
attestation is verified offline at a later point in time. Therefore, a
clock skew of 10 hours is give to adjust the key attestation key validity.

arc verify-kat \
--attester aws-nitro \
--refval data/nitro-ref-values.json \
--clock-skew -10h \
data/nitro-key-attestation.cbor

`,
RunE: func(cmd *cobra.Command, args []string) error {
var (
katBytes []byte
err error
)

if err = checkVerifyKatArgs(args); err != nil {
return fmt.Errorf("validating arguments: %w", err)
}

verifyKatInput = args[0]

if katBytes, err = afero.ReadFile(fs, verifyKatInput); err != nil {
return fmt.Errorf("loading key attestation from %q: %w", verifyKatInput, err)
}

// at this point the verifyKatAttesterType argument has already been
// sanitized by checkVerifyKatArgs
verify := attesterHandler[verifyKatAttesterType]

return verify(katBytes, verifyKatRefValues, verifyKatClockSkew)
},
}

cmd.Flags().StringVarP(
&verifyKatAttesterType,
"attester",
"a",
"",
fmt.Sprintf("attester type, one of: %s", strings.Join(supportedAttesterTypes(), ",")),
)
_ = cmd.MarkFlagRequired("attester")

cmd.Flags().StringVarP(
&verifyKatRefValues,
"refval",
"r",
"",
"file containing reference values",
)

cmd.Flags().StringVarP(
&verifyKatEndorsements,
"endorsements",
"e",
"",
"file containing endorsements",
)

cmd.Flags().DurationVarP(
&verifyKatClockSkew,
"clock-skew",
"c",
0,
"clock skew expressed as time duration (e.g., 10h, -2h45m)",
)

return cmd
}

type AttesterHandler func(kat []byte, rv string, clockSkew time.Duration) error

type NitroRefValues struct {
Measurements NitroMeasurements
}

type NitroMeasurements struct {
HashAlgorithm string
PCR0 HexString
PCR1 HexString
PCR2 HexString
PCR3 HexString
PCR4 HexString
PCR8 HexString
}

type HexString []byte

func (o *HexString) UnmarshalJSON(b []byte) error {
var (
s string
err error
)

if err = json.Unmarshal(b, &s); err != nil {
return fmt.Errorf("unmarshaling hex string: %w", err)
}

if *o, err = hex.DecodeString(s); err != nil {
return fmt.Errorf("decoding hex string: %w", err)
}

return nil
}

func nitroLoadRefValues(rv string) (*NitroMeasurements, error) {
var m NitroRefValues

b, err := afero.ReadFile(fs, rv)
if err != nil {
return nil, fmt.Errorf("reading file: %w", err)
}

if err = json.Unmarshal(b, &m); err != nil {
return nil, fmt.Errorf("unmarshaling JSON: %w", err)
}

return &m.Measurements, nil
}

func NitroHandler(kat []byte, rvFile string, clockSkew time.Duration) error {
var (
rvs *NitroMeasurements
err error
)

if rvFile != "" {
rvs, err = nitroLoadRefValues(rvFile)
if err != nil {
return fmt.Errorf("loading aws-nitro reference values from %q: %w", rvFile, err)
}
}

t := time.Now().Add(clockSkew)
opts := nitrite.VerifyOptions{CurrentTime: t}

res, err := nitrite.Verify(kat, opts)
if err != nil {
return fmt.Errorf("verification of aws-nitro attestation document failed: %w", err)
}

if rvs != nil {
var expected, actual []byte

for _, i := range []uint{0, 1, 2, 3, 4, 8} {
switch i {
case 0:
expected = rvs.PCR0
case 1:
expected = rvs.PCR1
case 2:
expected = rvs.PCR2
case 3:
expected = rvs.PCR3
case 4:
expected = rvs.PCR4
case 8:
expected = rvs.PCR8
}

if len(expected) == 0 {
continue
}

actual = res.Document.PCRs[i]

if bytes.Equal(expected, actual) {
fmt.Printf("PCR[%d] ok\n", i)
} else {
return fmt.Errorf("PCR[%d] check failed: want %x, got %x", i, expected, actual)
}
}
}

fmt.Printf(">> Attested public key: %s\n\n", string(res.Document.PublicKey))

return nil
}

var attesterHandler = map[string]AttesterHandler{
"aws-nitro": NitroHandler,
}

func supportedAttesterTypes() []string {
a := make([]string, 0, len(attesterHandler))

for k := range attesterHandler {
a = append(a, k)
}

return a
}

func checkVerifyKatArgs(args []string) error {
if len(args) != 1 {
return errors.New("no KAT file supplied")
}

_, ok := attesterHandler[verifyKatAttesterType]
if !ok {
return fmt.Errorf("unsupported attester type: %s", verifyKatAttesterType)
}

return nil
}

func init() {
rootCmd.AddCommand(verifyKatCmd)
}
118 changes: 118 additions & 0 deletions arc/cmd/verify_kat_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright 2023 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0
package cmd

import (
"testing"

"github.com/stretchr/testify/assert"
)

func Test_VerifyKatCmd_unknown_argument(t *testing.T) {
cmd := NewVerifyKatCmd()

args := []string{"--unknown-argument=val"}
cmd.SetArgs(args)

err := cmd.Execute()
assert.EqualError(t, err, "unknown flag: --unknown-argument")
}

func Test_VerifyKatCmd_no_kat_file(t *testing.T) {
cmd := NewVerifyKatCmd()

cmd.SetArgs([]string{"-a aws-nitro"})

err := cmd.Execute()
assert.EqualError(t, err, "validating arguments: no KAT file supplied")
}

func Test_VerifyKatCmd_unknown_attester_type(t *testing.T) {
cmd := NewVerifyKatCmd()

cmd.SetArgs([]string{
"--attester=xyz",
"kat-file",
})

err := cmd.Execute()
assert.EqualError(t, err, "validating arguments: unsupported attester type: xyz")
}

func Test_VerifyKatCmd_kat_file_not_found(t *testing.T) {
cmd := NewVerifyKatCmd()

args := []string{
"--attester=aws-nitro",
"non-existent",
}
cmd.SetArgs(args)

expectedErr := `loading key attestation from "non-existent": open non-existent: file does not exist`

err := cmd.Execute()
assert.EqualError(t, err, expectedErr)
}

func Test_VerifyKatCmd_kat_file_bad_format(t *testing.T) {
cmd := NewVerifyKatCmd()

files := []fileEntry{
{"kat.jwt", []byte("")},
}
makeFS(t, files)

args := []string{
"--attester=aws-nitro",
"kat.jwt",
}
cmd.SetArgs(args)

expectedErr := `verification of aws-nitro attestation document failed: Data is not a COSESign1 array`

err := cmd.Execute()
assert.EqualError(t, err, expectedErr)
}

func Test_VerifyKatCmd_refvalue_bad_format(t *testing.T) {
cmd := NewVerifyKatCmd()

files := []fileEntry{
{"bad-refval.json", []byte(`{ "Measurements": { "PCR0": "XYZ"} }`)},
{"kat.jwt", []byte("")},
}
makeFS(t, files)

args := []string{
"--attester=aws-nitro",
"--refval=bad-refval.json",
"kat.jwt",
}
cmd.SetArgs(args)

expectedErr := `loading aws-nitro reference values from "bad-refval.json": unmarshaling JSON: decoding hex string: encoding/hex: invalid byte: U+0058 'X'`

err := cmd.Execute()
assert.EqualError(t, err, expectedErr)
}

func Test_VerifyKatCmd_refvalue_file_not_found(t *testing.T) {
cmd := NewVerifyKatCmd()

files := []fileEntry{
{"kat.jwt", []byte("")},
}
makeFS(t, files)

args := []string{
"--attester=aws-nitro",
"--refval=non-existent",
"kat.jwt",
}
cmd.SetArgs(args)

expectedErr := `loading aws-nitro reference values from "non-existent": reading file: open non-existent: file does not exist`

err := cmd.Execute()
assert.EqualError(t, err, expectedErr)
}
Binary file added arc/data/nitro-key-attestation.cbor
Binary file not shown.
8 changes: 8 additions & 0 deletions arc/data/nitro-ref-values.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Measurements": {
"HashAlgorithm": "Sha384 { ... }",
"PCR0": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"PCR1": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"PCR2": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
}
}
Loading