diff --git a/cli/cmd/comid.go b/cli/cmd/comid.go index 5573fcfa..0b6c2429 100644 --- a/cli/cmd/comid.go +++ b/cli/cmd/comid.go @@ -11,14 +11,7 @@ import ( var comidCmd = &cobra.Command{ Use: "comid", - Short: "CoMID specific manipulation", - Long: `CoMID specific manipulation - - Create CoMID from template t1.json and save the result in the current working directory. - - cli comid create --file=t1.json - - `, + Short: "CoMID manipulation", Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { diff --git a/cli/cmd/comidCreate.go b/cli/cmd/comidCreate.go index be6f96ef..c1a5a9d2 100644 --- a/cli/cmd/comidCreate.go +++ b/cli/cmd/comidCreate.go @@ -27,14 +27,16 @@ func NewComidCreateCmd() *cobra.Command { Short: "create one or more CBOR-encoded CoMID(s) from the supplied JSON template(s)", Long: `create one or more CBOR-encoded CoMID(s) from the supplied JSON template(s) - Create CoMIDs from templates t1.json and t2.json, plus any template found in the - templates/ directory. Save them to the current working directory. - - cli comid create --tmpl-file=t1.json --tmpl-file=t2.json --tmpl-dir=templates - + Create CoMIDs from templates t1.json and t2.json, plus any template found in + the templates/ directory. Save them to the current working directory. + + cli comid create --tmpl-file=t1.json \ + --tmpl-file=t2.json \ + --tmpl-dir=templates + Create one CoMID from template t3.json and save it to the comids/ directory. Note that the output directory must exist. - + cli comid create --tmpl-file=t3.json --output-dir=comids `, @@ -110,7 +112,7 @@ func templateToCBOR(tmplFile, outputDir string) (string, error) { // we can use tag-id as the basename, since it is supposedly unique cborFile = filepath.Join(outputDir, c.TagIdentity.TagID.String()+".cbor") - err = afero.WriteFile(fs, cborFile, cborData, 0400) + err = afero.WriteFile(fs, cborFile, cborData, 0644) if err != nil { return "", fmt.Errorf("error saving CBOR file %s: %w", cborFile, err) } diff --git a/cli/cmd/comidCreate_test.go b/cli/cmd/comidCreate_test.go index 438dedc8..9f01d8cb 100644 --- a/cli/cmd/comidCreate_test.go +++ b/cli/cmd/comidCreate_test.go @@ -50,7 +50,7 @@ func Test_ComidCreateCmd_template_with_invalid_json(t *testing.T) { cmd := NewComidCreateCmd() fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "invalid.json", []byte("..."), 0400) + err = afero.WriteFile(fs, "invalid.json", []byte("..."), 0644) require.NoError(t, err) args := []string{ @@ -68,7 +68,7 @@ func Test_ComidCreateCmd_template_with_invalid_comid(t *testing.T) { cmd := NewComidCreateCmd() fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "bad-comid.json", []byte("{}"), 0400) + err = afero.WriteFile(fs, "bad-comid.json", []byte("{}"), 0644) require.NoError(t, err) args := []string{ @@ -86,7 +86,7 @@ func Test_ComidCreateCmd_template_from_file_to_default_dir(t *testing.T) { cmd := NewComidCreateCmd() fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "ok.json", []byte(comid.PSARefValJSONTemplate), 0400) + err = afero.WriteFile(fs, "ok.json", []byte(comid.PSARefValJSONTemplate), 0644) require.NoError(t, err) args := []string{ @@ -110,7 +110,7 @@ func Test_ComidCreateCmd_template_from_dir_to_custom_dir(t *testing.T) { cmd := NewComidCreateCmd() fs = afero.NewMemMapFs() - err = afero.WriteFile(fs, "testdir/ok.json", []byte(comid.PSARefValJSONTemplate), 0400) + err = afero.WriteFile(fs, "testdir/ok.json", []byte(comid.PSARefValJSONTemplate), 0644) require.NoError(t, err) args := []string{ diff --git a/cli/cmd/comidDisplay.go b/cli/cmd/comidDisplay.go index a7d04c5c..7fd9c2a3 100644 --- a/cli/cmd/comidDisplay.go +++ b/cli/cmd/comidDisplay.go @@ -31,7 +31,7 @@ func NewComidDisplayCmd() *cobra.Command { Display CoMIDs in files c1.cbor, c2.cbor and any cbor file in the comids/ directory. - + cli comid display --file=c1.cbor --file=c2.cbor --dir=comids `, diff --git a/cli/cmd/comidValidate.go b/cli/cmd/comidValidate.go index 64a8058b..936c3bf7 100644 --- a/cli/cmd/comidValidate.go +++ b/cli/cmd/comidValidate.go @@ -31,7 +31,7 @@ func NewComidValidateCmd() *cobra.Command { Validate CoMIDs in files c1.cbor, c2.cbor and any cbor file in the comids/ directory. - + cli comid validate --file=c1.cbor --file=c2.cbor --dir=comids `, diff --git a/cli/cmd/corim.go b/cli/cmd/corim.go new file mode 100644 index 00000000..2615064f --- /dev/null +++ b/cli/cmd/corim.go @@ -0,0 +1,26 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +var corimCmd = &cobra.Command{ + Use: "corim", + Short: "CoRIM manipulation", + + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.Help() // nolint: errcheck + os.Exit(0) + } + }, +} + +func init() { + rootCmd.AddCommand(corimCmd) +} diff --git a/cli/cmd/corimCreate.go b/cli/cmd/corimCreate.go new file mode 100644 index 00000000..79e55940 --- /dev/null +++ b/cli/cmd/corimCreate.go @@ -0,0 +1,202 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "errors" + "fmt" + + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" + "github.com/veraison/swid" +) + +var ( + corimCreateCorimFile *string + corimCreateCoswidFiles []string + corimCreateCoswidDirs []string + corimCreateComidFiles []string + corimCreateComidDirs []string + corimCreateOutputFile *string +) + +var corimCreateCmd = NewCorimCreateCmd() + +func NewCorimCreateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + Short: "create a CBOR-encoded CoRIM from the supplied JSON template, CoMID(s) and/or CoSWID(s)", + Long: `create a CBOR-encoded CoRIM from the supplied JSON template, CoMID(s) and/or CoSWID(s)", + + Create a CoRIM from template t1.json, adding CoMIDs found in the comid/ + directory and CoSWIDs found in the coswid/ directory. Since no explicit + output file is set, the (unsigned) CoRIM is saved to the current directory + with tag-id as basename and a .cbor extension. + + cli corim create --tmpl-file=t1.json --comid-dir=comid --coswid-dir=coswid + + Create a CoRIM from template corim-template.json, adding CoMID stored in + comid1.cbor and the two CoSWIDs stored in coswid1.cbor and dir/coswid2.cbor. + The (unsigned) CoRIM is saved to corim.cbor. + + cli corim create --tmpl-file=corim-template.json \ + --comid-file=comid1.cbor \ + --coswid-file=coswid1.cbor \ + --coswid-file=dir/coswid2.cbor \ + --ouptut-file=corim.cbor + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkCorimCreateArgs(); err != nil { + return err + } + + comidFilesList := filesList(corimCreateComidFiles, corimCreateComidDirs, ".cbor") + coswidFilesList := filesList(corimCreateCoswidFiles, corimCreateCoswidDirs, ".cbor") + + if len(comidFilesList)+len(coswidFilesList) == 0 { + return errors.New("no CoMID or CoSWID files found") + } + + // checkCorimCreateArgs makes sure corimCreateCorimFile is not nil + cborFile, err := corimTemplateToCBOR(*corimCreateCorimFile, + comidFilesList, coswidFilesList, corimCreateOutputFile) + if err != nil { + return err + } + fmt.Printf("created %q from %q\n", cborFile, *corimCreateCorimFile) + + return nil + }, + } + + corimCreateCorimFile = cmd.Flags().StringP("tmpl-file", "f", "", "a CoMID template file (in JSON format)") + + cmd.Flags().StringArrayVarP( + &corimCreateComidDirs, "comid-dir", "M", []string{}, "a directory containing CBOR-encoded CoMID files", + ) + + cmd.Flags().StringArrayVarP( + &corimCreateComidFiles, "comid-file", "m", []string{}, "a CBOR-encoded CoMID file", + ) + + cmd.Flags().StringArrayVarP( + &corimCreateCoswidDirs, "coswid-dir", "S", []string{}, "a directory containing CBOR-encoded CoSWID files", + ) + + cmd.Flags().StringArrayVarP( + &corimCreateCoswidFiles, "coswid-file", "s", []string{}, "a CBOR-encoded CoSWID file", + ) + + corimCreateOutputFile = cmd.Flags().StringP("output-file", "o", "", "name of the generated (unsigned) CoRIM file") + + return cmd +} + +func checkCorimCreateArgs() error { + if corimCreateCorimFile == nil || *corimCreateCorimFile == "" { + return errors.New("no CoRIM template supplied") + } + + if len(corimCreateComidDirs)+len(corimCreateComidFiles)+ + len(corimCreateCoswidDirs)+len(corimCreateCoswidFiles) == 0 { + return errors.New("no CoMID or CoSWID files or folders supplied") + } + + return nil +} + +func corimTemplateToCBOR(tmplFile string, comidFiles, coswidFiles []string, outputFile *string) (string, error) { + var ( + tmplData, corimCBOR []byte + c corim.UnsignedCorim + corimFile string + err error + ) + + if tmplData, err = afero.ReadFile(fs, tmplFile); err != nil { + return "", fmt.Errorf("error loading template from %s: %w", tmplFile, err) + } + + if err = c.FromJSON(tmplData); err != nil { + return "", fmt.Errorf("error decoding template from %s: %w", tmplFile, err) + } + + // append CoMID(s) + for _, comidFile := range comidFiles { + var ( + comidCBOR []byte + m comid.Comid + ) + + comidCBOR, err = afero.ReadFile(fs, comidFile) + if err != nil { + return "", fmt.Errorf("error loading CoMID from %s: %w", comidFile, err) + } + + err = m.FromCBOR(comidCBOR) + if err != nil { + return "", fmt.Errorf("error loading CoMID from %s: %w", comidFile, err) + } + + if c.AddComid(m) == nil { + return "", fmt.Errorf( + "error adding CoMID from %s (check its validity using the %q sub-command)", + comidFile, "comid validate", + ) + } + } + + // append CoSWID(s) + for _, coswidFile := range coswidFiles { + var ( + coswidCBOR []byte + s swid.SoftwareIdentity + ) + + coswidCBOR, err = afero.ReadFile(fs, coswidFile) + if err != nil { + return "", fmt.Errorf("error loading CoSWID from %s: %w", coswidFile, err) + } + + err = s.FromCBOR(coswidCBOR) + if err != nil { + return "", fmt.Errorf("error loading CoSWID from %s: %w", coswidFile, err) + } + + if c.AddCoswid(s) == nil { + return "", fmt.Errorf("error adding CoSWID from %s", coswidFile) + } + } + + // check the result + if err = c.Valid(); err != nil { + return "", fmt.Errorf("error validating CoRIM: %w", err) + } + + corimCBOR, err = c.ToCBOR() + if err != nil { + return "", fmt.Errorf("error encoding CoRIM to CBOR: %w", err) + } + + if outputFile == nil || *outputFile == "" { + // we can use tag-id as the basename, since it is supposedly unique + corimFile = c.ID.String() + ".cbor" + } else { + corimFile = *outputFile + } + + err = afero.WriteFile(fs, corimFile, corimCBOR, 0644) + if err != nil { + return "", fmt.Errorf("error saving CoRIM to file %s: %w", corimFile, err) + } + + return corimFile, nil +} + +func init() { + corimCmd.AddCommand(corimCreateCmd) +} diff --git a/cli/cmd/corimCreate_test.go b/cli/cmd/corimCreate_test.go new file mode 100644 index 00000000..5a157f2b --- /dev/null +++ b/cli/cmd/corimCreate_test.go @@ -0,0 +1,228 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" +) + +var ( + minimalCorimTemplate = []byte(`{ "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc" }`) + BadCBOR = comid.MustHexDecode(nil, "ffff") + // a "tag-id only" CoMID {1: {0: h'366D0A0A598845ED84882F2A544F6242'}} + InvalidComid = comid.MustHexDecode(nil, "a101a10050366d0a0a598845ed84882f2a544f6242") + testComid = comid.MustHexDecode(nil, "a40065656e2d474201a1005043bbe37f2e614b33aed353cff1428b160281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028300010204a1008182a100a300d90227582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016441434d45026a526f616452756e6e657283a200d90258a30162424c0465322e312e30055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a102818201582087428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7a200d90258a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820158200263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813fa200d90258a3016441526f540465302e312e34055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a1028182015820a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478") + testCoswid = comid.MustHexDecode(nil, "a8007820636f6d2e61636d652e727264323031332d63652d7370312d76342d312d352d300c0001783041434d4520526f616472756e6e6572204465746563746f72203230313320436f796f74652045646974696f6e205350310d65342e312e3505a5182b65747269616c182d6432303133182f66636f796f7465183473526f616472756e6e6572204465746563746f721836637370310282a3181f745468652041434d4520436f72706f726174696f6e18206861636d652e636f6d1821820102a3181f75436f796f74652053657276696365732c20496e632e18206c6d79636f796f74652e636f6d18210404a21826781c7777772e676e752e6f72672f6c6963656e7365732f67706c2e7478741828676c6963656e736506a110a318186a72726465746563746f7218196d2570726f6772616d6461746125181aa111a318186e72726465746563746f722e657865141a000820e80782015820a314fc2dc663ae7a6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a") +) + +func Test_CorimCreateCmd_unknown_argument(t *testing.T) { + cmd := NewCorimCreateCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_CorimCreateCmd_no_templates(t *testing.T) { + cmd := NewCorimCreateCmd() + + // no args + + err := cmd.Execute() + assert.EqualError(t, err, "no CoRIM template supplied") +} + +func Test_CorimCreateCmd_no_files_found(t *testing.T) { + cmd := NewCorimCreateCmd() + + args := []string{ + "--tmpl-file=unknown.json", + "--comid-file=unsure.cbor", + "--comid-dir=somedir", + "--coswid-file=what.cbor", + "--coswid-dir=someotherdir", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoMID or CoSWID files found") +} + +func Test_CorimCreateCmd_no_tag_files(t *testing.T) { + cmd := NewCorimCreateCmd() + + args := []string{ + "--tmpl-file=unknown.json", + // no --co{m,sw}id-{dir,file} + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoMID or CoSWID files or folders supplied") +} + +func Test_CorimCreateCmd_template_not_found(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "ignored-comid.cbor", []byte{}, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=nonexistent.json", + "--comid-file=ignored-comid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "error loading template from nonexistent.json: open nonexistent.json: file does not exist") +} + +func Test_CorimCreateCmd_template_with_invalid_json(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "invalid.json", []byte("..."), 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ignored-comid.cbor", []byte{}, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=invalid.json", + "--comid-file=ignored-comid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding template from invalid.json: invalid character '.' looking for beginning of value") +} + +func Test_CorimCreateCmd_with_a_bad_comid(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "bad-comid.cbor", BadCBOR, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=min-tmpl.json", + "--comid-file=bad-comid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, `error loading CoMID from bad-comid.cbor: cbor: unexpected "break" code`) +} + +func Test_CorimCreateCmd_with_an_invalid_comid(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "invalid-comid.cbor", InvalidComid, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=min-tmpl.json", + "--comid-file=invalid-comid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, `error adding CoMID from invalid-comid.cbor (check its validity using the "comid validate" sub-command)`) +} + +func Test_CorimCreateCmd_with_a_bad_coswid(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "bad-coswid.cbor", BadCBOR, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=min-tmpl.json", + "--coswid-file=bad-coswid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, `error loading CoSWID from bad-coswid.cbor: cbor: unexpected "break" code`) +} + +func Test_CorimCreateCmd_successful_comid_and_coswid_from_file(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "coswid.cbor", testCoswid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "comid.cbor", testComid, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=min-tmpl.json", + "--coswid-file=coswid.cbor", + "--comid-file=comid.cbor", + "--output-file=corim.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("corim.cbor") + assert.NoError(t, err) +} + +func Test_CorimCreateCmd_successful_comid_and_coswid_from_dir(t *testing.T) { + var err error + + cmd := NewCorimCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "min-tmpl.json", minimalCorimTemplate, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "coswid/1.cbor", testCoswid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "comid/1.cbor", testComid, 0644) + require.NoError(t, err) + + args := []string{ + "--tmpl-file=min-tmpl.json", + "--coswid-dir=coswid", + "--comid-dir=comid", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("5c57e8f4-46cd-421b-91c9-08cf93e13cfc.cbor") + assert.NoError(t, err) +} diff --git a/cli/data/comid/1.cbor b/cli/data/comid/1.cbor new file mode 100644 index 00000000..35e0cb60 Binary files /dev/null and b/cli/data/comid/1.cbor differ diff --git a/cli/data/comid/2.cbor b/cli/data/comid/2.cbor new file mode 100644 index 00000000..b35b2add Binary files /dev/null and b/cli/data/comid/2.cbor differ diff --git a/cli/data/coswid/1.cbor b/cli/data/coswid/1.cbor new file mode 100644 index 00000000..bc78ffcc Binary files /dev/null and b/cli/data/coswid/1.cbor differ diff --git a/cli/data/psa-iakpub-template.json b/cli/data/templates/comid-psa-iakpub.json similarity index 100% rename from cli/data/psa-iakpub-template.json rename to cli/data/templates/comid-psa-iakpub.json diff --git a/cli/data/psa-refval-template.json b/cli/data/templates/comid-psa-refval.json similarity index 100% rename from cli/data/psa-refval-template.json rename to cli/data/templates/comid-psa-refval.json diff --git a/cli/data/templates/corim-1.json b/cli/data/templates/corim-1.json new file mode 100644 index 00000000..bc7e70ba --- /dev/null +++ b/cli/data/templates/corim-1.json @@ -0,0 +1,3 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc" +}