Repository: veraison/cocli
Pull request #33: Updates the sign cmd to add X.509 certificate chain support to CoRIM signing
Author: login: Akhilesh-max
Created: 2025-03-16T08:27:30Z | Merged: 2025-03-19T10:00:37Z

Implements certificate chain embedding in COSE Sign1 headers when signing CoRIM files.
Fixes  #32 .

Linked issue #32: Add support for Signed CoRIM
Add support for Signed CoRIM
Linked issue comments: user: login: yogeshbdeshpande
created_at: 2025-01-23T14:01:59Z
body: The `cocli` tool in its command set, also has a command to sign a CoRIM.  This is done using cocli cmd `corimSign`

This command needs to be updated to supply the required certificate chain in the COSE protected header. 

This is similar to other information like Meta, done on
https://github.com/veraison/cocli/blob/main/cmd/corimSign.go#L82

The supplied information could be a single or a chain of X.509 certificates,
to be correctly be populated in the COSE Unprotected Header.
author_association: CONTRIBUTOR

Diff:
diff --git a/cmd/corimCreate.go b/cmd/corimCreate.go
index c3bfd56..bf23a40 100644
--- a/cmd/corimCreate.go
+++ b/cmd/corimCreate.go
@@ -157,7 +157,7 @@ func corimTemplateToCBOR(tmplFile string, comidFiles, coswidFiles, cotsFiles []s
 			return "", fmt.Errorf("error loading CoMID from %s: %w", comidFile, err)
 		}
 
-		if c.AddComid(m) == nil {
+		if c.AddComid(&m) == nil {
 			return "", fmt.Errorf(
 				"error adding CoMID from %s (check its validity using the %q sub-command)",
 				comidFile, "comid validate",
@@ -182,7 +182,7 @@ func corimTemplateToCBOR(tmplFile string, comidFiles, coswidFiles, cotsFiles []s
 			return "", fmt.Errorf("error loading CoSWID from %s: %w", coswidFile, err)
 		}
 
-		if c.AddCoswid(s) == nil {
+		if c.AddCoswid(&s) == nil {
 			return "", fmt.Errorf("error adding CoSWID from %s", coswidFile)
 		}
 	}
@@ -204,7 +204,7 @@ func corimTemplateToCBOR(tmplFile string, comidFiles, coswidFiles, cotsFiles []s
 			return "", fmt.Errorf("error loading CoTS from %s: %w", cotsFile, err)
 		}
 
-		if c.AddCots(t) == nil {
+		if c.AddCots(&t) == nil {
 			return "", fmt.Errorf("error adding CoTS from %s", cotsFile)
 		}
 	}
diff --git a/cmd/corimExtract.go b/cmd/corimExtract.go
index 3761ee0..bf8a627 100644
--- a/cmd/corimExtract.go
+++ b/cmd/corimExtract.go
@@ -98,25 +98,26 @@ func extract(signedCorimFile string, outputDir *string) error {
 		// split tag from data
 		cborTag, cborData := e[:3], e[3:]
 
-		if bytes.Equal(cborTag, corim.ComidTag) {
+		switch {
+		case 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) {
+		case 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 if bytes.Equal(cborTag, cots.CotsTag) {
+		case bytes.Equal(cborTag, cots.CotsTag):
 			outputFile = filepath.Join(baseDir, fmt.Sprintf("%06d-cots.cbor", i))
 
 			if err = afero.WriteFile(fs, outputFile, cborData, 0644); err != nil {
 				fmt.Printf(">> error saving CoTS tag at index %d: %v\n", i, err)
 			}
-		} else {
+		default:
 			fmt.Printf(">> unmatched CBOR tag: %x\n", cborTag)
 		}
 	}
diff --git a/cmd/corimSign.go b/cmd/corimSign.go
index 74a0c98..1bb6667 100644
--- a/cmd/corimSign.go
+++ b/cmd/corimSign.go
@@ -14,10 +14,12 @@ import (
 )
 
 var (
-	corimSignCorimFile  *string
-	corimSignKeyFile    *string
-	corimSignOutputFile *string
-	corimSignMetaFile   *string
+	corimSignCorimFile         *string
+	corimSignKeyFile           *string
+	corimSignOutputFile        *string
+	corimSignMetaFile          *string
+	corimSignCertFile          *string
+	corimSignIntermediateCerts *string
 )
 
 var corimSignCmd = NewCorimSignCmd()
@@ -28,15 +30,24 @@ func NewCorimSignCmd() *cobra.Command {
 		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.
-	
-	  cocli corim sign  --file=unsigned-corim.cbor \
-					--key=key.jwk \
-					--meta=meta.json \
-					--output=signed-corim.cbor
-	`,
+    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.
+    
+      cocli corim sign  --file=unsigned-corim.cbor \
+                    --key=key.jwk \
+                    --meta=meta.json \
+                    --output=signed-corim.cbor
+                    
+    Optionally include the signing certificate and certificate chain in the COSE header:
+    
+      cocli corim sign  --file=unsigned-corim.cbor \
+                    --key=key.jwk \
+                    --meta=meta.json \
+                    --cert=signing-cert.der \
+                    --intermediates=intermediate-certs.der \
+                    --output=signed-corim.cbor
+    `,
 
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if err := checkCorimSignArgs(); err != nil {
@@ -45,7 +56,7 @@ func NewCorimSignCmd() *cobra.Command {
 
 			// checkCorimSignArgs makes sure corimSignCorimFile is not nil
 			coseFile, err := sign(*corimSignCorimFile, *corimSignKeyFile,
-				*corimSignMetaFile, corimSignOutputFile)
+				*corimSignMetaFile, corimSignOutputFile, corimSignCertFile, corimSignIntermediateCerts)
 			if err != nil {
 				return err
 			}
@@ -59,6 +70,8 @@ func NewCorimSignCmd() *cobra.Command {
 	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")
+	corimSignCertFile = cmd.Flags().StringP("cert", "c", "", "signing certificate in DER format")
+	corimSignIntermediateCerts = cmd.Flags().String("intermediates", "", "intermediate certificates in DER format")
 
 	return cmd
 }
@@ -79,12 +92,14 @@ func checkCorimSignArgs() error {
 	return nil
 }
 
-func sign(unsignedCorimFile, keyFile, metaFile string, outputFile *string) (string, error) {
+func sign(unsignedCorimFile, keyFile, metaFile string, outputFile, certFile, intermediatesFile *string) (string, error) {
 	var (
 		unsignedCorimCBOR []byte
 		signedCorimCBOR   []byte
 		metaJSON          []byte
 		keyJWK            []byte
+		certDER           []byte
+		intermediatesDER  []byte
 		err               error
 		signedCorimFile   string
 		c                 corim.UnsignedCorim
@@ -129,6 +144,33 @@ func sign(unsignedCorimFile, keyFile, metaFile string, outputFile *string) (stri
 		Meta:          m,
 	}
 
+	// Add signing certificate if provided
+	if certFile != nil && *certFile != "" {
+		if certDER, err = afero.ReadFile(fs, *certFile); err != nil {
+			return "", fmt.Errorf("error loading signing certificate from %s: %w", *certFile, err)
+		}
+
+		if err = s.AddSigningCert(certDER); err != nil {
+			return "", fmt.Errorf("error adding signing certificate: %w", err)
+		}
+	}
+
+	// Add intermediate certificates if provided
+	if intermediatesFile != nil && *intermediatesFile != "" {
+		// Ensure signing certificate was provided
+		if certFile == nil || *certFile == "" {
+			return "", fmt.Errorf("cannot add intermediate certificates without a signing certificate")
+		}
+
+		if intermediatesDER, err = afero.ReadFile(fs, *intermediatesFile); err != nil {
+			return "", fmt.Errorf("error loading intermediate certificates from %s: %w", *intermediatesFile, err)
+		}
+
+		if err = s.AddIntermediateCerts(intermediatesDER); err != nil {
+			return "", fmt.Errorf("error adding intermediate certificates: %w", err)
+		}
+	}
+
 	signedCorimCBOR, err = s.Sign(signer)
 	if err != nil {
 		return "", fmt.Errorf("error signing CoRIM: %w", err)
diff --git a/cmd/corimSign_test.go b/cmd/corimSign_test.go
index f8f78ec..59e6aa6 100644
--- a/cmd/corimSign_test.go
+++ b/cmd/corimSign_test.go
@@ -262,3 +262,191 @@ func Test_CorimSignCmd_ok_with_custom_output_file(t *testing.T) {
 	_, err = fs.Stat("my-signed-corim.cbor")
 	assert.NoError(t, err)
 }
+
+func Test_CorimSignCmd_with_signing_cert(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--cert=cert.der",
+	}
+	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 = afero.WriteFile(fs, "cert.der", testSigningCertificate, 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_with_signing_cert_and_intermediates(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--cert=cert.der",
+		"--intermediates=intermediates.der",
+	}
+	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 = afero.WriteFile(fs, "cert.der", testSigningCertificate, 0644)
+	require.NoError(t, err)
+	err = afero.WriteFile(fs, "intermediates.der", testIntermediateCerts, 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_invalid_cert_file(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--cert=invalid-cert.der",
+	}
+	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)
+	// Write an invalid certificate (just some random bytes that aren't a valid DER certificate)
+	err = afero.WriteFile(fs, "invalid-cert.der", []byte{0x30, 0x03, 0x02, 0x01, 0x01}, 0644)
+	require.NoError(t, err)
+
+	err = cmd.Execute()
+	assert.ErrorContains(t, err, "error adding signing certificate")
+}
+
+func Test_CorimSignCmd_invalid_intermediates_file(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--cert=cert.der",
+		"--intermediates=invalid-intermediates.der",
+	}
+	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 = afero.WriteFile(fs, "cert.der", testSigningCertificate, 0644)
+	require.NoError(t, err)
+	// Write invalid intermediate certificates
+	err = afero.WriteFile(fs, "invalid-intermediates.der", []byte{0x30, 0x03, 0x02, 0x01, 0x02}, 0644)
+	require.NoError(t, err)
+
+	err = cmd.Execute()
+	assert.ErrorContains(t, err, "error adding intermediate certificates")
+}
+
+func Test_CorimSignCmd_intermediates_without_signing_cert(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--intermediates=intermediates.der",
+	}
+	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 = afero.WriteFile(fs, "intermediates.der", testIntermediateCerts, 0644)
+	require.NoError(t, err)
+
+	err = cmd.Execute()
+	assert.EqualError(t, err, "cannot add intermediate certificates without a signing certificate")
+}
+
+func Test_CorimSignCmd_nonexistent_cert_file(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--cert=nonexistent.der",
+	}
+	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.EqualError(t, err, "error loading signing certificate from nonexistent.der: open nonexistent.der: file does not exist")
+}
+
+func Test_CorimSignCmd_nonexistent_intermediates_file(t *testing.T) {
+	cmd := NewCorimSignCmd()
+
+	args := []string{
+		"--file=ok.cbor",
+		"--key=ok.jwk",
+		"--meta=ok.json",
+		"--cert=cert.der",
+		"--intermediates=nonexistent.der",
+	}
+	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 = afero.WriteFile(fs, "cert.der", testSigningCertificate, 0644)
+	require.NoError(t, err)
+
+	err = cmd.Execute()
+	assert.EqualError(t, err, "error loading intermediate certificates from nonexistent.der: open nonexistent.der: file does not exist")
+}
diff --git a/cmd/cotsCreate.go b/cmd/cotsCreate.go
index aea8ebb..4b2109f 100644
--- a/cmd/cotsCreate.go
+++ b/cmd/cotsCreate.go
@@ -199,7 +199,7 @@ func ctsTemplateToCBOR(language string, tagID string, genUUID bool, uuidStr stri
 		if err = permClaims.FromJSON(permClaimsData); err != nil {
 			return "", fmt.Errorf("error decoding template from %s: %w", permClaimsFile, err)
 		}
-		cts.AddPermClaims(permClaims)
+		cts.AddPermClaims(&permClaims)
 	}
 	if exclClaimsFile != "" {
 		if exclClaimsData, err = afero.ReadFile(fs, exclClaimsFile); err != nil {
@@ -209,9 +209,9 @@ func ctsTemplateToCBOR(language string, tagID string, genUUID bool, uuidStr stri
 		if err = exclClaims.FromJSON(exclClaimsData); err != nil {
 			return "", fmt.Errorf("error decoding template from %s: %w", exclClaimsFile, err)
 		}
-		cts.AddExclClaims(exclClaims)
+		cts.AddExclClaims(&exclClaims)
 	}
-	if 0 != len(purposes) {
+	if len(purposes) > 0 {
 		cts.Purposes = purposes
 	}
 
@@ -227,13 +227,13 @@ func ctsTemplateToCBOR(language string, tagID string, genUUID bool, uuidStr stri
 		if err != nil {
 			return "", fmt.Errorf("error loading TA from %s: %w", taFile, err)
 		}
-		if ".der" == filepath.Ext(taFile) {
+		if filepath.Ext(taFile) == ".der" {
 			trustAnchor.Format = cots.TaFormatCertificate
 		}
-		if ".spki" == filepath.Ext(taFile) {
+		if filepath.Ext(taFile) == ".spki" {
 			trustAnchor.Format = cots.TaFormatSubjectPublicKeyInfo
 		}
-		if ".ta" == filepath.Ext(taFile) {
+		if filepath.Ext(taFile) == ".ta" {
 			trustAnchor.Format = cots.TaFormatTrustAnchorInfo
 		}
 		trustAnchor.Data = tadata
diff --git a/cmd/test_vars.go b/cmd/test_vars.go
index 99b6ee9..63b3f69 100644
--- a/cmd/test_vars.go
+++ b/cmd/test_vars.go
@@ -43,6 +43,12 @@ var (
 	//go:embed testcases/ec-p256.jwk
 	testECKey []byte
 
+	//go:embed testcases/test-certs/endEntity.der
+	testSigningCertificate []byte
+
+	//go:embed testcases/test-certs/intermediateCA.der
+	testIntermediateCerts []byte
+
 	//go:embed testcases/signed-corim-valid.cbor
 	testSignedCorimValid []byte
 
diff --git a/cmd/testcases/test-certs/endEntity.der b/cmd/testcases/test-certs/endEntity.der
new file mode 100644
index 0000000..0d1c6ce
Binary files /dev/null and b/cmd/testcases/test-certs/endEntity.der differ
diff --git a/cmd/testcases/test-certs/intermediateCA.der b/cmd/testcases/test-certs/intermediateCA.der
new file mode 100644
index 0000000..dfc1c68
Binary files /dev/null and b/cmd/testcases/test-certs/intermediateCA.der differ
diff --git a/go.mod b/go.mod
index 1a993d3..82c4113 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,8 @@
 module github.com/veraison/cocli
 
-go 1.22
+go 1.22.0
+
+toolchain go1.22.10
 
 require (
 	github.com/golang/mock v1.6.0
@@ -11,7 +13,7 @@ require (
 	github.com/spf13/viper v1.9.0
 	github.com/stretchr/testify v1.9.0
 	github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45
-	github.com/veraison/corim v1.1.3-0.20241003171039-fe09de9f3764
+	github.com/veraison/corim v1.1.3-0.20250307044607-0bbdd6c78526
 	github.com/veraison/go-cose v1.3.0
 	github.com/veraison/swid v1.1.1-0.20230911094910-8ffdd07a22ca
 )
@@ -42,11 +44,11 @@ require (
 	github.com/subosito/gotenv v1.2.0 // indirect
 	github.com/veraison/eat v0.0.0-20210331113810-3da8a4dd42ff // indirect
 	github.com/x448/float16 v0.8.4 // indirect
-	golang.org/x/crypto v0.26.0 // indirect
+	golang.org/x/crypto v0.31.0 // indirect
 	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/oauth2 v0.11.0 // indirect
-	golang.org/x/sys v0.23.0 // indirect
-	golang.org/x/text v0.17.0 // indirect
+	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.33.0 // indirect
 	gopkg.in/ini.v1 v1.63.2 // indirect
diff --git a/go.sum b/go.sum
index 95dd6cc..52342a3 100644
--- a/go.sum
+++ b/go.sum
@@ -321,8 +321,8 @@ 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/apiclient v0.3.1-0.20240807160142-9141ad363e45 h1:o+gCzGtusXZOOXuJavzvpraSk8ify4U60Aq9r/4vOho=
 github.com/veraison/apiclient v0.3.1-0.20240807160142-9141ad363e45/go.mod h1:LCXFZ3D/tJ3HLAOHUg8bnAKGvgTl53e1ntwdwjVbQ5A=
-github.com/veraison/corim v1.1.3-0.20241003171039-fe09de9f3764 h1:48GvCJSVsBDaqIiK0m+h4D9rhAwLlzias+F5oLId/Fg=
-github.com/veraison/corim v1.1.3-0.20241003171039-fe09de9f3764/go.mod h1:Wj3a6bSo7+3peVGjwGayHDALILh4PHMngDhgBYUbVLk=
+github.com/veraison/corim v1.1.3-0.20250307044607-0bbdd6c78526 h1:SEMbaI+cPmtUZvutz8T1EgPYkBM+5iwOEhCAJioDqLI=
+github.com/veraison/corim v1.1.3-0.20250307044607-0bbdd6c78526/go.mod h1:ih8kOpsI3+2iy8IvHkD6xSNryfK3oe9c05nlBE78BV0=
 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 v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7rk=
@@ -361,8 +361,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
-golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 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=
@@ -526,8 +526,8 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
 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.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
-golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 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=
@@ -537,8 +537,8 @@ 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 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=


Reviews:
user: login: thomas-fossati
body: I took a quick look while you are working on the tests.

I have left a couple of tiny comments inline, but in general it looks good.

BTW, thanks for fixing a number of lint warnings.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T13:07:12Z
user: login: Akhilesh-max
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T19:20:21Z
user: login: Akhilesh-max
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T19:20:23Z
user: login: Akhilesh-max
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T19:20:54Z
user: login: yogeshbdeshpande
body: LGTM! Thanks for the changes
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T09:24:58Z
user: login: thomas-fossati
body: Nice job!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T10:00:21Z

Review comments:
diff_hunk: @@ -14,10 +14,12 @@ import (
 )
 
 var (
-	corimSignCorimFile  *string
-	corimSignKeyFile    *string
-	corimSignOutputFile *string
-	corimSignMetaFile   *string
+	corimSignCorimFile         *string
+	corimSignKeyFile           *string
+	corimSignOutputFile        *string
+	corimSignMetaFile          *string
+	corimSignCertFile          *string
+	corimSignIntermediatesCert *string
path: cmd/corimSign.go
user: login: thomas-fossati
body: ```suggestion
	corimSignIntermediateCerts *string
```
created_at: 2025-03-18T12:58:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,15 +30,24 @@ func NewCorimSignCmd() *cobra.Command {
 		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.
-	
-	  cocli corim sign  --file=unsigned-corim.cbor \
-					--key=key.jwk \
-					--meta=meta.json \
-					--output=signed-corim.cbor
-	`,
+    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.
+    
+      cocli corim sign  --file=unsigned-corim.cbor \
+                    --key=key.jwk \
+                    --meta=meta.json \
+                    --output=signed-corim.cbor
+                    
+    Optionally include a certificate chain in the signature:
path: cmd/corimSign.go
user: login: thomas-fossati
body: ```suggestion
    Optionally include the signing certificate and certificate chain in the COSE header:
```
created_at: 2025-03-18T12:59:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -129,6 +144,28 @@ func sign(unsignedCorimFile, keyFile, metaFile string, outputFile *string) (stri
 		Meta:          m,
 	}
 
+	// Add signing certificate if provided
+	if certFile != nil && *certFile != "" {
+		if certDER, err = afero.ReadFile(fs, *certFile); err != nil {
+			return "", fmt.Errorf("error loading signing certificate from %s: %w", *certFile, err)
+		}
+
+		if err = s.AddSigningCert(certDER); err != nil {
+			return "", fmt.Errorf("error adding signing certificate: %w", err)
+		}
+	}
+
+	// Add intermediate certificates if provided
+	if intermediatesFile != nil && *intermediatesFile != "" {
+		if intermediatesDER, err = afero.ReadFile(fs, *intermediatesFile); err != nil {
+			return "", fmt.Errorf("error loading intermediate certificates from %s: %w", *intermediatesFile, err)
+		}
+
+		if err = s.AddIntermediateCerts(intermediatesDER); err != nil {
+			return "", fmt.Errorf("error adding intermediate certificates: %w", err)
+		}
+	}
+
path: cmd/corimSign.go
user: login: thomas-fossati
body: Here we should also check that if the intermediate certs have been supplied, the signing cert was also supplied.
Even though https://github.com/veraison/corim/pull/178 will make sure the anomaly is caught and dealt with, it is better UX to catch and report it here.
created_at: 2025-03-18T13:05:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -14,10 +14,12 @@ import (
 )
 
 var (
-	corimSignCorimFile  *string
-	corimSignKeyFile    *string
-	corimSignOutputFile *string
-	corimSignMetaFile   *string
+	corimSignCorimFile         *string
+	corimSignKeyFile           *string
+	corimSignOutputFile        *string
+	corimSignMetaFile          *string
+	corimSignCertFile          *string
+	corimSignIntermediatesCert *string
path: cmd/corimSign.go
user: login: Akhilesh-max
body: Done
created_at: 2025-03-18T19:20:20Z
author_association: MEMBER
diff_hunk: @@ -28,15 +30,24 @@ func NewCorimSignCmd() *cobra.Command {
 		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.
-	
-	  cocli corim sign  --file=unsigned-corim.cbor \
-					--key=key.jwk \
-					--meta=meta.json \
-					--output=signed-corim.cbor
-	`,
+    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.
+    
+      cocli corim sign  --file=unsigned-corim.cbor \
+                    --key=key.jwk \
+                    --meta=meta.json \
+                    --output=signed-corim.cbor
+                    
+    Optionally include a certificate chain in the signature:
path: cmd/corimSign.go
user: login: Akhilesh-max
body: Done
created_at: 2025-03-18T19:20:23Z
author_association: MEMBER
diff_hunk: @@ -129,6 +144,28 @@ func sign(unsignedCorimFile, keyFile, metaFile string, outputFile *string) (stri
 		Meta:          m,
 	}
 
+	// Add signing certificate if provided
+	if certFile != nil && *certFile != "" {
+		if certDER, err = afero.ReadFile(fs, *certFile); err != nil {
+			return "", fmt.Errorf("error loading signing certificate from %s: %w", *certFile, err)
+		}
+
+		if err = s.AddSigningCert(certDER); err != nil {
+			return "", fmt.Errorf("error adding signing certificate: %w", err)
+		}
+	}
+
+	// Add intermediate certificates if provided
+	if intermediatesFile != nil && *intermediatesFile != "" {
+		if intermediatesDER, err = afero.ReadFile(fs, *intermediatesFile); err != nil {
+			return "", fmt.Errorf("error loading intermediate certificates from %s: %w", *intermediatesFile, err)
+		}
+
+		if err = s.AddIntermediateCerts(intermediatesDER); err != nil {
+			return "", fmt.Errorf("error adding intermediate certificates: %w", err)
+		}
+	}
+
path: cmd/corimSign.go
user: login: Akhilesh-max
body: My bad, missed that. 
Updated.
created_at: 2025-03-18T19:20:54Z
author_association: MEMBER

PR comments:
user: login: Akhilesh-max
created_at: 2025-03-16T09:19:20Z
body: Hi @yogeshbdeshpande,
Could you please take a look at dependency update? I'm still working on the associated tests.

Thank you.
author_association: MEMBER
user: login: yogeshbdeshpande
created_at: 2025-03-18T11:08:33Z
body: @Akhilesh-max : Please add tests, overall the PR for functional changes is coming nicely!
author_association: CONTRIBUTOR
user: login: Akhilesh-max
created_at: 2025-03-18T19:22:07Z
body: @yogeshbdeshpande @thomas-fossati 
I've added the tests. Please take a look when get a chance.

Thanks!
author_association: MEMBER