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=