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

Add CoSWID command and display functionality with CBOR support #31

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
19 changes: 19 additions & 0 deletions cmd/coswid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cmd

import (
"github.com/spf13/cobra"
"fmt"
)

var coswidCmd = &cobra.Command{
Use: "coswid",
Short: "A brief description of your command",
RunE: func(cmd *cobra.Command, args []string) error {
fmt.Println("coswid command executed")
return nil
},
}

func init() {
rootCmd.AddCommand(coswidCmd)
}
146 changes: 146 additions & 0 deletions cmd/coswidCreate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package cmd

import (
"encoding/json"
"fmt"
"path/filepath"

"github.com/xeipuuv/gojsonschema"
"github.com/fxamacker/cbor/v2"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/veraison/swid"
)

type CustomSoftwareIdentity struct {
swid.SoftwareIdentity
Evidence struct {
Type string `json:"type"`
Value string `json:"value"`
} `json:"evidence"`
}

var (
coswidCreateTemplate string
coswidCreateOutputDir string
)

var coswidCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a CBOR-encoded CoSWID from the supplied JSON template",
Long: `Create a CBOR-encoded CoSWID from the supplied JSON template.

Create a CoSWID from template t1.json and save it to the current directory.

cocli coswid create --template=t1.json

Create a CoSWID from template t1.json and save it to the specified directory.
`,
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkCoswidCreateArgs(); err != nil {
return err
}

// Validate JSON against schema before processing
schemaPath := "D:/opensource/cocli/data/coswid/templates/coswid-schema.json"
err := validateJSON(coswidCreateTemplate, schemaPath)
if err != nil {
return fmt.Errorf("JSON validation failed: %v", err)
}

cborFile, err := coswidTemplateToCBOR(coswidCreateTemplate, coswidCreateOutputDir)
if err != nil {
return fmt.Errorf("error creating CBOR: %v", err)
}
fmt.Printf(">> created %q from %q\n", cborFile, coswidCreateTemplate)

return nil
},
}

func checkCoswidCreateArgs() error {
if coswidCreateTemplate == "" {
return fmt.Errorf("template file is required")
}
return nil
}

func coswidTemplateToCBOR(tmplFile, outputDir string) (string, error) {
var (
tmplData []byte
coswidCBOR []byte
s CustomSoftwareIdentity
coswidFile string
err error
)

// Read the template file
tmplData, err = afero.ReadFile(afero.NewOsFs(), tmplFile)
if err != nil {
return "", fmt.Errorf("unable to read template file: %v", err)
}

// Parse the JSON into the custom struct
err = json.Unmarshal(tmplData, &s)
if err != nil {
return "", fmt.Errorf("error decoding template from %s: %v", tmplFile, err)
}

// Debugging: Print the parsed CustomSoftwareIdentity object
fmt.Println("Decoded CustomSoftwareIdentity object:")
fmt.Printf("%+v\n", s)

// Encode the struct to CBOR using fxamacker/cbor
coswidCBOR, err = cbor.Marshal(s)
if err != nil {
return "", fmt.Errorf("error encoding to CBOR: %v", err)
}

// Generate the output file name
coswidFile = makeFileName(outputDir, tmplFile, ".cbor")

// Write the CBOR data to the output file
err = afero.WriteFile(afero.NewOsFs(), coswidFile, coswidCBOR, 0644)
if err != nil {
return "", fmt.Errorf("error writing CBOR file: %v", err)
}

return coswidFile, nil
}


// validateJSON validates the JSON template against the provided schema
func validateJSON(tmplFile, schemaFile string) error {
schemaLoader := gojsonschema.NewReferenceLoader("file://" + filepath.ToSlash(schemaFile))
documentLoader := gojsonschema.NewReferenceLoader("file://" + filepath.ToSlash(tmplFile))

result, err := gojsonschema.Validate(schemaLoader, documentLoader)
if err != nil {
return fmt.Errorf("error during JSON validation: %v", err)
}

if !result.Valid() {
for _, desc := range result.Errors() {
fmt.Printf("- %s\n", desc)
}
return fmt.Errorf("schema validation failed: JSON does not conform to schema")
}

fmt.Println("JSON validation successful.")
return nil
}

func init() {
coswidCmd.AddCommand(coswidCreateCmd)
coswidCreateCmd.Flags().StringVarP(&coswidCreateTemplate, "template", "t", "", "a CoSWID template file (in JSON format)")
coswidCreateCmd.Flags().StringVarP(&coswidCreateOutputDir, "output-dir", "o", ".", "output directory for CBOR file")

// Handle required flag errors
if err := coswidCreateCmd.MarkFlagRequired("template"); err != nil {
// Since we're in init(), we can only panic on critical errors
panic(fmt.Sprintf("Failed to mark 'template' flag as required: %v", err))
}
if err := coswidCreateCmd.MarkFlagRequired("output-dir"); err != nil {
panic(fmt.Sprintf("Failed to mark 'output-dir' flag as required: %v", err))
}
}
134 changes: 134 additions & 0 deletions cmd/coswidDisplay.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package cmd

import (
"fmt"
"os"
"path/filepath"
"encoding/json"

"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/veraison/swid"
)

var (
coswidDisplayFile string
coswidDisplayDir string
)

var coswidDisplayCmd = &cobra.Command{
Use: "display",
Short: "Display one or more CBOR-encoded CoSWID(s) in human-readable (JSON) format",
Long: `Display one or more CBOR-encoded CoSWID(s) in human-readable (JSON) format.
You can supply individual CoSWID files or directories containing CoSWID files.

Display CoSWID in file s.cbor.

cocli coswid display --file=s.cbor

Display CoSWIDs in files s1.cbor, s2.cbor and any cbor file in the coswids/ directory.

cocli coswid display --file=s1.cbor --file=s2.cbor --dir=coswids
`,
RunE: func(cmd *cobra.Command, args []string) error {
// Validate input arguments
if err := checkCoswidDisplayArgs(); err != nil {
return err
}

filesList := gatherFiles([]string{coswidDisplayFile}, []string{coswidDisplayDir}, ".cbor")
if len(filesList) == 0 {
return fmt.Errorf("no CoSWID files found")
}

for _, file := range filesList {
if err := displayCoswid(file); err != nil {
fmt.Printf("Error displaying %s: %v\n", file, err)
}
}

return nil
},
}

func checkCoswidDisplayArgs() error {
if coswidDisplayFile == "" && coswidDisplayDir == "" {
return fmt.Errorf("no CoSWID file or directory supplied")
}
return nil
}

func gatherFiles(files []string, dirs []string, ext string) []string {
collectedMap := make(map[string]struct{})
var collected []string

Check failure on line 63 in cmd/coswidDisplay.go

View workflow job for this annotation

GitHub Actions / Lint

Consider pre-allocating `collected` (prealloc)
var walkErr error

// Collect files from specified file paths
for _, file := range files {
if filepath.Ext(file) == ext {
collectedMap[file] = struct{}{}
}
}

// Collect files from specified directories
for _, dir := range dirs {
if dir != "" {
exists, err := afero.Exists(fs, dir)
if err == nil && exists {
walkErr = afero.Walk(fs, dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("error accessing path %s: %v", path, err)
}
if !info.IsDir() && filepath.Ext(path) == ext {
collectedMap[path] = struct{}{}
}
return nil
})
if walkErr != nil {
fmt.Printf("Warning: error walking directory %s: %v\n", dir, walkErr)
}
}
}
}

// Convert map keys to slice
for file := range collectedMap {
collected = append(collected, file)
}

return collected
}

func displayCoswid(file string) error {
fmt.Printf("Processing file: %s\n", file)
var (
coswidCBOR []byte
s swid.SoftwareIdentity
err error
)

// Read the CBOR file
if coswidCBOR, err = afero.ReadFile(fs, file); err != nil {
return fmt.Errorf("error reading file %s: %w", file, err)
}

// Decode CBOR to SoftwareIdentity
if err = s.FromCBOR(coswidCBOR); err != nil {
return fmt.Errorf("error decoding CoSWID from %s: %w", file, err)
}

// Convert to JSON
coswidJSON, err := json.MarshalIndent(&s, "", " ")
if err != nil {
return fmt.Errorf("error marshaling CoSWID to JSON: %w", err)
}

fmt.Printf(">> [%s]\n%s\n", file, string(coswidJSON))
return nil
}

func init() {
coswidCmd.AddCommand(coswidDisplayCmd)
coswidDisplayCmd.Flags().StringVarP(&coswidDisplayFile, "file", "f", "", "a CoSWID file (in CBOR format)")
coswidDisplayCmd.Flags().StringVarP(&coswidDisplayDir, "dir", "d", "", "a directory containing CoSWID files")
}
Loading
Loading