diff --git a/.github/workflows/ci-go-cover.yml b/.github/workflows/ci-go-cover.yml index 88390865..983b13be 100644 --- a/.github/workflows/ci-go-cover.yml +++ b/.github/workflows/ci-go-cover.yml @@ -14,7 +14,7 @@ # 1. Change workflow name from "cover 100%" to "cover ≥92.5%". Script will automatically use 92.5%. # 2. Update README.md to use the new path to badge.svg because the path includes the workflow name. -name: cover ≥83% +name: cover ≥82% on: [push, pull_request] jobs: diff --git a/Makefile b/Makefile index 9932258d..c429376b 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,7 @@ GO111MODULE := on GOPKG := github.com/veraison/corim/corim GOPKG += github.com/veraison/corim/comid +GOPKG += github.com/veraison/corim/cocli/cmd GOLINT ?= golangci-lint diff --git a/cocli/README.md b/cocli/README.md new file mode 100644 index 00000000..a1c2fe0e --- /dev/null +++ b/cocli/README.md @@ -0,0 +1,340 @@ +# Corim Command Line Interface + +## Installing and configuring + +To install the `cocli` command, do: +``` +$ go install github.com/veraison/corim/cocli +``` + +To configure auto-completion, use the `completion` subcommand. For example, if +`bash` is your shell, you would do something like: +``` +$ cocli completion bash > ~/.bash_completion.d/cocli +$ . .bash_completion +``` +to get automatic command completion and suggestions using the TAB key. + +To get a list of the supported shells, do: +``` +$ cocli completion --help +``` + +## CoMIDs manipulation + +The `comid` subcommand allows you to create, display and validate CoMIDs. + +### Create + +Use the `comid create` subcommand to create a CBOR-encoded CoMID, passing its +JSON representation<sup>[1](#templates-ex)</sup> via the `--template` switch (or +equivalently its `-t` shorthand): +``` +$ cocli comid create --template t1.json +``` +On success, you should see something like the following printed to stdout: +``` +>> created "t1.cbor" from "t1.json" +``` + +The CBOR-encoded CoMID file is stored in the current working directory with a +name derived from its template. If you want, you can specify a different +target directory using the `--output-dir` command line switch (abbrev. `-o`) +``` +$ cocli comid create --template t1.json --output-dir /tmp +>> created "/tmp/t1.cbor" from "t1.json" +``` +Note that the output directory, as well as all its parent directories, MUST +pre-exist. + +You can also create multiple CoMIDs in one go. Suppose all your templates are +stored in the `templates/` folder: +``` +$ tree templates/ +templates/ +├── t1.json +├── t2.json +... +└── tn.json +``` +Then, you can use the `--template-dir` (abbrev. `-T`), and let the tool load, +validate, and CBOR-encode the templates one by one: +``` +$ cocli comid create --template-dir templates +>> created "t1.cbor" from "templates/t1.json" +>> created "t2.cbor" from "templates/t2.json" +... +>> created "tn.cbor" from "templates/tn.json" +``` + +You can specify both the `-T` and `-t` switches as many times as needed, and +even combine them in one invocation: +``` +$ cocli comid create -T comid-templates/ \ + -T comid-templates-aux/ \ + -t extra-comid.json \ + -t yet-another-comid.json \ + -o /var/spool/comid +``` + +**NOTE** that since the output file name is deterministically generated from the +template file name, all the template files (when from different directories) +MUST have different base names. + + +### Display + +Use the `comid display` subcommand to print to stdout one or more CBOR-encoded +CoMIDs in human readable (JSON) format. + +You can supply individual files using the `--file` switch (abbrev. `-f`), or +directories that may (or may not) contain CoMID files using the `--dir` switch +(abbrev. `-d`). Only valid CoMIDs will be displayed, and any decoding or +validation error will be printed alongside the corresponding file name. + +For example: +``` +$ cocli comid display --file m1.cbor +``` +provided the `m1.cbor` file contains valid CoMID, would print something like: +``` +>> [m1.cbor] +{ + "lang": "en-GB", + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } +[...] +``` +While a `comids.d` folder with the following contents: +``` +$ tree comids.d/ +comids.d/ +├── rubbish.cbor +├── valid-comid-1.cbor +└── valid-comid-2.cbor +``` +could be inspected in one go using: +``` +$ cocli comid display --dir comids.d/ +``` +which would output something like: +``` +>> failed displaying "comids.d/rubbish.cbor": CBOR decoding failed: EOF +>> [comids.d/valid-comid-1.cbor] +{ + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, +[...] +} +>> [comids.d/valid-comid-2.cbor] +{ + "tag-identity": { + "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" + }, +[...] +} +Error: 1/3 display(s) failed +``` + +One of more files and directories can be supplied in the same invocation, e.g.: +``` +$ cocli comid display -f m1.cbor \ + -f comids.d/m2.cbor \ + -d /var/spool/comids \ + -d yet-another-comid-folder/ +``` + +## CoRIMs manipulation + +The `corim` subcommand allows you to create, display, sign and verify CoRIMs. +It also provides a means to extract as-is the embedded CoSWIDs and CoMIDs and save +them as separate files. + +### Create + +Use the `corim create` subcommand to create a CBOR-encoded, unsigned CoRIM, by +passing its JSON representation<sup>[1](#templates-ex)</sup> via the +`--template` switch (or equivalently its `-t` shorthand) together with the +CBOR-encoded CoMIDs and/or CoSWIDs to be embedded. For example: +``` +$ cocli corim create --template c1.json --comid m1.cbor --coswid s1.cbor +``` +On success, you should see something like the following printed to stdout: +``` +>> created "c1.cbor" from "c1.json" +``` + +The CBOR-encoded CoRIM file is stored in the current working directory with a +name derived from its template. If you want, you can specify a different +file name using the `--output` command line switch (abbrev. `-o`): +``` +$ cocli corim create -t c1.json -m m1.cbor -s s1.cbor -o my.cbor +>> created "my.cbor" from "c1.json" +``` + +CoMIDs and CoSWIDs can be either supplied as individual files, using the +`--comid` (abbrev. `-m`) and `--coswid` (abbrev. `-s`) switches respectively, or +as "per-folder" blocks using the `--comid-dir` (abbrev. `-M`) and `--coswid-dir` +(abbrev. `-S`) switch. For example: +``` +$ cocli corim create --template c1.json --comid-dir comids.d/ +``` + +Creation will fail if *any* of the inputs is non conformant. For example, if +`comids.d` contains an invalid CoMID file `rubbish.cbor`, an attempt to create a +CoRIM: +``` +$ cocli corim create -t c1.json -M comids.d/ +``` +will fail with: +``` +Error: error loading CoMID from comids.d/rubbish.cbor: EOF +``` + +### Sign + +Use the `corim sign` subcommand to cryptographically seal the unsigned CoRIM +supplied via the `--file` switch (abbrev. `-f`). The signature is produced +using the key supplied via the `--key` switch (abbrev. `-k`), which is expected +to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. On success, the +resulting COSE Sign1 payload is saved to file whose name can be controlled using +the `--output` switch (abbrev. `-o`). A CoRIM Meta<sup>[1](#templates-ex)</sup> +template in JSON format must also be provided using the `--meta` switch (abbrev. +`-m`). For example, with the default output file: +``` +$ cocli corim sign --file corim.cbor --key ec-p256.jwk --meta meta.json +>> "corim.cbor" signed and saved to "signed-corim.cbor" +``` +Or, the same but with a custom output file: +``` +$ cocli corim sign --file corim.cbor \ + --key ec-p256.jwk \ + --meta meta.json \ + --output /var/spool/signed-corim.cbor +>> "corim.cbor" signed and saved to "/var/spool/signed-corim.cbor" +``` + +### Verify + +Use the `corim verify` subcommand to cryptographically verify the signed CoRIM +supplied via the `--file` switch (abbrev. `-f`). The signature is checked +using the key supplied via the `--key` switch (abbrev. `-k`), which is expected +to be in [JWK](https://www.rfc-editor.org/rfc/rfc7517) format. For example: +``` +$ cocli corim verify --file signed-corim.cbor --key ec-p256.jwk +>> "corim.cbor" verified +``` + +Verification can fail either because the cryptographic processing fails or +because the signed payload or protected headers are themselves invalid. For example: +``` +$ cocli corim verify --file signed-corim-bad-signature.cbor --key ec-p256.jwk +``` +will give +``` +Error: error verifying signed-corim-bad-signature.cbor with key ec-p256.jwk: verification failed ecdsa.Verify +``` + +### Display + +Use the `corim display` subcommand to print to stdout a signed CoRIM in human +readable (JSON) format. + +You must supply the file you want to display using the `--file` switch (abbrev. +`-f`). Only a valid CoRIM will be displayed, and any occurring decoding or +validation errors will be printed instead. + +The output has two logical sections: one for Meta and one for the (unsigned) +CoRIM: +``` +$ cocli corim display --file signed-corim.cbor +Meta: +{ + "signer": { + "name": "ACME Ltd signing key", + "uri": "https://acme.example/signing-key.pub" + }, +[...] +} +Corim: +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "tags": [ + "2QH...", +[...] + ] +} +``` + +By default, the embedded CoMID and CoSWID tags are not expanded, and what you +will see is the base64 encoding of their CBOR serialisation. If you want to +peek at the tags' content, supply the `--show-tags` (abbrev. `-v`) switch, which +will add a further Tags section with one entry per each expanded tag: +``` +$ cocli corim display --file signed-corim.cbor --show-tags +Meta: +{ +[...] +} +Corim: +{ +[...] +} +Tags: +>> [ 0 ] +{ + "tag-identity": { + "id": "366d0a0a-5988-45ed-8488-2f2a544f6242" + }, +[...] +} +>> [ 1 ] +{ + "tag-identity": { + "id": "43bbe37f-2e61-4b33-aed3-53cff1428b16" + }, +[...] +} +>> [ 2 ] +{ + "tag-id": "com.acme.rrd2013-ce-sp1-v4-1-5-0", +[...] +} +``` + +### Extract CoSWIDs and CoMIDs + +Use the `corim extract` subcommand to extract the embedded CoMIDs and CoSWIDs +from a signed CoRIM. + +You must supply a signed CoRIM file using the `--file` switch (abbrev. `-f`) and +an optional output folder (default is the current working directory) using the +`--output-dir` switch (abbrev. `-o`). Make sure that the output directory as +well as any parent folder exists prior to issuing the command. + +On success, the found CoMIDs and CoSWIDs are saved in CBOR format: +``` +$ cocli corim extract --file signed-corim.cbor --output-dir output.d/ +$ tree output.d/ +output.d/ +├── 000000-comid.cbor +├── 000001-comid.cbor +└── 000002-coswid.cbor +``` + + + +<a name="templates-ex">1</a>: A few examples of CoMID, CoRIM, and Meta JSON +templates can be found in the [data/templates](data/templates) folder. \ No newline at end of file diff --git a/cocli/cmd/comid.go b/cocli/cmd/comid.go new file mode 100644 index 00000000..0b6c2429 --- /dev/null +++ b/cocli/cmd/comid.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 comidCmd = &cobra.Command{ + Use: "comid", + Short: "CoMID manipulation", + + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.Help() // nolint: errcheck + os.Exit(0) + } + }, +} + +func init() { + rootCmd.AddCommand(comidCmd) +} diff --git a/cocli/cmd/comidCreate.go b/cocli/cmd/comidCreate.go new file mode 100644 index 00000000..ca32fca2 --- /dev/null +++ b/cocli/cmd/comidCreate.go @@ -0,0 +1,132 @@ +// 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" +) + +var ( + comidCreateFiles []string + comidCreateDirs []string + comidCreateOutputDir string +) + +var comidCreateCmd = NewComidCreateCmd() + +func NewComidCreateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create", + 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 --template=t1.json \ + --template=t2.json \ + --template-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 --template=t3.json --output-dir=comids + + Note: since the output file is deterministically generated from the template + file name, all the template file names (when from different directories) + MUST be different. + `, + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkComidCreateArgs(); err != nil { + return err + } + + filesList := filesList(comidCreateFiles, comidCreateDirs, ".json") + if len(filesList) == 0 { + return errors.New("no files found") + } + + errs := 0 + for _, tmplFile := range filesList { + cborFile, err := templateToCBOR(tmplFile, comidCreateOutputDir) + if err != nil { + fmt.Printf(">> creation failed for %q: %v\n", cborFile, err) + errs++ + continue + } + fmt.Printf(">> created %q from %q\n", cborFile, tmplFile) + } + + if errs != 0 { + return fmt.Errorf("%d/%d creations(s) failed", errs, len(filesList)) + } + return nil + }, + } + + cmd.Flags().StringArrayVarP( + &comidCreateFiles, "template", "t", []string{}, "a CoMID template file (in JSON format)", + ) + + cmd.Flags().StringArrayVarP( + &comidCreateDirs, "template-dir", "T", []string{}, "a directory containing CoMID template files", + ) + + cmd.Flags().StringVarP( + &comidCreateOutputDir, "output-dir", "o", ".", "directory where the created files are stored", + ) + + return cmd +} + +func checkComidCreateArgs() error { + if len(comidCreateFiles) == 0 && len(comidCreateDirs) == 0 { + return errors.New("no templates supplied") + } + return nil +} + +func templateToCBOR(tmplFile, outputDir string) (string, error) { + var ( + tmplData, cborData []byte + cborFile string + c comid.Comid + 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) + } + + if err = c.Valid(); err != nil { + return "", fmt.Errorf("error validating template %s: %w", tmplFile, err) + } + + cborData, err = c.ToCBOR() + if err != nil { + return "", fmt.Errorf("error encoding template %s to CBOR: %w", tmplFile, err) + } + + cborFile = makeFileName(outputDir, tmplFile, ".cbor") + + err = afero.WriteFile(fs, cborFile, cborData, 0644) + if err != nil { + return "", fmt.Errorf("error saving CBOR file %s: %w", cborFile, err) + } + + return cborFile, nil +} + +func init() { + comidCmd.AddCommand(comidCreateCmd) +} diff --git a/cocli/cmd/comidCreate_test.go b/cocli/cmd/comidCreate_test.go new file mode 100644 index 00000000..b651ebd6 --- /dev/null +++ b/cocli/cmd/comidCreate_test.go @@ -0,0 +1,128 @@ +// 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" +) + +func Test_ComidCreateCmd_unknown_argument(t *testing.T) { + cmd := NewComidCreateCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_ComidCreateCmd_no_templates(t *testing.T) { + cmd := NewComidCreateCmd() + + // no args + + err := cmd.Execute() + assert.EqualError(t, err, "no templates supplied") +} + +func Test_ComidCreateCmd_no_files_found(t *testing.T) { + cmd := NewComidCreateCmd() + + args := []string{ + "--template=unknown", + "--template-dir=unsure", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no files found") +} + +func Test_ComidCreateCmd_template_with_invalid_json(t *testing.T) { + var err error + + cmd := NewComidCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "invalid.json", []byte("..."), 0644) + require.NoError(t, err) + + args := []string{ + "--template=invalid.json", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "1/1 creations(s) failed") +} + +func Test_ComidCreateCmd_template_with_invalid_comid(t *testing.T) { + var err error + + cmd := NewComidCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "bad-comid.json", []byte("{}"), 0644) + require.NoError(t, err) + + args := []string{ + "--template=bad-comid.json", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "1/1 creations(s) failed") +} + +func Test_ComidCreateCmd_template_from_file_to_default_dir(t *testing.T) { + var err error + + cmd := NewComidCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "ok.json", []byte(comid.PSARefValJSONTemplate), 0644) + require.NoError(t, err) + + args := []string{ + "--template=ok.json", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) + + expectedFileName := "ok.cbor" + + _, err = fs.Stat(expectedFileName) + assert.NoError(t, err) +} + +func Test_ComidCreateCmd_template_from_dir_to_custom_dir(t *testing.T) { + var err error + + cmd := NewComidCreateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "testdir/ok.json", []byte(comid.PSARefValJSONTemplate), 0644) + require.NoError(t, err) + + args := []string{ + "--template-dir=testdir", + "--output-dir=testdir", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) + + expectedFileName := "testdir/ok.cbor" + + _, err = fs.Stat(expectedFileName) + assert.NoError(t, err) +} diff --git a/cocli/cmd/comidDisplay.go b/cocli/cmd/comidDisplay.go new file mode 100644 index 00000000..5733442a --- /dev/null +++ b/cocli/cmd/comidDisplay.go @@ -0,0 +1,98 @@ +// 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" +) + +var ( + comidDisplayFiles []string + comidDisplayDirs []string +) + +var comidDisplayCmd = NewComidDisplayCmd() + +func NewComidDisplayCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "display", + Short: "display one or more CBOR-encoded CoMID(s) in human readable (JSON) format", + Long: `display one or more CBOR-encoded CoMID(s) in human readable (JSON) format. + You can supply individual CoMID files or directories containing CoMID files. + + Display CoMID in file c.cbor. + + cli comid display --file=c.cbor + + 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 + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkComidDisplayArgs(); err != nil { + return err + } + + filesList := filesList(comidDisplayFiles, comidDisplayDirs, ".cbor") + if len(filesList) == 0 { + return errors.New("no files found") + } + + errs := 0 + for _, file := range filesList { + if err := displayComidFile(file); err != nil { + fmt.Printf(">> failed displaying %q: %v\n", file, err) + errs++ + continue + } + } + + if errs != 0 { + return fmt.Errorf("%d/%d display(s) failed", errs, len(filesList)) + } + return nil + }, + } + + cmd.Flags().StringArrayVarP( + &comidDisplayFiles, "file", "f", []string{}, "a CoMID file (in CBOR format)", + ) + + cmd.Flags().StringArrayVarP( + &comidDisplayDirs, "dir", "d", []string{}, "a directory containing CoMID files (in CBOR format)", + ) + + return cmd +} + +func displayComidFile(file string) error { + var ( + data []byte + err error + ) + + if data, err = afero.ReadFile(fs, file); err != nil { + return fmt.Errorf("error loading CoMID from %s: %w", file, err) + } + + // use file name as heading + return printComid(data, ">> ["+file+"]") +} + +func checkComidDisplayArgs() error { + if len(comidDisplayFiles) == 0 && len(comidDisplayDirs) == 0 { + return errors.New("no files supplied") + } + return nil +} + +func init() { + comidCmd.AddCommand(comidDisplayCmd) +} diff --git a/cocli/cmd/comidDisplay_test.go b/cocli/cmd/comidDisplay_test.go new file mode 100644 index 00000000..cc43a92f --- /dev/null +++ b/cocli/cmd/comidDisplay_test.go @@ -0,0 +1,101 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "fmt" + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_ComidDisplayCmd_unknown_argument(t *testing.T) { + cmd := NewComidDisplayCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_ComidDisplayCmd_no_files(t *testing.T) { + cmd := NewComidDisplayCmd() + + // no args + + err := cmd.Execute() + assert.EqualError(t, err, "no files supplied") +} + +func Test_ComidDisplayCmd_no_files_found(t *testing.T) { + cmd := NewComidDisplayCmd() + + args := []string{ + "--file=unknown", + "--dir=unsure", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no files found") +} + +func Test_ComidDisplayCmd_file_with_invalid_cbor(t *testing.T) { + var err error + + cmd := NewComidDisplayCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "invalid.cbor", []byte{0xff, 0xff}, 0400) + require.NoError(t, err) + + args := []string{ + "--file=invalid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "1/1 display(s) failed") +} + +func Test_ComidDisplayCmd_file_with_valid_comid(t *testing.T) { + var err error + + cmd := NewComidDisplayCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "ok.cbor", PSARefValCBOR, 0400) + require.NoError(t, err) + + args := []string{ + "--file=ok.cbor", + } + cmd.SetArgs(args) + + fmt.Printf("%x\n", PSARefValCBOR) + + err = cmd.Execute() + assert.NoError(t, err) +} + +func Test_ComidDisplayCmd_file_with_valid_comid_from_dir(t *testing.T) { + var err error + + cmd := NewComidDisplayCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "testdir/ok.cbor", PSARefValCBOR, 0400) + require.NoError(t, err) + + args := []string{ + "--dir=testdir", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) +} diff --git a/cocli/cmd/comidValidate.go b/cocli/cmd/comidValidate.go new file mode 100644 index 00000000..792c7234 --- /dev/null +++ b/cocli/cmd/comidValidate.go @@ -0,0 +1,108 @@ +// 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" +) + +var ( + comidValidateFiles []string + comidValidateDirs []string +) + +var comidValidateCmd = NewComidValidateCmd() + +func NewComidValidateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "validate", + Short: "validate one or more CBOR-encoded CoMID(s)", + Long: `validate one or more CBOR-encoded CoMID(s) + + Validate CoMID in file c.cbor. + + cli comid validate --file=c.cbor + + 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 + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkComidValidateArgs(); err != nil { + return err + } + + filesList := filesList(comidValidateFiles, comidValidateDirs, ".cbor") + if len(filesList) == 0 { + return errors.New("no files found") + } + + errs := 0 + for _, file := range filesList { + err := validateComid(file) + if err != nil { + fmt.Printf("[invalid] %q: %v\n", file, err) + errs++ + continue + } + fmt.Printf("[valid] %q\n", file) + } + + if errs != 0 { + return fmt.Errorf("%d/%d validation(s) failed", errs, len(filesList)) + } + return nil + }, + } + + cmd.Flags().StringArrayVarP( + &comidValidateFiles, "file", "f", []string{}, "a CoMID file (in CBOR format)", + ) + + cmd.Flags().StringArrayVarP( + &comidValidateDirs, "dir", "d", []string{}, "a directory containing CoMID files (in CBOR format)", + ) + + return cmd +} + +func validateComid(file string) error { + var ( + data []byte + err error + c comid.Comid + ) + + if data, err = afero.ReadFile(fs, file); err != nil { + return fmt.Errorf("error loading CoMID from %s: %w", file, err) + } + + if err = c.FromCBOR(data); err != nil { + return fmt.Errorf("error decoding CoMID from %s: %w", file, err) + } + + if err = c.Valid(); err != nil { + return fmt.Errorf("error validating CoMID %s: %w", file, err) + } + + return nil +} + +func checkComidValidateArgs() error { + if len(comidValidateFiles) == 0 && len(comidValidateDirs) == 0 { + return errors.New("no files supplied") + } + return nil +} + +func init() { + comidCmd.AddCommand(comidValidateCmd) +} diff --git a/cocli/cmd/comidValidate_test.go b/cocli/cmd/comidValidate_test.go new file mode 100644 index 00000000..6c78c200 --- /dev/null +++ b/cocli/cmd/comidValidate_test.go @@ -0,0 +1,116 @@ +// 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" +) + +func Test_ComidValidateCmd_unknown_argument(t *testing.T) { + cmd := NewComidValidateCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_ComidValidateCmd_no_files(t *testing.T) { + cmd := NewComidValidateCmd() + + // no args + + err := cmd.Execute() + assert.EqualError(t, err, "no files supplied") +} + +func Test_ComidValidateCmd_no_files_found(t *testing.T) { + cmd := NewComidValidateCmd() + + args := []string{ + "--file=unknown", + "--dir=unsure", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no files found") +} + +func Test_ComidValidateCmd_file_with_invalid_cbor(t *testing.T) { + var err error + + cmd := NewComidValidateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "invalid.cbor", []byte{0xff, 0xff}, 0400) + require.NoError(t, err) + + args := []string{ + "--file=invalid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "1/1 validation(s) failed") +} + +func Test_ComidValidateCmd_file_with_invalid_comid(t *testing.T) { + var err error + + cmd := NewComidValidateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "bad-comid.cbor", []byte{0xa0}, 0400) + require.NoError(t, err) + + args := []string{ + "--file=bad-comid.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.EqualError(t, err, "1/1 validation(s) failed") +} + +func Test_ComidValidateCmd_file_with_valid_comid(t *testing.T) { + var err error + + cmd := NewComidValidateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "ok.cbor", PSARefValCBOR, 0400) + require.NoError(t, err) + + args := []string{ + "--file=ok.cbor", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) +} + +func Test_ComidValidateCmd_file_with_valid_comid_from_dir(t *testing.T) { + var err error + + cmd := NewComidValidateCmd() + + fs = afero.NewMemMapFs() + err = afero.WriteFile(fs, "testdir/ok.cbor", PSARefValCBOR, 0400) + require.NoError(t, err) + + args := []string{ + "--dir=testdir", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) +} diff --git a/cocli/cmd/common.go b/cocli/cmd/common.go new file mode 100644 index 00000000..8e303194 --- /dev/null +++ b/cocli/cmd/common.go @@ -0,0 +1,85 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "encoding/json" + "fmt" + "path/filepath" + "strings" + + "github.com/spf13/afero" + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +func filesList(files, dirs []string, ext string) []string { + var l []string + + for _, file := range files { + if _, err := fs.Stat(file); err == nil { + l = append(l, file) + } + } + + for _, dir := range dirs { + filesInfo, err := afero.ReadDir(fs, dir) + if err != nil { + continue + } + + for _, fileInfo := range filesInfo { + if !fileInfo.IsDir() && filepath.Ext(fileInfo.Name()) == ext { + l = append(l, filepath.Join(dir, fileInfo.Name())) + } + } + } + + return l +} + +type FromCBORLoader interface { + FromCBOR([]byte) error +} + +func printJSONFromCBOR(fcl FromCBORLoader, cbor []byte, heading string) error { + var ( + err error + j []byte + ) + + if err = fcl.FromCBOR(cbor); err != nil { + return fmt.Errorf("CBOR decoding failed: %w", err) + } + + indent := " " + if j, err = json.MarshalIndent(fcl, "", indent); err != nil { + return fmt.Errorf("JSON encoding failed: %w", err) + } + + fmt.Println(heading) + fmt.Println(string(j)) + + return nil +} + +func printComid(cbor []byte, heading string) error { + return printJSONFromCBOR(&comid.Comid{}, cbor, heading) +} + +func printCoswid(cbor []byte, heading string) error { + return printJSONFromCBOR(&swid.SoftwareIdentity{}, cbor, heading) +} + +func makeFileName(dirName, baseName, ext string) string { + return filepath.Join( + dirName, + filepath.Base( + strings.TrimSuffix( + baseName, + filepath.Ext(baseName), + ), + )+ext, + ) +} diff --git a/cocli/cmd/corim.go b/cocli/cmd/corim.go new file mode 100644 index 00000000..2615064f --- /dev/null +++ b/cocli/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/cocli/cmd/corimCreate.go b/cocli/cmd/corimCreate.go new file mode 100644 index 00000000..012667da --- /dev/null +++ b/cocli/cmd/corimCreate.go @@ -0,0 +1,201 @@ +// 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 --template=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 --template=corim-template.json \ + --comid=comid1.cbor \ + --coswid=coswid1.cbor \ + --coswid=dir/coswid2.cbor \ + --output=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("template", "t", "", "a CoRIM 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", "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", "s", []string{}, "a CBOR-encoded CoSWID file", + ) + + corimCreateOutputFile = cmd.Flags().StringP("output", "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 == "" { + corimFile = makeFileName("", tmplFile, ".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/cocli/cmd/corimCreate_test.go b/cocli/cmd/corimCreate_test.go new file mode 100644 index 00000000..90ca88fa --- /dev/null +++ b/cocli/cmd/corimCreate_test.go @@ -0,0 +1,218 @@ +// 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" +) + +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{ + "--template=unknown.json", + "--comid=unsure.cbor", + "--comid-dir=somedir", + "--coswid=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{ + "--template=unknown.json", + // no --co{m,sw}id-{dir,} + } + 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{ + "--template=nonexistent.json", + "--comid=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{ + "--template=invalid.json", + "--comid=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{ + "--template=min-tmpl.json", + "--comid=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{ + "--template=min-tmpl.json", + "--comid=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{ + "--template=min-tmpl.json", + "--coswid=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{ + "--template=min-tmpl.json", + "--coswid=coswid.cbor", + "--comid=comid.cbor", + "--output=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{ + "--template=min-tmpl.json", + "--coswid-dir=coswid", + "--comid-dir=comid", + } + cmd.SetArgs(args) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("min-tmpl.cbor") + assert.NoError(t, err) +} diff --git a/cocli/cmd/corimDisplay.go b/cocli/cmd/corimDisplay.go new file mode 100644 index 00000000..9caa9ae9 --- /dev/null +++ b/cocli/cmd/corimDisplay.go @@ -0,0 +1,131 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/veraison/corim/corim" +) + +var ( + corimDisplayCorimFile *string + corimDisplayShowTags *bool +) + +var corimDisplayCmd = NewCorimDisplayCmd() + +func NewCorimDisplayCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "display", + Short: "display the content of a CoRIM as JSON", + Long: `display the content of a CoRIM as JSON + + Display the contents of the signed CoRIM signed-corim.cbor + + cli corim display --file signed-corim.cbor + + Display the contents of the signed CoRIM yet-another-signed-corim.cbor and + also unpack any embedded CoMID and CoSWID + + cli corim display --file yet-another-signed-corim.cbor --show-tags + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkCorimDisplayArgs(); err != nil { + return err + } + + if err := display(*corimDisplayCorimFile, *corimDisplayShowTags); err != nil { + return err + } + + return nil + }, + } + + corimDisplayCorimFile = cmd.Flags().StringP("file", "f", "", "a signed CoRIM file (in CBOR format)") + corimDisplayShowTags = cmd.Flags().BoolP("show-tags", "v", false, "display embedded tags") + + return cmd +} + +func checkCorimDisplayArgs() error { + if corimDisplayCorimFile == nil || *corimDisplayCorimFile == "" { + return errors.New("no CoRIM supplied") + } + + return nil +} + +func display(signedCorimFile string, showTags bool) error { + var ( + signedCorimCBOR []byte + metaJSON []byte + corimJSON []byte + err error + s corim.SignedCorim + ) + + if signedCorimCBOR, err = afero.ReadFile(fs, signedCorimFile); err != nil { + return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err) + } + + if err = s.FromCOSE(signedCorimCBOR); err != nil { + return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err) + } + + if metaJSON, err = json.MarshalIndent(&s.Meta, "", " "); err != nil { + return fmt.Errorf("error decoding CoRIM Meta from %s: %w", signedCorimFile, err) + } + + fmt.Println("Meta:") + fmt.Println(string(metaJSON)) + + if corimJSON, err = json.MarshalIndent(&s.UnsignedCorim, "", " "); err != nil { + return fmt.Errorf("error decoding unsigned CoRIM from %s: %w", signedCorimFile, err) + } + + fmt.Println("Corim:") + fmt.Println(string(corimJSON)) + + if showTags { + fmt.Println("Tags:") + for i, e := range s.UnsignedCorim.Tags { + // need at least 3 bytes for the tag and 1 for the smallest bstr + if len(e) < 3+1 { + fmt.Printf(">> skipping malformed tag at index %d\n", i) + continue + } + + // split tag from data + cborTag, cborData := e[:3], e[3:] + + hdr := fmt.Sprintf(">> [ %d ]", i) + + if bytes.Equal(cborTag, corim.ComidTag) { + if err = printComid(cborData, hdr); err != nil { + fmt.Printf(">> skipping malformed CoMID tag at index %d: %v\n", i, err) + } + } else if bytes.Equal(cborTag, corim.CoswidTag) { + if err = printCoswid(cborData, hdr); err != nil { + fmt.Printf(">> skipping malformed CoSWID tag at index %d: %v\n", i, err) + } + } else { + fmt.Printf(">> unmatched CBOR tag: %x\n", cborTag) + } + } + } + + return nil +} + +func init() { + corimCmd.AddCommand(corimDisplayCmd) +} diff --git a/cocli/cmd/corimDisplay_test.go b/cocli/cmd/corimDisplay_test.go new file mode 100644 index 00000000..e7e9f20d --- /dev/null +++ b/cocli/cmd/corimDisplay_test.go @@ -0,0 +1,113 @@ +// 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" +) + +func Test_CorimDisplayCmd_unknown_argument(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_CorimDisplayCmd_mandatory_args_missing_corim_file(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{ + "--show-tags", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoRIM supplied") +} + +func Test_CorimDisplayCmd_non_existent_corim_file(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{ + "--file=nonexistent.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + + err := cmd.Execute() + assert.EqualError(t, err, "error loading signed CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") +} + +func Test_CorimDisplayCmd_bad_signed_corim(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{ + "--file=bad.txt", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding signed CoRIM from bad.txt: failed CBOR decoding for COSE-Sign1 signed CoRIM: unexpected EOF") +} + +func Test_CorimDisplayCmd_invalid_signed_corim(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{ + "--file=invalid.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "invalid.cbor", testSignedCorimInvalid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding signed CoRIM from invalid.cbor: failed validation of unsigned CoRIM: empty id") +} + +func Test_CorimDisplayCmd_ok_top_level_view(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{ + "--file=ok.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) +} + +func Test_CorimDisplayCmd_ok_nested_view(t *testing.T) { + cmd := NewCorimDisplayCmd() + + args := []string{ + "--file=ok.cbor", + "--show-tags", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) +} diff --git a/cocli/cmd/corimExtract.go b/cocli/cmd/corimExtract.go new file mode 100644 index 00000000..715acae7 --- /dev/null +++ b/cocli/cmd/corimExtract.go @@ -0,0 +1,126 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "bytes" + "errors" + "fmt" + "path/filepath" + + "github.com/spf13/afero" + "github.com/spf13/cobra" + "github.com/veraison/corim/corim" +) + +var ( + corimExtractCorimFile *string + corimExtractOutputDir *string +) + +var corimExtractCmd = NewCorimExtractCmd() + +func NewCorimExtractCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "extract", + Short: "extract, as-is, CoSWIDs and CoMIDs found in a CoRIM and save them to disk", + Long: `extract, as-is, CoSWIDs and CoMIDs found in a CoRIM and save them to disk + + Extract the contents of the signed CoRIM signed-corim.cbor to the current + directory + + cli corim extract --file=signed-corim.cbor + + Extract the contents of the signed CoRIM yet-another-signed-corim.cbor and + store them to directory my-dir. Note that my-dir must exist. + + cli corim extract --file=yet-another-signed-corim.cbor \ + --output-dir=my-dir + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkCorimExtractArgs(); err != nil { + return err + } + + if err := extract(*corimExtractCorimFile, corimExtractOutputDir); err != nil { + return err + } + + return nil + }, + } + + corimExtractCorimFile = cmd.Flags().StringP("file", "f", "", "a signed CoRIM file (in CBOR format)") + corimExtractOutputDir = cmd.Flags().StringP("output-dir", "o", ".", "folder to which CoSWIDs and CoMIDs are saved") + + return cmd +} + +func checkCorimExtractArgs() error { + if corimExtractCorimFile == nil || *corimExtractCorimFile == "" { + return errors.New("no CoRIM supplied") + } + + return nil +} + +func extract(signedCorimFile string, outputDir *string) error { + var ( + signedCorimCBOR []byte + err error + s corim.SignedCorim + baseDir string + ) + + if signedCorimCBOR, err = afero.ReadFile(fs, signedCorimFile); err != nil { + return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err) + } + + if err = s.FromCOSE(signedCorimCBOR); err != nil { + return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err) + } + + baseDir = "." + if outputDir != nil { + baseDir = *outputDir + } + + for i, e := range s.UnsignedCorim.Tags { + var ( + outputFile string + ) + + // need at least 3 bytes for the tag and 1 for the smallest bstr + if len(e) < 3+1 { + fmt.Printf(">> skipping malformed tag at index %d\n", i) + continue + } + + // split tag from data + cborTag, cborData := e[:3], e[3:] + + if bytes.Equal(cborTag, corim.ComidTag) { + outputFile = filepath.Join(baseDir, fmt.Sprintf("%06d-comid.cbor", i)) + + if err = afero.WriteFile(fs, outputFile, cborData, 0644); err != nil { + fmt.Printf(">> error saving CoMID tag at index %d: %v\n", i, err) + } + } else if bytes.Equal(cborTag, corim.CoswidTag) { + outputFile = filepath.Join(baseDir, fmt.Sprintf("%06d-coswid.cbor", i)) + + if err = afero.WriteFile(fs, outputFile, cborData, 0644); err != nil { + fmt.Printf(">> error saving CoSWID tag at index %d: %v\n", i, err) + } + } else { + fmt.Printf(">> unmatched CBOR tag: %x\n", cborTag) + } + } + + return nil +} + +func init() { + corimCmd.AddCommand(corimExtractCmd) +} diff --git a/cocli/cmd/corimExtract_test.go b/cocli/cmd/corimExtract_test.go new file mode 100644 index 00000000..c59fe327 --- /dev/null +++ b/cocli/cmd/corimExtract_test.go @@ -0,0 +1,127 @@ +// 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" +) + +func Test_CorimExtractCmd_unknown_argument(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_CorimExtractCmd_mandatory_args_missing_corim_file(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{ + "--output-dir=ignore.d/", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoRIM supplied") +} + +func Test_CorimExtractCmd_non_existent_corim_file(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{ + "--file=nonexistent.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + + err := cmd.Execute() + assert.EqualError(t, err, "error loading signed CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") +} + +func Test_CorimExtractCmd_bad_signed_corim(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{ + "--file=bad.txt", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding signed CoRIM from bad.txt: failed CBOR decoding for COSE-Sign1 signed CoRIM: unexpected EOF") +} + +func Test_CorimExtractCmd_invalid_signed_corim(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{ + "--file=invalid.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "invalid.cbor", testSignedCorimInvalid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding signed CoRIM from invalid.cbor: failed validation of unsigned CoRIM: empty id") +} + +func Test_CorimExtractCmd_ok_save_to_default_dir(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{ + "--file=ok.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("000000-comid.cbor") + assert.NoError(t, err) + _, err = fs.Stat("000001-comid.cbor") + assert.NoError(t, err) + _, err = fs.Stat("000002-coswid.cbor") + assert.NoError(t, err) +} + +func Test_CorimExtractCmd_ok_save_to_non_default_dir(t *testing.T) { + cmd := NewCorimExtractCmd() + + args := []string{ + "--file=ok.cbor", + "--output-dir=my-dir/", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("my-dir/000000-comid.cbor") + assert.NoError(t, err) + _, err = fs.Stat("my-dir/000001-comid.cbor") + assert.NoError(t, err) + _, err = fs.Stat("my-dir/000002-coswid.cbor") + assert.NoError(t, err) +} diff --git a/cocli/cmd/corimSign.go b/cocli/cmd/corimSign.go new file mode 100644 index 00000000..ba842aca --- /dev/null +++ b/cocli/cmd/corimSign.go @@ -0,0 +1,153 @@ +// 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/corim" + cose "github.com/veraison/go-cose" +) + +var ( + corimSignCorimFile *string + corimSignKeyFile *string + corimSignOutputFile *string + corimSignMetaFile *string +) + +var corimSignCmd = NewCorimSignCmd() + +func NewCorimSignCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "sign", + Short: "create a signed CoRIM from an unsigned, CBOR-encoded CoRIM using the supplied key", + Long: `create a signed CoRIM from an unsigned, CBOR-encoded CoRIM using the supplied key + + Sign the unsigned CoRIM unsigned-corim.cbor using the key in JWK format from + file key.jwk and save the resulting COSE Sign1 to signed-corim.cbor. Read + the relevant CorimMeta information from file meta.json. + + cli corim sign --file=unsigned-corim.cbor \ + --key=key.jwk \ + --meta=meta.json \ + --output=signed-corim.cbor + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkCorimSignArgs(); err != nil { + return err + } + + // checkCorimSignArgs makes sure corimSignCorimFile is not nil + coseFile, err := sign(*corimSignCorimFile, *corimSignKeyFile, + *corimSignMetaFile, corimSignOutputFile) + if err != nil { + return err + } + fmt.Printf(">> %q signed and saved to %q\n", *corimSignCorimFile, coseFile) + + return nil + }, + } + + corimSignCorimFile = cmd.Flags().StringP("file", "f", "", "an unsigned CoRIM file (in CBOR format)") + corimSignMetaFile = cmd.Flags().StringP("meta", "m", "", "CoRIM Meta file (in JSON format)") + corimSignKeyFile = cmd.Flags().StringP("key", "k", "", "signing key in JWK format") + corimSignOutputFile = cmd.Flags().StringP("output", "o", "", "name of the generated COSE Sign1 file") + + return cmd +} + +func checkCorimSignArgs() error { + if corimSignCorimFile == nil || *corimSignCorimFile == "" { + return errors.New("no CoRIM supplied") + } + + if corimSignKeyFile == nil || *corimSignKeyFile == "" { + return errors.New("no key supplied") + } + + if corimSignMetaFile == nil || *corimSignMetaFile == "" { + return errors.New("no CoRIM Meta supplied") + } + + return nil +} + +func sign(unsignedCorimFile, keyFile, metaFile string, outputFile *string) (string, error) { + var ( + unsignedCorimCBOR []byte + signedCorimCBOR []byte + metaJSON []byte + keyJWK []byte + err error + signedCorimFile string + c corim.UnsignedCorim + m corim.Meta + signer *cose.Signer + ) + + if unsignedCorimCBOR, err = afero.ReadFile(fs, unsignedCorimFile); err != nil { + return "", fmt.Errorf("error loading unsigned CoRIM from %s: %w", unsignedCorimFile, err) + } + + if err = c.FromCBOR(unsignedCorimCBOR); err != nil { + return "", fmt.Errorf("error decoding unsigned CoRIM from %s: %w", unsignedCorimFile, err) + } + + if err = c.Valid(); err != nil { + return "", fmt.Errorf("error validating CoRIM: %w", err) + } + + if metaJSON, err = afero.ReadFile(fs, metaFile); err != nil { + return "", fmt.Errorf("error loading CoRIM Meta from %s: %w", metaFile, err) + } + + if err = m.FromJSON(metaJSON); err != nil { + return "", fmt.Errorf("error decoding CoRIM Meta from %s: %w", metaFile, err) + } + + if err = m.Valid(); err != nil { + return "", fmt.Errorf("error validating CoRIM Meta: %w", err) + } + + if keyJWK, err = afero.ReadFile(fs, keyFile); err != nil { + return "", fmt.Errorf("error loading signing key from %s: %w", keyFile, err) + } + + if signer, err = corim.SignerFromJWK(keyJWK); err != nil { + return "", fmt.Errorf("error loading signing key from %s: %w", keyFile, err) + } + + s := corim.SignedCorim{ + UnsignedCorim: c, + Meta: m, + } + + signedCorimCBOR, err = s.Sign(signer) + if err != nil { + return "", fmt.Errorf("error signing CoRIM: %w", err) + } + + if outputFile == nil || *outputFile == "" { + signedCorimFile = "signed-" + unsignedCorimFile + } else { + signedCorimFile = *outputFile + } + + err = afero.WriteFile(fs, signedCorimFile, signedCorimCBOR, 0644) + if err != nil { + return "", fmt.Errorf("error saving signed CoRIM to file %s: %w", signedCorimFile, err) + } + + return signedCorimFile, nil +} + +func init() { + corimCmd.AddCommand(corimSignCmd) +} diff --git a/cocli/cmd/corimSign_test.go b/cocli/cmd/corimSign_test.go new file mode 100644 index 00000000..44dcb1ee --- /dev/null +++ b/cocli/cmd/corimSign_test.go @@ -0,0 +1,264 @@ +// 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" +) + +func Test_CorimSignCmd_unknown_argument(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_CorimSignCmd_mandatory_args_missing_corim_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--key=ignored.jwk", + "--meta=ignored.json", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoRIM supplied") +} + +func Test_CorimSignCmd_mandatory_args_missing_meta_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ignored.cbor", + "--key=ignored.jwk", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoRIM Meta supplied") +} + +func Test_CorimSignCmd_mandatory_args_missing_key_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ignored.cbor", + "--meta=ignored.json", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no key supplied") +} + +func Test_CorimSignCmd_non_existent_unsigned_corim_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=nonexistent.cbor", + "--key=ignored.jwk", + "--meta=ignored.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + + err := cmd.Execute() + assert.EqualError(t, err, "error loading unsigned CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") +} + +func Test_CorimSignCmd_bad_unsigned_corim(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=bad.txt", + "--key=ignored.jwk", + "--meta=ignored.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding unsigned CoRIM from bad.txt: unexpected EOF") +} + +func Test_CorimSignCmd_invalid_unsigned_corim(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=invalid.cbor", + "--key=ignored.jwk", + "--meta=ignored.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "invalid.cbor", testCorimInvalid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error validating CoRIM: tags validation failed: no tags") +} + +func Test_CorimSignCmd_non_existent_meta_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=ignored.jwk", + "--meta=nonexistent.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error loading CoRIM Meta from nonexistent.json: open nonexistent.json: file does not exist") +} + +func Test_CorimSignCmd_bad_meta_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=ignored.jwk", + "--meta=bad.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "bad.json", []byte("{"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding CoRIM Meta from bad.json: unexpected end of JSON input") +} + +func Test_CorimSignCmd_invalid_meta_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=ignored.jwk", + "--meta=invalid.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "invalid.json", testMetaInvalid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error validating CoRIM Meta: invalid meta: empty name") +} + +func Test_CorimSignCmd_non_existent_key_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=nonexistent.jwk", + "--meta=ok.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error loading signing key from nonexistent.jwk: open nonexistent.jwk: file does not exist") +} + +func Test_CorimSignCmd_invalid_key_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=invalid.jwk", + "--meta=ok.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "invalid.jwk", []byte("{}"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error loading signing key from invalid.jwk: failed to unmarshal JWK set: failed to unmarshal key from JSON headers: invalid key type from JSON ()") +} + +func Test_CorimSignCmd_ok_with_default_output_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=ok.jwk", + "--meta=ok.json", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.jwk", testECKey, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("signed-ok.cbor") + assert.NoError(t, err) +} + +func Test_CorimSignCmd_ok_with_custom_output_file(t *testing.T) { + cmd := NewCorimSignCmd() + + args := []string{ + "--file=ok.cbor", + "--key=ok.jwk", + "--meta=ok.json", + "--output=my-signed-corim.cbor", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.json", testMetaValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.jwk", testECKey, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) + + _, err = fs.Stat("my-signed-corim.cbor") + assert.NoError(t, err) +} diff --git a/cocli/cmd/corimVerify.go b/cocli/cmd/corimVerify.go new file mode 100644 index 00000000..35f79f0d --- /dev/null +++ b/cocli/cmd/corimVerify.go @@ -0,0 +1,103 @@ +// 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/corim" + cose "github.com/veraison/go-cose" +) + +var ( + corimVerifyCorimFile *string + corimVerifyKeyFile *string +) + +var corimVerifyCmd = NewCorimVerifyCmd() + +func NewCorimVerifyCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "verify", + Short: "verify a signed CoRIM using the supplied key", + Long: `verify a signed CoRIM using the supplied key + + Verify the signed CoRIM signed-corim.cbor using the key in JWK format from + file key.jwk + + cli corim verify --file=signed-corim.cbor --key=key.jwk + `, + + RunE: func(cmd *cobra.Command, args []string) error { + if err := checkCorimVerifyArgs(); err != nil { + return err + } + + // checkCorimVerifyArgs makes sure corimVerifyCorimFile is not nil + err := verify(*corimVerifyCorimFile, *corimVerifyKeyFile) + if err != nil { + return err + } + fmt.Printf(">> %q verified\n", *corimVerifyCorimFile) + + return nil + }, + } + + corimVerifyCorimFile = cmd.Flags().StringP("file", "f", "", "a signed CoRIM file (in CBOR format)") + corimVerifyKeyFile = cmd.Flags().StringP("key", "k", "", "verification key in JWK format") + + return cmd +} + +func checkCorimVerifyArgs() error { + if corimVerifyCorimFile == nil || *corimVerifyCorimFile == "" { + return errors.New("no CoRIM supplied") + } + + if corimVerifyKeyFile == nil || *corimVerifyKeyFile == "" { + return errors.New("no key supplied") + } + + return nil +} + +func verify(signedCorimFile, keyFile string) error { + var ( + signedCorimCBOR []byte + keyJWK []byte + err error + signer *cose.Signer + s corim.SignedCorim + ) + + if signedCorimCBOR, err = afero.ReadFile(fs, signedCorimFile); err != nil { + return fmt.Errorf("error loading signed CoRIM from %s: %w", signedCorimFile, err) + } + + if err = s.FromCOSE(signedCorimCBOR); err != nil { + return fmt.Errorf("error decoding signed CoRIM from %s: %w", signedCorimFile, err) + } + + if keyJWK, err = afero.ReadFile(fs, keyFile); err != nil { + return fmt.Errorf("error loading verifying key from %s: %w", keyFile, err) + } + + if signer, err = corim.SignerFromJWK(keyJWK); err != nil { + return fmt.Errorf("error loading verifying key from %s: %w", keyFile, err) + } + + if err = s.Verify(signer.Verifier().PublicKey); err != nil { + return fmt.Errorf("error verifying %s with key %s: %w", signedCorimFile, keyFile, err) + } + + return nil +} + +func init() { + corimCmd.AddCommand(corimVerifyCmd) +} diff --git a/cocli/cmd/corimVerify_test.go b/cocli/cmd/corimVerify_test.go new file mode 100644 index 00000000..ec581844 --- /dev/null +++ b/cocli/cmd/corimVerify_test.go @@ -0,0 +1,133 @@ +// 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" +) + +func Test_CorimVerifyCmd_unknown_argument(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{"--unknown-argument=val"} + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "unknown flag: --unknown-argument") +} + +func Test_CorimVerifyCmd_mandatory_args_missing_corim_file(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--key=ignored.jwk", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no CoRIM supplied") +} + +func Test_CorimVerifyCmd_mandatory_args_missing_key_file(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--file=ignored.jwk", + } + cmd.SetArgs(args) + + err := cmd.Execute() + assert.EqualError(t, err, "no key supplied") +} + +func Test_CorimVerifyCmd_non_existent_signed_corim_file(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--file=nonexistent.cbor", + "--key=ignored.jwk", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + + err := cmd.Execute() + assert.EqualError(t, err, "error loading signed CoRIM from nonexistent.cbor: open nonexistent.cbor: file does not exist") +} + +func Test_CorimVerifyCmd_bad_signed_corim(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--file=bad.txt", + "--key=ignored.jwk", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "bad.txt", []byte("hello!"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error decoding signed CoRIM from bad.txt: failed CBOR decoding for COSE-Sign1 signed CoRIM: unexpected EOF") +} + +func Test_CorimVerifyCmd_non_existent_key_file(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--file=ok.cbor", + "--key=nonexistent.jwk", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error loading verifying key from nonexistent.jwk: open nonexistent.jwk: file does not exist") +} + +func Test_CorimVerifyCmd_invalid_key_file(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--file=ok.cbor", + "--key=invalid.jwk", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "invalid.jwk", []byte("{}"), 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.EqualError(t, err, "error loading verifying key from invalid.jwk: failed to unmarshal JWK set: failed to unmarshal key from JSON headers: invalid key type from JSON ()") +} + +func Test_CorimVerifyCmd_ok(t *testing.T) { + cmd := NewCorimVerifyCmd() + + args := []string{ + "--file=ok.cbor", + "--key=ok.jwk", + } + cmd.SetArgs(args) + + fs = afero.NewMemMapFs() + err := afero.WriteFile(fs, "ok.cbor", testSignedCorimValid, 0644) + require.NoError(t, err) + err = afero.WriteFile(fs, "ok.jwk", testECKey, 0644) + require.NoError(t, err) + + err = cmd.Execute() + assert.NoError(t, err) +} diff --git a/cocli/cmd/root.go b/cocli/cmd/root.go new file mode 100644 index 00000000..ec617023 --- /dev/null +++ b/cocli/cmd/root.go @@ -0,0 +1,60 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/afero" + "github.com/spf13/cobra" + + "github.com/spf13/viper" +) + +var ( + cfgFile string + fs = afero.NewOsFs() +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "cocli", + Short: "CoRIM & CoMID swiss-army knife", + Version: "0.0.1", + SilenceUsage: true, + SilenceErrors: true, +} + +func Execute() { + cobra.CheckErr(rootCmd.Execute()) +} + +func init() { + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cli.yaml)") +} + +// initConfig reads in config file and ENV variables if set +func initConfig() { + if cfgFile != "" { + viper.SetConfigFile(cfgFile) + } else { + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // search config in home directory with name ".cli" (without extension) + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".cli") + } + + viper.AutomaticEnv() // read in environment variables that match + + // if a config file is found, read it in + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/cocli/cmd/test_vars.go b/cocli/cmd/test_vars.go new file mode 100644 index 00000000..812eb609 --- /dev/null +++ b/cocli/cmd/test_vars.go @@ -0,0 +1,159 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +import "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", + ) + // note: embedded CoSWIDs are not validated {0: h'5C57E8F446CD421B91C908CF93E13CFC', 1: [505(h'deadbeef')]} + testCorimValid = comid.MustHexDecode(nil, + "a200505c57e8f446cd421b91c908cf93e13cfc0181d901f944deadbeef", + ) + // {0: h'5C57E8F446CD421B91C908CF93E13CFC'} + testCorimInvalid = comid.MustHexDecode(nil, + "a100505c57e8f446cd421b91c908cf93e13cfc", + ) + testMetaInvalid = []byte("{}") + testMetaValid = []byte(`{ + "signer": { + "name": "ACME Ltd signing key", + "uri": "https://acme.example" + }, + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + } + }`) + testECKey = []byte(`{ + "kty": "EC", + "crv": "P-256", + "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + "use": "enc", + "kid": "1" + }`) + + testSignedCorimValid = comid.MustHexDecode(nil, ` +d284585da3012603746170706c69636174696f6e2f72696d2b63626f7208 +5841a200a2007441434d45204c7464207369676e696e67206b657901d820 +7468747470733a2f2f61636d652e6578616d706c6501a200c11a61ce4800 +01c11a69546780a059061aa600505c57e8f446cd421b91c908cf93e13cfc +0183590216d901faa40065656e2d474201a10050366d0a0a598845ed8488 +2f2a544f62420281a3006941434d45204c74642e01d8207468747470733a +2f2f61636d652e6578616d706c65028300010204a1028282a200a300d902 +27582061636d652d696d706c656d656e746174696f6e2d69642d30303030 +3030303031016441434d45026a526f616452756e6e657201d90226582101 +ceebae7b8927a3227e5303cf5e0f1f7b34bb542ad7250ac03fbcde36ec2f +150881a100787c4d466b77457759484b6f5a497a6a3043415159494b6f5a +497a6a30444151634451674145466e3074616f41775233506d724b6b594c +74417344396f30354b534d366d6267664e436770754c306736567054486b +5a6c3733776b354244786f56376e2b4f656565306949716b5733484d5a54 +334554696e694a64673d3d82a200a300d90227582061636d652d696d706c +656d656e746174696f6e2d69642d303030303030303031016441434d4502 +6a526f616452756e6e657201d902265821014ca3e4f50bf248c39787020d +68ffd05c88767751bf2645ca923f57a98becd29681a100787c4d466b7745 +7759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741 +453656777165376879334f385970612b425545544c556a424e5533724558 +565579743958485237484a574c473758544b51643969316b565258654250 +444c466e66597275312f657578526e4a4d374839556f46444c64413d3d59 +01a3d901faa40065656e2d474201a1005043bbe37f2e614b33aed353cff1 +428b160281a3006941434d45204c74642e01d8207468747470733a2f2f61 +636d652e6578616d706c65028300010204a1008182a100a300d902275820 +61636d652d696d706c656d656e746174696f6e2d69642d30303030303030 +3031016441434d45026a526f616452756e6e657283a200d90258a3016242 +4c0465322e312e30055820acbb11c7e4da217205523ce4ce1a245ae1a239 +ae3c6bfd9e7871f7e5d8bae86b01a102818201582087428fc522803d3106 +5e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7a200d90258a301 +6450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a24 +5ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820158200263829989 +b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813fa200d9 +0258a3016441526f540465302e312e34055820acbb11c7e4da217205523c +e4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a1028182015820a3 +a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a14 +7859017cd901f9a8007820636f6d2e61636d652e727264323031332d6365 +2d7370312d76342d312d352d300c0001783041434d4520526f616472756e +6e6572204465746563746f72203230313320436f796f7465204564697469 +6f6e205350310d65342e312e3505a5182b65747269616c182d6432303133 +182f66636f796f7465183473526f616472756e6e6572204465746563746f +721836637370310282a3181f745468652041434d4520436f72706f726174 +696f6e18206861636d652e636f6d1821820102a3181f75436f796f746520 +53657276696365732c20496e632e18206c6d79636f796f74652e636f6d18 +210404a21826781c7777772e676e752e6f72672f6c6963656e7365732f67 +706c2e7478741828676c6963656e736506a110a318186a72726465746563 +746f7218196d2570726f6772616d6461746125181aa111a318186e727264 +65746563746f722e657865141a000820e80782015820a314fc2dc663ae7a +6b6bc6787594057396e6b3f569cd50fd5ddb4d1bbafd2b6a0281a200d820 +784068747470733a2f2f706172656e742e6578616d706c652f72696d732f +63636233616138352d363162342d343066312d383438652d303261643665 +3861323534620182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e +62347a824decb67a84e5224d750382482b06010401a02064781c68747470 +3a2f2f61726d2e636f6d2f696f742f70726f66696c652f3104a200c11a61 +ce480001c11a695467800581a3006941434d45204c74642e01d8206c6163 +6d652e6578616d706c6502810158400d35302dda8e1249e295154255370f +ade855043ae59c7d460b49415fddec40d70e22f21f98ac18bc69a18d1757 +30f6d7c6c28fa0819aeded8653f69d71d60172 + `) + testSignedCorimInvalid = comid.MustHexDecode(nil, ` +d284585da3012603746170706c69636174696f6e2f72696d2b63626f7208 +5841a200a2007441434d45204c7464207369676e696e67206b657901d820 +7468747470733a2f2f61636d652e6578616d706c6501a200c11a61ce4800 +01c11a69546780a041a044deadbeef + `) + PSARefValCBOR = comid.MustHexDecode(nil, ` +a40065656e2d474201a1005043bbe37f2e614b33aed353cff1428b160281 +a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e65 +78616d706c65028300010204a1008182a100a300d90227582061636d652d +696d706c656d656e746174696f6e2d69642d303030303030303031016441 +434d45026a526f616452756e6e657283a200d90258a30162424c0465322e +312e30055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e +7871f7e5d8bae86b01a102818201582087428fc522803d31065e7bce3cf0 +3fe475096631e5e07bbd7a0fde60c4cf25c7a200d90258a3016450526f54 +0465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae +3c6bfd9e7871f7e5d8bae86b01a10281820158200263829989b6fd954f72 +baaf2fc64bc2e2f01d692d4de72986ea808f6e99813fa200d90258a30164 +41526f540465302e312e34055820acbb11c7e4da217205523ce4ce1a245a +e1a239ae3c6bfd9e7871f7e5d8bae86b01a1028182015820a3a5e715f0cc +574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + `) + testComid = comid.MustHexDecode(nil, ` +a40065656e2d474201a1005043bbe37f2e614b33aed353cff1428b160281 +a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e65 +78616d706c65028300010204a1008182a100a300d90227582061636d652d +696d706c656d656e746174696f6e2d69642d303030303030303031016441 +434d45026a526f616452756e6e657283a200d90258a30162424c0465322e +312e30055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e +7871f7e5d8bae86b01a102818201582087428fc522803d31065e7bce3cf0 +3fe475096631e5e07bbd7a0fde60c4cf25c7a200d90258a3016450526f54 +0465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae +3c6bfd9e7871f7e5d8bae86b01a10281820158200263829989b6fd954f72 +baaf2fc64bc2e2f01d692d4de72986ea808f6e99813fa200d90258a30164 +41526f540465302e312e34055820acbb11c7e4da217205523ce4ce1a245a +e1a239ae3c6bfd9e7871f7e5d8bae86b01a1028182015820a3a5e715f0cc +574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 + `) + testCoswid = comid.MustHexDecode(nil, ` +a8007820636f6d2e61636d652e727264323031332d63652d7370312d7634 +2d312d352d300c0001783041434d4520526f616472756e6e657220446574 +6563746f72203230313320436f796f74652045646974696f6e205350310d +65342e312e3505a5182b65747269616c182d6432303133182f66636f796f +7465183473526f616472756e6e6572204465746563746f72183663737031 +0282a3181f745468652041434d4520436f72706f726174696f6e18206861 +636d652e636f6d1821820102a3181f75436f796f74652053657276696365 +732c20496e632e18206c6d79636f796f74652e636f6d18210404a2182678 +1c7777772e676e752e6f72672f6c6963656e7365732f67706c2e74787418 +28676c6963656e736506a110a318186a72726465746563746f7218196d25 +70726f6772616d6461746125181aa111a318186e72726465746563746f72 +2e657865141a000820e80782015820a314fc2dc663ae7a6b6bc678759405 +7396e6b3f569cd50fd5ddb4d1bbafd2b6a + `) +) diff --git a/cocli/data/comid/1.cbor b/cocli/data/comid/1.cbor new file mode 100644 index 00000000..35e0cb60 Binary files /dev/null and b/cocli/data/comid/1.cbor differ diff --git a/cocli/data/comid/2.cbor b/cocli/data/comid/2.cbor new file mode 100644 index 00000000..b35b2add Binary files /dev/null and b/cocli/data/comid/2.cbor differ diff --git a/cocli/data/coswid/1.cbor b/cocli/data/coswid/1.cbor new file mode 100644 index 00000000..bc78ffcc Binary files /dev/null and b/cocli/data/coswid/1.cbor differ diff --git a/cocli/data/keys/ec-p256.jwk b/cocli/data/keys/ec-p256.jwk new file mode 100644 index 00000000..e3c07719 --- /dev/null +++ b/cocli/data/keys/ec-p256.jwk @@ -0,0 +1,9 @@ +{ + "kty": "EC", + "crv": "P-256", + "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + "use": "enc", + "kid": "1" +} diff --git a/cocli/data/templates/comid-psa-iakpub.json b/cocli/data/templates/comid-psa-iakpub.json new file mode 100644 index 00000000..03aaa6c7 --- /dev/null +++ b/cocli/data/templates/comid-psa-iakpub.json @@ -0,0 +1,64 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "366D0A0A-5988-45ED-8488-2F2A544F6242", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "attester-verification-keys": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + }, + "instance": { + "type": "ueid", + "value": "Ac7rrnuJJ6MiflMDz14PH3s0u1Qq1yUKwD+83jbsLxUI" + } + }, + "verification-keys": [ + { + "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFn0taoAwR3PmrKkYLtAsD9o05KSM6mbgfNCgpuL0g6VpTHkZl73wk5BDxoV7n+Oeee0iIqkW3HMZT3ETiniJdg==" + } + ] + }, + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + }, + "instance": { + "type": "ueid", + "value": "AUyj5PUL8kjDl4cCDWj/0FyIdndRvyZFypI/V6mL7NKW" + } + }, + "verification-keys": [ + { + "key": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE6Vwqe7hy3O8Ypa+BUETLUjBNU3rEXVUyt9XHR7HJWLG7XTKQd9i1kVRXeBPDLFnfYru1/euxRnJM7H9UoFDLdA==" + } + ] + } + ] + } +} diff --git a/cocli/data/templates/comid-psa-refval.json b/cocli/data/templates/comid-psa-refval.json new file mode 100644 index 00000000..8fd66fbc --- /dev/null +++ b/cocli/data/templates/comid-psa-refval.json @@ -0,0 +1,81 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "BL", + "version": "2.1.0", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } + }, + "value": { + "digests": [ + "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + ] + } + }, + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "PRoT", + "version": "1.3.5", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } + }, + "value": { + "digests": [ + "sha-256:AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8=" + ] + } + }, + { + "key": { + "type": "psa.refval-id", + "value": { + "label": "ARoT", + "version": "0.1.4", + "signer-id": "rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs=" + } + }, + "value": { + "digests": [ + "sha-256:o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg=" + ] + } + } + ] + } + ] + } +} diff --git a/cocli/data/templates/corim-full.json b/cocli/data/templates/corim-full.json new file mode 100644 index 00000000..c8b3cf1b --- /dev/null +++ b/cocli/data/templates/corim-full.json @@ -0,0 +1,26 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "dependent-rims": [ + { + "href": "https://parent.example/rims/ccb3aa85-61b4-40f1-848e-02ad6e8a254b", + "thumbprint": "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + } + ], + "profiles": [ + "1.3.6.1.4.1.4128.100", + "http://arm.com/iot/profile/1" + ], + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "acme.example", + "roles": [ + "manifestCreator" + ] + } + ] +} diff --git a/cocli/data/templates/corim-mini.json b/cocli/data/templates/corim-mini.json new file mode 100644 index 00000000..bc7e70ba --- /dev/null +++ b/cocli/data/templates/corim-mini.json @@ -0,0 +1,3 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc" +} diff --git a/cocli/data/templates/meta-full.json b/cocli/data/templates/meta-full.json new file mode 100644 index 00000000..5c465494 --- /dev/null +++ b/cocli/data/templates/meta-full.json @@ -0,0 +1,10 @@ +{ + "signer": { + "name": "ACME Ltd signing key", + "uri": "https://acme.example" + }, + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + } +} diff --git a/cocli/data/templates/meta-mini.json b/cocli/data/templates/meta-mini.json new file mode 100644 index 00000000..fb978cc8 --- /dev/null +++ b/cocli/data/templates/meta-mini.json @@ -0,0 +1,5 @@ +{ + "signer": { + "name": "ACME Ltd signing key" + } +} diff --git a/cocli/main.go b/cocli/main.go new file mode 100644 index 00000000..c3fc2a3f --- /dev/null +++ b/cocli/main.go @@ -0,0 +1,12 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "github.com/veraison/corim/cocli/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/comid/class.go b/comid/class.go index 38f26b68..085e703b 100644 --- a/comid/class.go +++ b/comid/class.go @@ -164,3 +164,12 @@ func (o *Class) FromJSON(data []byte) error { return o.Valid() } + +// ToJSON serializes the target Class to JSON (if the Class is "valid") +func (o Class) ToJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return json.Marshal(&o) +} diff --git a/comid/classid.go b/comid/classid.go index 913c604f..d74ff941 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -35,6 +35,27 @@ func (o *ClassID) SetUUID(uuid UUID) *ClassID { type ImplID [32]byte type TaggedImplID ImplID + +func (o ImplID) MarshalJSON() ([]byte, error) { + return json.Marshal(o[:]) +} + +func (o *ImplID) UnmarshalJSON(data []byte) error { + var b []byte + + if err := json.Unmarshal(data, &b); err != nil { + return fmt.Errorf("bad ImplID: %w", err) + } + + if nb := len(b); nb != 32 { + return fmt.Errorf("bad ImplID format: got %d bytes, want 32", nb) + } + + copy(o[:], b) + + return nil +} + type TaggedOID OID // SetImplID sets the value of the targed ClassID to the supplied PSA @@ -122,10 +143,7 @@ func (o *ClassID) UnmarshalCBOR(data []byte) error { // "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" // } func (o *ClassID) UnmarshalJSON(data []byte) error { - v := struct { - Type string `json:"type"` - Value interface{} `json:"value"` - }{} + var v tnv if err := json.Unmarshal(data, &v); err != nil { return err @@ -133,23 +151,23 @@ func (o *ClassID) UnmarshalJSON(data []byte) error { switch v.Type { case "uuid": // nolint: goconst - var uuid UUID - if err := jsonDecodeUUID(v.Value, &uuid); err != nil { + var x UUID + if err := x.UnmarshalJSON(v.Value); err != nil { return err } - o.SetUUID(uuid) + o.val = TaggedUUID(x) case "oid": - var berOID OID - if err := jsonDecodeOID(v.Value, &berOID); err != nil { + var x OID + if err := x.UnmarshalJSON(v.Value); err != nil { return err } - o.val = TaggedOID(berOID) + o.val = TaggedOID(x) case "psa.impl-id": - var implID ImplID - if err := jsonDecodeImplID(v.Value, &implID); err != nil { + var x ImplID + if err := x.UnmarshalJSON(v.Value); err != nil { return err } - o.SetImplID(implID) + o.val = TaggedImplID(x) default: return fmt.Errorf("unknown type '%s' for class id", v.Type) } @@ -157,6 +175,40 @@ func (o *ClassID) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON serializes the target ClassID to JSON +func (o ClassID) MarshalJSON() ([]byte, error) { + var ( + v tnv + b []byte + err error + ) + + switch t := o.val.(type) { + case TaggedUUID: + b, err = UUID(t).MarshalJSON() + if err != nil { + return nil, err + } + v = tnv{Type: "uuid", Value: b} + case TaggedOID: + b, err = OID(t).MarshalJSON() + if err != nil { + return nil, err + } + v = tnv{Type: "oid", Value: b} + case TaggedImplID: + b, err = ImplID(t).MarshalJSON() + if err != nil { + return nil, err + } + v = tnv{Type: "psa.impl-id", Value: b} + default: + return nil, fmt.Errorf("unknown type %T for class-id", t) + } + + return json.Marshal(v) +} + // Type returns the type of the target ClassID, i.e., one of UUID, OID or PSA // Implementation ID func (o ClassID) Type() ClassIDType { diff --git a/comid/classid_test.go b/comid/classid_test.go index 8d23a8a1..b9b5efc9 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -143,7 +143,7 @@ func TestClassID_UnmarshalJSON_badInput_missing_value(t *testing.T) { var actual ClassID err := actual.UnmarshalJSON([]byte(tv)) - assert.EqualError(t, err, "ImplID must be string") + assert.EqualError(t, err, "bad ImplID: unexpected end of JSON input") assert.Equal(t, ClassIDTypeUnknown, actual.Type()) } diff --git a/comid/comid.go b/comid/comid.go index deb6af1e..ea9779c7 100644 --- a/comid/comid.go +++ b/comid/comid.go @@ -234,6 +234,10 @@ func (o Comid) Valid() error { // ToCBOR serializes the target Comid to CBOR func (o Comid) ToCBOR() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + return em.Marshal(&o) } @@ -246,3 +250,20 @@ func (o *Comid) FromCBOR(data []byte) error { func (o *Comid) FromJSON(data []byte) error { return json.Unmarshal(data, o) } + +// ToJSON serializes the target Comid to JSON +func (o Comid) ToJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return json.Marshal(&o) +} + +func (o Comid) ToJSONPretty(indent string) ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return json.MarshalIndent(&o, "", indent) +} diff --git a/comid/common.go b/comid/common.go deleted file mode 100644 index 43866d30..00000000 --- a/comid/common.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import ( - "encoding/base64" - "fmt" - "net" - - "github.com/google/uuid" - "github.com/veraison/eat" -) - -// UUID string in canonical 8-4-4-4-12 format -func jsonDecodeUUID(from interface{}, to *UUID) error { - s, ok := from.(string) - if !ok { - return fmt.Errorf("UUID must be string") - } - - u, err := uuid.Parse(s) - if err != nil { - return fmt.Errorf("bad UUID: %w", err) - } - - *to = UUID(u) - - return nil -} - -// (absolute) OID string in dotted decimal notation -func jsonDecodeOID(from interface{}, to *OID) error { - s, ok := from.(string) - if !ok { - return fmt.Errorf("OID must be string") - } - - var oid OID - - err := oid.FromString(s) - if err != nil { - return fmt.Errorf("decoding %s: %w", s, err) - } - - *to = oid - - return nil -} - -// Implementation ID as base64-encoded string -func jsonDecodeImplID(from interface{}, to *ImplID) error { - s, ok := from.(string) - if !ok { - return fmt.Errorf("ImplID must be string") // nolint: golint - } - - val, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return fmt.Errorf("bad ImplID: %w", err) - } - - if nb := len(val); nb != 32 { - return fmt.Errorf("bad ImplID format: got %d bytes, want 32", nb) - } - - copy(to[:], val) - - return nil -} - -// UEID (bstr .size (7..33)) as base64-encoded string -func jsonDecodeUEID(from interface{}, to *eat.UEID) error { - s, ok := from.(string) - if !ok { - return fmt.Errorf("UEID must be string") - } - - val, err := base64.StdEncoding.DecodeString(s) - if err != nil { - return fmt.Errorf("bad UUID: %w", err) - } - - ueid := eat.UEID(val) - - if err := ueid.Validate(); err != nil { - return err - } - - *to = ueid - - return nil -} - -// Supported formats are IEEE 802 MAC-48, EUI-48, EUI-64, e.g.: -// 00:00:5e:00:53:01 -// 00-00-5e-00-53-01 -// 02:00:5e:10:00:00:00:01 -// 02-00-5e-10-00-00-00-01 -func jsonDecodeMACaddr(from interface{}, to *net.HardwareAddr) error { - s, ok := from.(string) - if !ok { - return fmt.Errorf("MAC address must be string") - } - - val, err := net.ParseMAC(s) - if err != nil { - return fmt.Errorf("bad MAC address %w", err) - } - - *to = val - - return nil -} diff --git a/comid/endorsedvalue_test.go b/comid/endorsedvalue_test.go new file mode 100644 index 00000000..f9b2e1ac --- /dev/null +++ b/comid/endorsedvalue_test.go @@ -0,0 +1,23 @@ +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEndorsedValue_Valid_empty_environment(t *testing.T) { + tv := EndorsedValue{} + + assert.EqualError(t, tv.Valid(), "environment validation failed: environment must not be empty") +} + +func TestEndorsedValue_Valid_empty_measurements(t *testing.T) { + tv := EndorsedValue{ + Environment: Environment{ + Class: NewClassUUID(TestUUID), + }, + } + + assert.EqualError(t, tv.Valid(), "measurements validation failed: no measurement entries") +} diff --git a/comid/environment.go b/comid/environment.go index b52a6f82..bef47e24 100644 --- a/comid/environment.go +++ b/comid/environment.go @@ -26,19 +26,19 @@ func (o Environment) Valid() error { if o.Class != nil { if err := o.Class.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) + return fmt.Errorf("class validation failed: %w", err) } } if o.Instance != nil { if err := o.Instance.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) + return fmt.Errorf("instance validation failed: %w", err) } } if o.Group != nil { if err := o.Group.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) + return fmt.Errorf("group validation failed: %w", err) } } @@ -71,3 +71,12 @@ func (o *Environment) FromJSON(data []byte) error { return o.Valid() } + +// ToJSON serializes the target Environment to JSON (if the Environment is "valid") +func (o Environment) ToJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + return json.Marshal(&o) +} diff --git a/comid/environment_test.go b/comid/environment_test.go index bed036ed..c078ce90 100644 --- a/comid/environment_test.go +++ b/comid/environment_test.go @@ -26,7 +26,7 @@ func TestEnvironment_Valid_empty_class(t *testing.T) { err := tv.Valid() - assert.EqualError(t, err, "environment validation failed: class must not be empty") + assert.EqualError(t, err, "class validation failed: class must not be empty") } func TestEnvironment_Valid_empty_instance(t *testing.T) { @@ -36,7 +36,7 @@ func TestEnvironment_Valid_empty_instance(t *testing.T) { err := tv.Valid() - assert.EqualError(t, err, "environment validation failed: invalid instance id") + assert.EqualError(t, err, "instance validation failed: invalid instance id") } func TestEnvironment_Valid_empty_group(t *testing.T) { @@ -46,7 +46,7 @@ func TestEnvironment_Valid_empty_group(t *testing.T) { err := tv.Valid() - assert.EqualError(t, err, "environment validation failed: invalid group id") + assert.EqualError(t, err, "group validation failed: invalid group id") } func TestEnvironment_Valid_ok_with_class(t *testing.T) { tv := Environment{ diff --git a/comid/example_test.go b/comid/example_test.go index cb81d23c..1fdcb5e8 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -10,7 +10,7 @@ import ( "github.com/veraison/swid" ) -func Example_encode_to_CBOR() { +func Example_encode() { comid := NewComid(). SetLanguage("en-GB"). SetTagIdentity("my-ns:acme-roadrunner-supplement", 0). @@ -21,7 +21,7 @@ func Example_encode_to_CBOR() { AddReferenceValue( ReferenceValue{ Environment: Environment{ - Class: NewClassUUID(TestUUID). + Class: NewClassOID(TestOID). SetVendor("ACME Ltd."). SetModel("RoadRunner"). SetLayer(0). @@ -62,7 +62,7 @@ func Example_encode_to_CBOR() { NewMeasurement(). SetKeyUUID(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). - SetSVN(2). + SetMinSVN(2). AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). SetOpFlags(OpFlagNotSecure, OpFlagDebug, OpFlagNotConfigured). @@ -104,10 +104,17 @@ func Example_encode_to_CBOR() { cbor, err := comid.ToCBOR() if err == nil { - fmt.Printf("%x", cbor) + fmt.Printf("%x\n", cbor) + } + + json, err := comid.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) } - // Output: a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030a04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030b04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81a20075464748496a6b69736c646e415344787657592e2e2e0182744d454e477364686675676a5157457479582e2e2e744d4949456e6a43434134616741774942412e2e2e038182a101d902264702deadbeefdead81a200744d49476b416745424244436b3551626f422e2e2e0182744d4949446b6a43434178696741774942412e2e2e744d4949456e6a43434134616741774942412e2e2e + // Output: + // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a4008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff030a04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff030b04d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81a20075464748496a6b69736c646e415344787657592e2e2e0182744d454e477364686675676a5157457479582e2e2e744d4949456e6a43434134616741774942412e2e2e038182a101d902264702deadbeefdead81a200744d49476b416745424244436b3551626f422e2e2e0182744d4949446b6a43434178696741774942412e2e2e744d4949456e6a43434134616741774942412e2e2e + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"cmp":"==","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notSecure","debug"],"raw-value":"AQIDBA==","raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"ueid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"cmp":"\u003e=","value":2},"digests":["sha-256-32:q83vAA==","sha-256-32://///w=="],"op-flags":["notConfigured","notSecure","debug"],"raw-value":"AQIDBA==","raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"key":"FGHIjkisldnASDxvWY...","chain":["MENGsdhfugjQWEtyX...","MIIEnjCCA4agAwIBA..."]}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MIGkAgEBBDCk5QboB...","chain":["MIIDkjCCAxigAwIBA...","MIIEnjCCA4agAwIBA..."]}]}]}} } func Example_encode_PSA() { @@ -153,10 +160,17 @@ func Example_encode_PSA() { cbor, err := comid.ToCBOR() if err == nil { - fmt.Printf("%x", cbor) + fmt.Printf("%x\n", cbor) + } + + json, err := comid.ToJSON() + if err == nil { + fmt.Printf("%s\n", string(json)) } - // Output: a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90227582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90258a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90258a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81a100744d49476b416745424244436b3551626f422e2e2e + // Output: + // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a2008182a100a300d90227582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3082a200d90258a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00a200d90258a3016450526f540465312e332e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00028182a101d902264702deadbeefdead81a100744d49476b416745424244436b3551626f422e2e2e + // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"psa.impl-id","value":"YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE="},"vendor":"ACME Ltd.","model":"RoadRunner 2.0"}},"measurements":[{"key":{"type":"psa.refval-id","value":{"label":"BL","version":"5.0.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32:q83vAA=="]}},{"key":{"type":"psa.refval-id","value":{"label":"PRoT","version":"1.3.5","signer-id":"rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs="}},"value":{"digests":["sha-256-32:q83vAA=="]}}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MIGkAgEBBDCk5QboB..."}]}]}} } func Example_encode_PSA_attestation_verification() { @@ -178,10 +192,17 @@ func Example_encode_PSA_attestation_verification() { cbor, err := comid.ToCBOR() if err == nil { - fmt.Printf("%x", cbor) + fmt.Printf("%x\n", cbor) } - // Output: a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1028182a101d902264702deadbeefdead81a1006f4d466b77457759484b6f5a492e2e2e + json, err := comid.ToJSON() + if err == nil { + fmt.Printf("%s", string(json)) + } + + // Output: + // a301a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740281a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028301000204a1028182a101d902264702deadbeefdead81a1006f4d466b77457759484b6f5a492e2e2e + // {"tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator","maintainer"]}],"triples":{"attester-verification-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"key":"MFkwEwYHKoZI..."}]}]}} } func Example_decode_JSON() { @@ -263,7 +284,12 @@ func Example_decode_JSON() { "value": { "digests": [ "sha-256:3q2+7w==" - ] + ], + "svn": { + "cmp": "==", + "value": 1 + }, + "mac-addr": "00:00:5e:00:53:01" } } ] diff --git a/comid/group.go b/comid/group.go index 89207bd4..cacbdb44 100644 --- a/comid/group.go +++ b/comid/group.go @@ -75,10 +75,7 @@ func (o *Group) UnmarshalCBOR(data []byte) error { // "value": "69E027B2-7157-4758-BCB4-D9F167FE49EA" // } func (o *Group) UnmarshalJSON(data []byte) error { - v := struct { - Type string `json:"type"` - Value interface{} `json:"value"` - }{} + var v tnv if err := json.Unmarshal(data, &v); err != nil { return err @@ -86,14 +83,35 @@ func (o *Group) UnmarshalJSON(data []byte) error { switch v.Type { case "uuid": - var uuid UUID - if err := jsonDecodeUUID(v.Value, &uuid); err != nil { + var x UUID + if err := x.UnmarshalJSON(v.Value); err != nil { return err } - o.SetUUID(uuid) + o.val = TaggedUUID(x) default: return fmt.Errorf("unknown type %s for group", v.Type) } return nil } + +func (o Group) MarshalJSON() ([]byte, error) { + var ( + v tnv + b []byte + err error + ) + + switch t := o.val.(type) { + case TaggedUUID: + b, err = UUID(t).MarshalJSON() + if err != nil { + return nil, err + } + v = tnv{Type: "ueid", Value: b} + default: + return nil, fmt.Errorf("unknown type %T for group", t) + } + + return json.Marshal(v) +} diff --git a/comid/instance.go b/comid/instance.go index 5512921c..a215e24a 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -14,9 +14,6 @@ type Instance struct { val interface{} } -// TaggedUEID is an alias to allow automatic tagging of a eat.UEID type -type TaggedUEID eat.UEID - // NewInstance instantiates an empty instance func NewInstance() *Instance { return &Instance{} @@ -120,10 +117,7 @@ func (o *Instance) UnmarshalCBOR(data []byte) error { // } // func (o *Instance) UnmarshalJSON(data []byte) error { - v := struct { - Type string `json:"type"` - Value interface{} `json:"value"` - }{} + var v tnv if err := json.Unmarshal(data, &v); err != nil { return err @@ -131,20 +125,47 @@ func (o *Instance) UnmarshalJSON(data []byte) error { switch v.Type { case "uuid": - var u UUID - if err := jsonDecodeUUID(v.Value, &u); err != nil { + var x UUID + if err := x.UnmarshalJSON(v.Value); err != nil { return err } - o.val = TaggedUUID(u) + o.val = TaggedUUID(x) case "ueid": - ueid := eat.UEID{} - if err := jsonDecodeUEID(v.Value, &ueid); err != nil { + var x UEID + if err := x.UnmarshalJSON(v.Value); err != nil { return err } - o.val = TaggedUEID(ueid) + o.val = TaggedUEID(x) default: return fmt.Errorf("unknown type %s for instance", v.Type) } return nil } + +func (o Instance) MarshalJSON() ([]byte, error) { + var ( + v tnv + b []byte + err error + ) + + switch t := o.val.(type) { + case TaggedUUID: + b, err = UUID(t).MarshalJSON() + if err != nil { + return nil, err + } + v = tnv{Type: "uuid", Value: b} + case TaggedUEID: + b, err = UEID(t).MarshalJSON() + if err != nil { + return nil, err + } + v = tnv{Type: "ueid", Value: b} + default: + return nil, fmt.Errorf("unknown type %T for instance", t) + } + + return json.Marshal(v) +} diff --git a/comid/macaddr.go b/comid/macaddr.go index 9a2c7456..2b0cfde3 100644 --- a/comid/macaddr.go +++ b/comid/macaddr.go @@ -5,6 +5,7 @@ package comid import ( "encoding/json" + "fmt" "net" ) @@ -20,20 +21,29 @@ type MACaddr net.HardwareAddr // "mac-addr": "00:00:5e:00:53:01" // or // "mac-addr": "02:00:5e:10:00:00:00:01" +// +// Supported formats are IEEE 802 MAC-48, EUI-48, EUI-64, e.g.: +// 00:00:5e:00:53:01 +// 00-00-5e-00-53-01 +// 02:00:5e:10:00:00:00:01 +// 02-00-5e-10-00-00-00-01 func (o *MACaddr) UnmarshalJSON(data []byte) error { - var s interface{} + var s string if err := json.Unmarshal(data, &s); err != nil { return err } - var i net.HardwareAddr - - if err := jsonDecodeMACaddr(s, &i); err != nil { - return err + val, err := net.ParseMAC(s) + if err != nil { + return fmt.Errorf("bad MAC address %w", err) } - *o = MACaddr(i) + *o = MACaddr(val) return nil } + +func (o MACaddr) MarshalJSON() ([]byte, error) { + return json.Marshal(net.HardwareAddr(o).String()) +} diff --git a/comid/measurement.go b/comid/measurement.go index 61eab113..ec1be7a2 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -24,6 +24,10 @@ type Mkey struct { val interface{} } +func (o Mkey) IsSet() bool { + return o.val != nil +} + func (o Mkey) Valid() error { switch t := o.val.(type) { case TaggedUUID: @@ -87,10 +91,44 @@ func (o *Mkey) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON serializes the target Mkey into the type'n'value JSON object +// uuid, psa.refval-id +func (o Mkey) MarshalJSON() ([]byte, error) { + var ( + v tnv + b []byte + err error + ) + + switch t := o.val.(type) { + case TaggedUUID: + uuidString := UUID(t).String() + b, err = json.Marshal(uuidString) + if err != nil { + return nil, err + } + v = tnv{Type: "uuid", Value: b} + case TaggedPSARefValID: + b, err = json.Marshal(t) + if err != nil { + return nil, err + } + v = tnv{Type: "psa.refval-id", Value: b} + default: + return nil, fmt.Errorf("unknown type %T for mkey", t) + } + + return json.Marshal(v) +} + func (o Mkey) MarshalCBOR() ([]byte, error) { return em.Marshal(o.val) } +func (o *Mkey) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.val) +} + // Mval stores a measurement-values-map with JSON and CBOR serializations. type Mval struct { Ver *Version `cbor:"0,keyasint,omitempty" json:"version,omitempty"` @@ -152,8 +190,28 @@ type Version struct { Scheme swid.VersionScheme `cbor:"1,keyasint" json:"scheme"` } +func NewVersion() *Version { + return &Version{} +} + +func (o *Version) SetVersion(v string) *Version { + if o != nil { + o.Version = v + } + return o +} + +func (o *Version) SetScheme(v uint64) *Version { + if o != nil { + if o.Scheme.SetCode(v) != nil { + return nil + } + } + return o +} + func (o Version) Valid() error { - if o.Version != "" { + if o.Version == "" { return fmt.Errorf("empty version") } return nil @@ -211,6 +269,18 @@ func NewUUIDMeasurement(uuid UUID) *Measurement { return m.SetKeyUUID(uuid) } +func (o *Measurement) SetVersion(ver string, scheme uint64) *Measurement { + if o != nil { + v := NewVersion().SetVersion(ver).SetScheme(scheme) + if v == nil { + return nil + } + + o.Val.Ver = v + } + return o +} + // SetRawValueBytes sets the supplied raw-value and its mask in the // measurement-values-map of the target measurement func (o *Measurement) SetRawValueBytes(rawValue, rawValueMask []byte) *Measurement { @@ -326,7 +396,7 @@ func (o *Measurement) SetUUID(u UUID) *Measurement { } func (o Measurement) Valid() error { - if o.Key != nil { + if o.Key.IsSet() { if err := o.Key.Valid(); err != nil { return err } diff --git a/comid/measurement_test.go b/comid/measurement_test.go index 153372e5..11ecc672 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -70,8 +70,14 @@ func TestMeasurement_NewUUIDMeasurement_no_values(t *testing.T) { assert.EqualError(t, err, "no measurement value set") } -func TestMeasurement_NewUUIDMeasurement_one_value(t *testing.T) { - tv := NewUUIDMeasurement(TestUUID).SetMinSVN(2) +func TestMeasurement_NewUUIDMeasurement_some_value(t *testing.T) { + var vs swid.VersionScheme + require.NoError(t, vs.SetCode(swid.VersionSchemeSemVer)) + + tv := NewUUIDMeasurement(TestUUID). + SetMinSVN(2). + SetOpFlags(OpFlagDebug). + SetVersion("1.2.3", swid.VersionSchemeSemVer) require.NotNil(t, tv) err := tv.Valid() diff --git a/comid/measurements.go b/comid/measurements.go index 7585adc2..6dd311e8 100644 --- a/comid/measurements.go +++ b/comid/measurements.go @@ -3,7 +3,10 @@ package comid -import "fmt" +import ( + "errors" + "fmt" +) // Measurements is an array of Measurement type Measurements []Measurement @@ -22,6 +25,10 @@ func (o *Measurements) AddMeasurement(m *Measurement) *Measurements { } func (o Measurements) Valid() error { + if len(o) == 0 { + return errors.New("no measurement entries") + } + for i, m := range o { if err := m.Valid(); err != nil { return fmt.Errorf("measurement at index %d: %w", i, err) diff --git a/comid/oid.go b/comid/oid.go index 2e96bfe0..822d7deb 100644 --- a/comid/oid.go +++ b/comid/oid.go @@ -5,6 +5,7 @@ package comid import ( "encoding/asn1" + "encoding/json" "fmt" "strconv" "strings" @@ -133,3 +134,21 @@ func constructBERFromVal(val []byte) ([]byte, error) { return berOID, nil } + +func (o *OID) UnmarshalJSON(data []byte) error { + var s string + + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + if err := o.FromString(s); err != nil { + return err + } + + return nil +} + +func (o OID) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} diff --git a/comid/opflag.go b/comid/opflag.go index 74165f58..bd856225 100644 --- a/comid/opflag.go +++ b/comid/opflag.go @@ -24,6 +24,28 @@ func NewOpFlags() *OpFlags { return new(OpFlags) } +func (o OpFlags) Strings() []string { + var a []string + + if o&OpFlagNotConfigured != 0 { + a = append(a, "notConfigured") + } + + if o&OpFlagNotSecure != 0 { + a = append(a, "notSecure") + } + + if o&OpFlagRecovery != 0 { + a = append(a, "recovery") + } + + if o&OpFlagDebug != 0 { + a = append(a, "debug") + } + + return a +} + func (o OpFlags) Valid() error { // While any combination in the lower half-byte is acceptable, the most // significant nibble must be all zeroes. @@ -52,7 +74,7 @@ func (o OpFlags) IsSet(flag OpFlags) bool { // UnmarshalJSON provides a custom deserializer for the OpFlags type that uses an // array of identifiers rather than a bit set, e.g.: // -// "flags": [ +// "op-flags": [ // "notSecure", // "debug" // ] @@ -86,3 +108,7 @@ func (o *OpFlags) UnmarshalJSON(data []byte) error { return nil } + +func (o OpFlags) MarshalJSON() ([]byte, error) { + return json.Marshal(o.Strings()) +} diff --git a/comid/rawvalue.go b/comid/rawvalue.go index c0439f9e..f16aa6fd 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -67,3 +67,12 @@ func (o *RawValue) UnmarshalJSON(data []byte) error { return nil } + +func (o RawValue) MarshalJSON() ([]byte, error) { + switch t := o.val.(type) { + case TaggedRawValueBytes: + return json.Marshal(t) + default: + return nil, fmt.Errorf("unknown type %T for $raw-value-type-choice", t) + } +} diff --git a/comid/rel.go b/comid/rel.go index aaee275a..ad35ed36 100644 --- a/comid/rel.go +++ b/comid/rel.go @@ -42,6 +42,17 @@ func (o Rel) Valid() error { return nil } +func (o Rel) String() string { + switch o { + case RelReplaces: + return "replaces" + case RelSupplements: + return "supplements" + default: + return fmt.Sprintf("rel(%d)", o) + } +} + func (o Rel) ToCBOR() ([]byte, error) { if err := o.Valid(); err != nil { return nil, err @@ -86,3 +97,7 @@ func (o *Rel) UnmarshalJSON(data []byte) error { return nil } + +func (o Rel) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} diff --git a/comid/role.go b/comid/role.go index be3c314b..1b08322b 100644 --- a/comid/role.go +++ b/comid/role.go @@ -26,6 +26,20 @@ const ( RoleMaintainer ) +var ( + roleToString = map[Role]string{ + RoleTagCreator: "tagCreator", + RoleCreator: "creator", + RoleMaintainer: "maintainer", + } + + stringToRole = map[string]Role{ + "tagCreator": RoleTagCreator, + "creator": RoleCreator, + "maintainer": RoleMaintainer, + } +) + type Roles []Role func NewRoles() *Roles { @@ -81,21 +95,27 @@ func (o *Roles) UnmarshalJSON(data []byte) error { return fmt.Errorf("no roles found") } - var r Role - for _, s := range a { - switch s { - case "tagCreator": - r = RoleTagCreator - case "creator": - r = RoleCreator - case "maintainer": - r = RoleMaintainer - default: - return fmt.Errorf("unknown role '%s'", s) + r, ok := stringToRole[s] + if !ok { + return fmt.Errorf("unknown role %q", s) } o = o.Add(r) } return nil } + +func (o Roles) MarshalJSON() ([]byte, error) { + roles := []string{} + + for _, r := range o { + s, ok := roleToString[r] + if !ok { + return nil, fmt.Errorf("unknown role %d", r) + } + roles = append(roles, s) + } + + return json.Marshal(roles) +} diff --git a/comid/role_test.go b/comid/role_test.go index 10f866ed..4479960c 100644 --- a/comid/role_test.go +++ b/comid/role_test.go @@ -138,7 +138,6 @@ func TestRoles_ToCBOR_ok(t *testing.T) { assert.Nil(t, err) assert.Equal(t, tv.expected, actual) - } } @@ -195,7 +194,7 @@ func TestRoles_UnmarshalJSON_fail(t *testing.T) { }, { json: `["blabla"]`, - expectedErr: "unknown role 'blabla'", + expectedErr: `unknown role "blabla"`, }, { json: `"tagCreator"`, diff --git a/comid/svn.go b/comid/svn.go index e5fbddb4..90d7846b 100644 --- a/comid/svn.go +++ b/comid/svn.go @@ -51,14 +51,16 @@ func (o *SVN) UnmarshalCBOR(data []byte) error { return fmt.Errorf("unknown SVN (CBOR: %x)", data) } +type svnJSONRepr struct { + Cmp string `json:"cmp"` + Val int64 `json:"value"` +} + // Supported formats: // { "cmp": "==", "value": 123 } -> SVN // { "cmp": ">=", "value": 123 } -> MinSVN func (o *SVN) UnmarshalJSON(data []byte) error { - s := struct { - Cmp string `json:"cmp"` - Val int64 `json:"value"` - }{} + var s svnJSONRepr if err := json.Unmarshal(data, &s); err != nil { return fmt.Errorf("SVN decoding failure: %w", err) @@ -75,3 +77,20 @@ func (o *SVN) UnmarshalJSON(data []byte) error { return nil } + +func (o SVN) MarshalJSON() ([]byte, error) { + var s svnJSONRepr + + switch t := o.val.(type) { + case TaggedSVN: + s.Cmp = "==" + s.Val = int64(t) + case TaggedMinSVN: + s.Cmp = ">=" + s.Val = int64(t) + default: + return nil, fmt.Errorf("unknown SVN type: %T", t) + } + + return json.Marshal(s) +} diff --git a/comid/test_vars.go b/comid/test_vars.go index e3cbae84..83aa941a 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "encoding/hex" "net" + "regexp" "testing" "github.com/google/uuid" @@ -33,6 +34,11 @@ var ( ) func MustHexDecode(t *testing.T, s string) []byte { + // allow long hex string to be split over multiple lines (with soft or hard + // tab indentation) + m := regexp.MustCompile("[ \t\n]") + s = m.ReplaceAllString(s, "") + data, err := hex.DecodeString(s) if t != nil { require.Nil(t, err) diff --git a/comid/ueid.go b/comid/ueid.go new file mode 100644 index 00000000..765d1b86 --- /dev/null +++ b/comid/ueid.go @@ -0,0 +1,52 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "encoding/json" + "fmt" + + "github.com/veraison/eat" +) + +// UEID is an Unique Entity Identifier +type UEID eat.UEID + +// TaggedUEID is an alias to allow automatic tagging of an UEID type +type TaggedUEID UEID + +func (o UEID) Empty() bool { + return len(o) == 0 +} + +// Valid checks that the target UEID is in one of the defined formats: IMEI, EUI or RAND +func (o UEID) Valid() error { + if err := eat.UEID(o).Validate(); err != nil { + return fmt.Errorf("UEID validation failed: %w", err) + } + return nil +} + +// UnmarshalJSON deserializes the supplied string into the UEID target +func (o *UEID) UnmarshalJSON(data []byte) error { + var b []byte + + if err := json.Unmarshal(data, &b); err != nil { + return err + } + + u := UEID(b) + + if err := u.Valid(); err != nil { + return err + } + + *o = u + + return nil +} + +func (o UEID) MarshalJSON() ([]byte, error) { + return json.Marshal([]byte(o)) +} diff --git a/comid/uuid.go b/comid/uuid.go index cfb94123..c5c78d61 100644 --- a/comid/uuid.go +++ b/comid/uuid.go @@ -41,6 +41,7 @@ func (o UUID) Valid() error { } // UnmarshalJSON deserializes the supplied string into the UUID target +// The UUID string in expected to be in canonical 8-4-4-4-12 format func (o *UUID) UnmarshalJSON(data []byte) error { var s string @@ -57,3 +58,9 @@ func (o *UUID) UnmarshalJSON(data []byte) error { return nil } + +// MarshalJSON serialize the target UUID to a JSON string in canonical +// 8-4-4-4-12 format +func (o UUID) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} diff --git a/corim/cbor.go b/corim/cbor.go index 984dba70..ec15e95f 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -15,6 +15,11 @@ var ( dm, dmError = initCBORDecMode() ) +var ( + CoswidTag = []byte{0xd9, 0x01, 0xf9} // 505() + ComidTag = []byte{0xd9, 0x01, 0xfa} // 506() +) + func corimTags() cbor.TagSet { corimTagsMap := map[uint64]interface{}{ 32: comid.TaggedURI(""), diff --git a/corim/meta.go b/corim/meta.go index 476d9820..45f1ea49 100644 --- a/corim/meta.go +++ b/corim/meta.go @@ -4,6 +4,7 @@ package corim import ( + "encoding/json" "errors" "fmt" "time" @@ -133,3 +134,13 @@ func (o Meta) ToCBOR() ([]byte, error) { func (o *Meta) FromCBOR(data []byte) error { return dm.Unmarshal(data, o) } + +// FromJSON deserializes the supplied JSON data into the target Meta +func (o *Meta) FromJSON(data []byte) error { + return json.Unmarshal(data, o) +} + +// ToJSON serializes the target Meta to JSON +func (o Meta) ToJSON() ([]byte, error) { + return json.Marshal(&o) +} diff --git a/corim/role.go b/corim/role.go index 4d84b2cf..9bfc1276 100644 --- a/corim/role.go +++ b/corim/role.go @@ -4,6 +4,7 @@ package corim import ( + "encoding/json" "errors" "fmt" ) @@ -14,8 +15,21 @@ const ( RoleManifestCreator Role = iota + 1 ) +var ( + stringToRole = map[string]Role{ + "manifestCreator": RoleManifestCreator, + } + roleToString = map[Role]string{ + RoleManifestCreator: "manifestCreator", + } +) + type Roles []Role +func NewRoles() *Roles { + return &Roles{} +} + // Add appends the supplied roles to Roles list. func (o *Roles) Add(roles ...Role) *Roles { if o != nil { @@ -47,3 +61,61 @@ func (o Roles) Valid() error { return nil } + +func (o *Roles) UnmarshalJSON(data []byte) error { + var a []string + + if err := json.Unmarshal(data, &a); err != nil { + return err + } + + for _, s := range a { + r, ok := stringToRole[s] + if !ok { + return fmt.Errorf("unknown role %q", s) + } + o = o.Add(r) + } + + return nil +} + +func (o Roles) MarshalJSON() ([]byte, error) { + roles := []string{} + + for _, r := range o { + s, ok := roleToString[r] + if !ok { + return nil, fmt.Errorf("unknown role %d", r) + } + roles = append(roles, s) + } + + return json.Marshal(roles) +} + +func (o Roles) ToJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, fmt.Errorf("validation failed: %w", err) + } + + data, err := o.MarshalJSON() + if err != nil { + return nil, fmt.Errorf("encoding failed: %w", err) + } + + return data, err +} + +func (o *Roles) FromJSON(data []byte) error { + if err := o.UnmarshalJSON(data); err != nil { + return fmt.Errorf("decoding failed: %w", err) + } + + err := o.Valid() + if err != nil { + return fmt.Errorf("validation failed: %w", err) + } + + return err +} diff --git a/corim/role_test.go b/corim/role_test.go new file mode 100644 index 00000000..66df92b0 --- /dev/null +++ b/corim/role_test.go @@ -0,0 +1,79 @@ +// Copyright 2021 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package corim + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRoles_ToJSON_ok(t *testing.T) { + tvs := []struct { + roles *Roles + expected string + }{ + { + roles: NewRoles().Add(RoleManifestCreator), + expected: `[ "manifestCreator" ]`, + }, + } + + for _, tv := range tvs { + actual, err := tv.roles.ToJSON() + + assert.NoError(t, err) + assert.JSONEq(t, tv.expected, string(actual)) + } +} + +func TestRoles_ToJSON_fail(t *testing.T) { + tvs := []struct { + roles *Roles + expectedErr string + }{ + { + roles: NewRoles(), + expectedErr: "validation failed: empty roles", + }, + } + + for _, tv := range tvs { + _, err := tv.roles.ToJSON() + + assert.EqualError(t, err, tv.expectedErr) + } +} + +func TestRoles_FromJSON_fail(t *testing.T) { + tvs := []struct { + json string + expectedErr string + }{ + { + json: `[]`, + expectedErr: "validation failed: empty roles", + }, + { + json: `["blabla"]`, + expectedErr: `decoding failed: unknown role "blabla"`, + }, + { + json: `[ "manifestCreator", "xyz" ]`, + expectedErr: `decoding failed: unknown role "xyz"`, + }, + { + json: `"tagCreator"`, + expectedErr: "decoding failed: json: cannot unmarshal string into Go value of type []string", + }, + } + + for _, tv := range tvs { + var actual Roles + + err := actual.FromJSON([]byte(tv.json)) + + assert.EqualError(t, err, tv.expectedErr) + } +} diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index 883c5a82..e9269248 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -4,22 +4,16 @@ package corim import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" "fmt" - "reflect" "testing" "time" - "github.com/lestrrat-go/jwx/jwk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cose "github.com/veraison/go-cose" ) var ( - testECKey = `{ + testECKey = []byte(`{ "kty": "EC", "crv": "P-256", "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", @@ -27,42 +21,9 @@ var ( "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", "use": "enc", "kid": "1" - }` + }`) ) -func signerFromJWK(t *testing.T, j string) *cose.Signer { - ks, err := jwk.ParseString(j) - require.Nil(t, err) - - k, ok := ks.Get(0) - require.True(t, ok) - - var key crypto.PrivateKey - - err = k.Raw(&key) - require.Nil(t, err) - - var crv elliptic.Curve - var alg *cose.Algorithm - - switch v := key.(type) { - case *ecdsa.PrivateKey: - crv = v.Curve - if crv == elliptic.P256() { - alg = cose.ES256 - break - } - require.True(t, false, "unknown elliptic curve %v", crv) - default: - require.True(t, false, "unknown private key type %v", reflect.TypeOf(key)) - } - - s, err := cose.NewSignerFromKey(alg, key) - require.Nil(t, err) - - return s -} - func TestSignedCorim_FromCOSE_ok(t *testing.T) { /* 18( @@ -293,7 +254,8 @@ func metaGood(t *testing.T) *Meta { } func TestSignedCorim_SignVerify_ok(t *testing.T) { - signer := signerFromJWK(t, testECKey) + signer, err := SignerFromJWK(testECKey) + require.NoError(t, err) var SignedCorimIn SignedCorim @@ -315,7 +277,8 @@ func TestSignedCorim_SignVerify_ok(t *testing.T) { } func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { - signer := signerFromJWK(t, testECKey) + signer, err := SignerFromJWK(testECKey) + require.NoError(t, err) var SignedCorimIn SignedCorim @@ -342,7 +305,8 @@ func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { } func TestSignedCorim_Sign_fail_bad_corim(t *testing.T) { - signer := signerFromJWK(t, testECKey) + signer, err := SignerFromJWK(testECKey) + require.NoError(t, err) var SignedCorimIn SignedCorim @@ -351,7 +315,7 @@ func TestSignedCorim_Sign_fail_bad_corim(t *testing.T) { SignedCorimIn.UnsignedCorim = *emptyCorim - _, err := SignedCorimIn.Sign(signer) + _, err = SignedCorimIn.Sign(signer) assert.EqualError(t, err, "failed validation of unsigned CoRIM: empty id") } diff --git a/corim/signer.go b/corim/signer.go new file mode 100644 index 00000000..972bcf78 --- /dev/null +++ b/corim/signer.go @@ -0,0 +1,51 @@ +package corim + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "errors" + "fmt" + "reflect" + + "github.com/lestrrat-go/jwx/jwk" + cose "github.com/veraison/go-cose" +) + +func SignerFromJWK(j []byte) (*cose.Signer, error) { + var ( + err error + ks jwk.Set + k jwk.Key + ok bool + pkey crypto.PrivateKey + crv elliptic.Curve + alg *cose.Algorithm + ) + + if ks, err = jwk.ParseString(string(j)); err != nil { + return nil, err + } + + if k, ok = ks.Get(0); !ok { + return nil, errors.New("no key found at slot 0") + } + + if err = k.Raw(&pkey); err != nil { + return nil, err + } + + switch v := pkey.(type) { + case *ecdsa.PrivateKey: + crv = v.Curve + if crv == elliptic.P256() { + alg = cose.ES256 + break + } + return nil, fmt.Errorf("unknown elliptic curve %v", crv) + default: + return nil, fmt.Errorf("unknown private key type %v", reflect.TypeOf(pkey)) + } + + return cose.NewSignerFromKey(alg, pkey) +} diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 8030e173..c77bf6b9 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -62,10 +62,7 @@ func (o *UnsignedCorim) AddComid(c comid.Comid) *UnsignedCorim { return nil } - // d9 01fa # tag(506) - comidTag := []byte{0xd9, 0x01, 0xfa} - - taggedComid := append(comidTag, comidCBOR...) + taggedComid := append(ComidTag, comidCBOR...) o.Tags = append(o.Tags, taggedComid) } @@ -86,10 +83,7 @@ func (o *UnsignedCorim) AddCoswid(c swid.SoftwareIdentity) *UnsignedCorim { return nil } - // d9 01f9 # tag(505) - coswidTag := []byte{0xd9, 0x01, 0xf9} - - taggedCoswid := append(coswidTag, coswidCBOR...) + taggedCoswid := append(CoswidTag, coswidCBOR...) o.Tags = append(o.Tags, taggedCoswid) } diff --git a/go.mod b/go.mod index 2769f602..96b8e786 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,13 @@ require ( github.com/fxamacker/cbor/v2 v2.3.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx v1.2.6 + github.com/spf13/afero v1.6.0 + github.com/spf13/cobra v1.2.1 + github.com/spf13/viper v1.9.0 github.com/stretchr/testify v1.7.0 github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff github.com/veraison/go-cose v0.0.0-20201125131510-de93f6091ed4 - github.com/veraison/swid v0.0.1-beta.5 + github.com/veraison/swid v0.0.1-beta.6 + golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect + golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index c33881da..e6bfb2f2 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,221 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.2.1-0.20200429214022-fc263b46c618/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/fxamacker/cbor/v2 v2.3.0 h1:aM45YGMctNakddNNAezPxDUpv38j44Abh+hifNuqXik= github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/goccy/go-json v0.7.6 h1:H0wq4jppBQ+9222sk5+hPLL25abZQiRuQ6YPnjO9c+A= github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= @@ -25,57 +229,504 @@ github.com/lestrrat-go/jwx v1.2.6 h1:XAgfuHaOB7fDZ/6WhVgl8K89af768dU+3Nx4DlTbLIk github.com/lestrrat-go/jwx v1.2.6/go.mod h1:tJuGuAI3LC71IicTx82Mz1n3w9woAs2bYJZpkjJQ5aU= github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk= +github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff h1:r6I2eJL/z8dp5flsQIKHMeDjyV6UO8If3MaVBLvTjF4= github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff/go.mod h1:+kxt8iuFiVvKRs2VQ1Ho7bbAScXAB/kHFFuP5Biw19I= github.com/veraison/go-cose v0.0.0-20201125131510-de93f6091ed4 h1:YCSAzEYRLKSUauy/lrUSFcJB/8vOZaIeH7eDNXqffX0= github.com/veraison/go-cose v0.0.0-20201125131510-de93f6091ed4/go.mod h1:sjLU/8dYHRJj3RWtKLJUbPLoByKdV7nnegaTBgQ+9XA= -github.com/veraison/swid v0.0.1-beta.5 h1:9YiH5qNNNNFCjeifM42xizgXEAxvhVBh7B+dCwnek6o= -github.com/veraison/swid v0.0.1-beta.5/go.mod h1:d5jt76uMNbTfQ+f2qU4Lt8RvWOTsv6PFgstIM1QdMH0= +github.com/veraison/swid v0.0.1-beta.6 h1:ysDyCOPwGyjiBnhAM+/kgTEcc/PWieIbUQJOjnSTK48= +github.com/veraison/swid v0.0.1-beta.6/go.mod h1:d5jt76uMNbTfQ+f2qU4Lt8RvWOTsv6PFgstIM1QdMH0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c= +gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=