diff --git a/attestation.bin b/attestation.bin index 1a4cdc6a..364fa960 100644 Binary files a/attestation.bin and b/attestation.bin differ diff --git a/cli/checksum.go b/cli/checksum.go index bcbadfcc..2db0874c 100644 --- a/cli/checksum.go +++ b/cli/checksum.go @@ -3,12 +3,24 @@ package cli import ( + "encoding/base64" + "encoding/hex" + "encoding/json" + "os" + "github.com/spf13/cobra" + "github.com/ultravioletrs/cocos/agent" "github.com/ultravioletrs/cocos/internal" + "golang.org/x/crypto/sha3" +) + +var ( + ismanifest bool + toBase64 bool ) func (cli *CLI) NewFileHashCmd() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "checksum", Short: "Compute the sha3-256 hash of a file", Example: "checksum ", @@ -16,13 +28,68 @@ func (cli *CLI) NewFileHashCmd() *cobra.Command { Run: func(cmd *cobra.Command, args []string) { path := args[0] + if ismanifest { + hash, err := manifestChecksum(path) + if err != nil { + printError(cmd, "Error computing hash: %v ❌ ", err) + return + } + + cmd.Println("Hash of manifest file:", hashOut(hash)) + return + } + hash, err := internal.ChecksumHex(path) if err != nil { printError(cmd, "Error computing hash: %v ❌ ", err) return } - cmd.Println("Hash of file:", hash) + cmd.Println("Hash of file:", hashOut(hash)) }, } + + cmd.Flags().BoolVarP(&ismanifest, "manifest", "m", false, "Compute the hash of the manifest file") + cmd.Flags().BoolVarP(&toBase64, "base64", "b", false, "Output the hash in base64") + + return cmd +} + +func manifestChecksum(path string) (string, error) { + file, err := os.ReadFile(path) + if err != nil { + return "", err + } + + var cmp agent.Computation + + if err := json.Unmarshal(file, &cmp); err != nil { + return "", err + } + + jsonBytes, err := json.Marshal(cmp) + if err != nil { + return "", err + } + + sum := sha3.Sum256(jsonBytes) + + return hex.EncodeToString(sum[:]), nil +} + +func hashOut(hashHex string) string { + if toBase64 { + return hexToBase64(hashHex) + } + + return hashHex +} + +func hexToBase64(hexStr string) string { + decoded, err := hex.DecodeString(hexStr) + if err != nil { + return "" + } + + return base64.StdEncoding.EncodeToString(decoded) } diff --git a/cli/checksum_test.go b/cli/checksum_test.go index 93d0185c..0d03a2cf 100644 --- a/cli/checksum_test.go +++ b/cli/checksum_test.go @@ -4,11 +4,12 @@ package cli import ( "bytes" + "fmt" "os" "strings" "testing" - "github.com/ultravioletrs/cocos/internal" + "github.com/stretchr/testify/assert" ) func TestNewFileHashCmd(t *testing.T) { @@ -29,74 +30,201 @@ func TestNewFileHashCmd(t *testing.T) { } func TestNewFileHashCmdRun(t *testing.T) { - cli := &CLI{} - cmd := cli.NewFileHashCmd() - - content := []byte("test content") - tmpfile, err := os.CreateTemp("", "example") - if err != nil { - t.Fatal(err) - } - defer os.Remove(tmpfile.Name()) - - if _, err := tmpfile.Write(content); err != nil { - t.Fatal(err) - } - if err := tmpfile.Close(); err != nil { - t.Fatal(err) + testCases := []struct { + name string + isManifest bool + toBase64 bool + expectedOut string + expectedErr string + }{ + { + name: "Valid file", + isManifest: false, + toBase64: false, + expectedOut: "Hash of file:", + expectedErr: "", + }, + { + name: "Valid manifest file", + isManifest: true, + toBase64: false, + expectedOut: "Hash of manifest file:", + expectedErr: "", + }, + { + name: "Valid file with base64 output", + isManifest: false, + toBase64: true, + expectedOut: "Hash of file:", + expectedErr: "", + }, + { + name: "Non-existent file", + isManifest: false, + toBase64: false, + expectedOut: "Error computing hash:", + expectedErr: "", + }, } - var output bytes.Buffer - cmd.SetOut(&output) - cmd.SetErr(&output) - - cmd.SetArgs([]string{tmpfile.Name()}) - err = cmd.Execute() - if err != nil { - t.Fatalf("Error executing command: %v", err) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cli := &CLI{} + cmd := cli.NewFileHashCmd() + + var output bytes.Buffer + cmd.SetOut(&output) + cmd.SetErr(&output) + + err := cmd.Flags().Set("manifest", fmt.Sprint(tc.isManifest)) + assert.Nil(t, err) + err = cmd.Flags().Set("base64", fmt.Sprint(tc.toBase64)) + assert.Nil(t, err) + + if tc.name == "Non-existent file" { + cmd.SetArgs([]string{"non_existent_file.txt"}) + } else { + content := []byte("{}") + tmpfile, err := os.CreateTemp("", "example") + if err != nil { + t.Fatal(err) + } + defer os.Remove(tmpfile.Name()) + + if _, err := tmpfile.Write(content); err != nil { + t.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + t.Fatal(err) + } + + cmd.SetArgs([]string{tmpfile.Name()}) + } + + err = cmd.Execute() + if err != nil { + t.Fatalf("Error executing command: %v", err) + } + + out := output.String() + if !strings.Contains(out, tc.expectedOut) { + t.Errorf("Expected output to contain '%s', got '%s'", tc.expectedOut, out) + } + + if tc.expectedErr != "" && !strings.Contains(out, tc.expectedErr) { + t.Errorf("Expected output to contain '%s', got '%s'", tc.expectedErr, out) + } + }) } +} - expectedHash, err := internal.ChecksumHex(tmpfile.Name()) - if err != nil { - t.Fatalf("Error computing expected hash: %v", err) +func TestManifestChecksum(t *testing.T) { + testCases := []struct { + name string + jsonContent string + expectedSum string + }{ + { + name: "Valid manifest file", + jsonContent: `{ + "id": "1234", + "name": "Example Computation", + "description": "This is an example computation" + }`, + expectedSum: "868825367c32c4b6d621d5d95e2890f233d8554df2348ab743aac2663a936f08", + }, + { + name: "Invalid JSON", + jsonContent: `{`, + expectedSum: "", + }, } - if !strings.Contains(output.String(), expectedHash) { - t.Errorf("Expected output to contain hash %s, got %s", expectedHash, output.String()) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + f, err := os.CreateTemp("", "test") + assert.Nil(t, err) + + t.Cleanup(func() { + os.Remove(f.Name()) + }) + + _, err = f.WriteString(tc.jsonContent) + assert.NoError(t, err) + + err = f.Close() + assert.Nil(t, err) + + hash, err := manifestChecksum(f.Name()) + if tc.expectedSum == "" && err == nil { + t.Errorf("Expected error, got nil") + } + if tc.expectedSum != "" && err != nil { + t.Errorf("Unexpected error: %v", err) + } + if hash != tc.expectedSum { + t.Errorf("Expected hash %s, got %s", tc.expectedSum, hash) + } + }) } } -func TestNewFileHashCmdInvalidArgs(t *testing.T) { - cli := &CLI{} - cmd := cli.NewFileHashCmd() - - err := cmd.Execute() - if err == nil { - t.Error("Expected error when executing without arguments, got nil") +func TestHexToBase64(t *testing.T) { + testCases := []struct { + name string + hexInput string + expectedOut string + }{ + { + name: "Valid hex input", + hexInput: "48656c6c6f", + expectedOut: "SGVsbG8=", + }, + { + name: "Invalid hex input", + hexInput: "invalid-hex", + expectedOut: "", + }, } - cmd.SetArgs([]string{"file1", "file2"}) - err = cmd.Execute() - if err == nil { - t.Error("Expected error when executing with too many arguments, got nil") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + out := hexToBase64(tc.hexInput) + if out != tc.expectedOut { + t.Errorf("Expected %s, got %s", tc.expectedOut, out) + } + }) } } -func TestNewFileHashCmdNonExistentFile(t *testing.T) { - cli := &CLI{} - cmd := cli.NewFileHashCmd() - - var output bytes.Buffer - cmd.SetOut(&output) - cmd.SetErr(&output) - - cmd.SetArgs([]string{"non_existent_file.txt"}) - err := cmd.Execute() - if err != nil { - t.Fatalf("Error executing command: %v", err) +func TestHashOut(t *testing.T) { + testCases := []struct { + name string + hashHex string + toBase64 bool + expectedOut string + }{ + { + name: "Hex output", + hashHex: "48656c6c6f", + toBase64: false, + expectedOut: "48656c6c6f", + }, + { + name: "Base64 output", + hashHex: "48656c6c6f", + toBase64: true, + expectedOut: "SGVsbG8=", + }, } - if !strings.Contains(output.String(), "Error computing hash") { - t.Errorf("Expected output to contain 'Error computing hash', got %s", output.String()) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + toBase64 = tc.toBase64 + out := hashOut(tc.hashHex) + if out != tc.expectedOut { + t.Errorf("Expected %s, got %s", tc.expectedOut, out) + } + }) } } diff --git a/pkg/attestation/quoteprovider/embed.go b/pkg/attestation/quoteprovider/embed.go index 8ad7a8b8..c92f797d 100644 --- a/pkg/attestation/quoteprovider/embed.go +++ b/pkg/attestation/quoteprovider/embed.go @@ -8,10 +8,15 @@ package quoteprovider import ( "github.com/google/go-sev-guest/client" + "github.com/google/go-sev-guest/proto/check" pb "github.com/google/go-sev-guest/proto/sevsnp" cocosai "github.com/ultravioletrs/cocos" ) +var ( + AttConfigurationSEVSNP = check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}} +) + var _ client.QuoteProvider = (*embeddedQuoteProvider)(nil) type embeddedQuoteProvider struct { @@ -36,3 +41,11 @@ func (e *embeddedQuoteProvider) IsSupported() bool { func (e *embeddedQuoteProvider) Product() *pb.SevProduct { panic("unimplemented") } + +func FetchAttestation(reportDataSlice []byte) ([]byte, error) { + return cocosai.EmbeddedAttestation, nil +} + +func VerifyAttestationReportTLS(attestationBytes []byte, reportData []byte) error { + return nil +} diff --git a/scripts/backend_info/backend_info.json b/scripts/backend_info/backend_info.json index e82d5fa9..fc7159bf 100644 --- a/scripts/backend_info/backend_info.json +++ b/scripts/backend_info/backend_info.json @@ -1,28 +1,25 @@ { - "policy": { - "policy": 196608, - "family_id": "AAAAAAAAAAAAAAAAAAAAAA==", - "image_id": "AAAAAAAAAAAAAAAAAAAAAA==", - "vmpl": 0, - "minimum_tcb": 15066229603414573059, - "minimum_launch_tcb": 15066229603414573059, - "require_author_key": false, - "measurement": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", - "host_data": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "report_id_ma": "//////////////////////////////////////////8=", - "chip_id": "GrFqtQ+lrkLsjBslu9pcC6XqkrtFWY1ArIQ+I4gugQIsvCG0qekSvEtE4P/SLSJ6mHNpOkY0MHnGpvz1OkV+kw==", - "minimum_build": 21, - "minimum_version": "1.55", - "permit_provisional_firmware": false, - "require_id_block": false, - "product": { - "name": 1 + "rootOfTrust":{ + "product":"Milan", + "checkCrl":true, + "productLine":"Milan" + }, + "policy":{ + "policy":"196608", + "permit_provisional_firmware":true, + "familyId":"AAAAAAAAAAAAAAAAAAAAAA==", + "imageId":"AAAAAAAAAAAAAAAAAAAAAA==", + "vmpl":0, + "minimumTcb":"15352208179752599555", + "minimumLaunchTcb":"15352208179752599555", + "measurement":"TsWRmg8efWUW9XHZIomxBKrv4iCYeMO3ZlUPr+OhU5/QAPjCr96w0Dq9gJ7EaaP/", + "hostData":"HE5X+yGlBfpKlg4z9TTdV6ATs7MUr4Y+EhN+reuG+zY=", + "reportIdMa":"//////////////////////////////////////////8=", + "chipId":"GrFqtQ+lrkLsjBslu9pcC6XqkrtFWY1ArIQ+I4gugQIsvCG0qekSvEtE4P/SLSJ6mHNpOkY0MHnGpvz1OkV+kw==", + "minimumBuild":7, + "minimumVersion":"1.55", + "product":{ + "name":"SEV_PRODUCT_MILAN" + } } - }, - "root_of_trust": { - "product": "Milan", - "check_crl": true, - "disallow_network": false, - "product_line": "Milan" - } } \ No newline at end of file