From 2e067332cc39a566931e072d096896faa8790498 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 26 Jul 2024 15:10:10 +0800 Subject: [PATCH 01/11] updated timestamp countersignature under inspect Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 78 +++++++++++++++++++++------- test/e2e/suite/command/inspect.go | 84 +++++++++++++++++++++++-------- 2 files changed, 122 insertions(+), 40 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index df91dbea0..de543a56e 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -15,7 +15,7 @@ package main import ( "crypto/sha256" - b64 "encoding/base64" + "crypto/x509" "encoding/hex" "errors" "fmt" @@ -33,6 +33,7 @@ import ( "github.com/notaryproject/notation/internal/envelope" "github.com/notaryproject/notation/internal/ioutil" "github.com/notaryproject/notation/internal/tree" + "github.com/notaryproject/tspclient-go" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/spf13/cobra" ) @@ -57,7 +58,7 @@ type signatureOutput struct { SignatureAlgorithm string `json:"signatureAlgorithm"` SignedAttributes map[string]string `json:"signedAttributes"` UserDefinedAttributes map[string]string `json:"userDefinedAttributes"` - UnsignedAttributes map[string]string `json:"unsignedAttributes"` + UnsignedAttributes map[string]any `json:"unsignedAttributes"` Certificates []certificateOutput `json:"certificates"` SignedArtifact ocispec.Descriptor `json:"signedArtifact"` } @@ -69,6 +70,11 @@ type certificateOutput struct { Expiry string `json:"expiry"` } +type timestampOutput struct { + Timestamp string `json:"timestamp"` + TimestampCertificates []certificateOutput `json:"timestampCertificates"` +} + func inspectCommand(opts *inspectOpts) *cobra.Command { if opts == nil { opts = &inspectOpts{} @@ -175,14 +181,21 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { return nil } + unsignedAttributes, err := getUnsignedAttributes(opts.outputFormat, envelopeContent) + if err != nil { + logSkippedSignature(sigManifestDesc, err) + skippedSignatures = true + return nil + } + sig := signatureOutput{ MediaType: sigDesc.MediaType, Digest: sigManifestDesc.Digest.String(), SignatureAlgorithm: string(signatureAlgorithm), SignedAttributes: getSignedAttributes(opts.outputFormat, envelopeContent), UserDefinedAttributes: signedArtifactDesc.Annotations, - UnsignedAttributes: getUnsignedAttributes(envelopeContent), - Certificates: getCertificates(opts.outputFormat, envelopeContent), + UnsignedAttributes: unsignedAttributes, + Certificates: getCertificates(opts.outputFormat, envelopeContent.SignerInfo.CertificateChain), SignedArtifact: *signedArtifactDesc, } @@ -235,18 +248,34 @@ func getSignedAttributes(outputFormat string, envContent *signature.EnvelopeCont return signedAttributes } -func getUnsignedAttributes(envContent *signature.EnvelopeContent) map[string]string { - unsignedAttributes := map[string]string{} +func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) (map[string]any, error) { + unsignedAttributes := map[string]any{} if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { - unsignedAttributes["timestampSignature"] = b64.StdEncoding.EncodeToString(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) + signedToken, err := tspclient.ParseSignedToken(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) + if err != nil { + return nil, fmt.Errorf("failed to parse timestamp countersignature with error: %w", err) + } + info, err := signedToken.Info() + if err != nil { + return nil, fmt.Errorf("failed to get the timestamp TSTInfo with error: %w", err) + } + timestamp, err := info.Validate(envContent.SignerInfo.Signature) + if err != nil { + return nil, fmt.Errorf("failed to get timestamp from timestamp countersignature with error: %w", err) + } + certificates := getCertificates(outputFormat, signedToken.Certificates) + unsignedAttributes["timestampSignature"] = timestampOutput{ + Timestamp: timestamp.Format(time.RFC3339), + TimestampCertificates: certificates, + } } if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent } - return unsignedAttributes + return unsignedAttributes, nil } func formatTimestamp(outputFormat string, t time.Time) string { @@ -258,10 +287,10 @@ func formatTimestamp(outputFormat string, t time.Time) string { } } -func getCertificates(outputFormat string, envContent *signature.EnvelopeContent) []certificateOutput { +func getCertificates(outputFormat string, certChain []*x509.Certificate) []certificateOutput { certificates := []certificateOutput{} - for _, cert := range envContent.SignerInfo.CertificateChain { + for _, cert := range certChain { h := sha256.Sum256(cert.Raw) fingerprint := strings.ToLower(hex.EncodeToString(h[:])) @@ -304,16 +333,19 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { addMapToTree(userDefinedAttributesNode, signature.UserDefinedAttributes) unsignedAttributesNode := sigNode.Add("unsigned attributes") - addMapToTree(unsignedAttributesNode, signature.UnsignedAttributes) - - certListNode := sigNode.Add("certificates") - for _, cert := range signature.Certificates { - certNode := certListNode.AddPair("SHA256 fingerprint", cert.SHA256Fingerprint) - certNode.AddPair("issued to", cert.IssuedTo) - certNode.AddPair("issued by", cert.IssuedBy) - certNode.AddPair("expiry", cert.Expiry) + for k, v := range signature.UnsignedAttributes { + switch value := v.(type) { + case string: + unsignedAttributesNode.AddPair(k, value) + case timestampOutput: + timestampNode := unsignedAttributesNode.Add("timestampSignature") + timestampNode.AddPair("timestamp", value.Timestamp) + addCertificatesToTree(timestampNode, "timestampCertificates", value.TimestampCertificates) + } } + addCertificatesToTree(sigNode, "certificates", signature.Certificates) + artifactNode := sigNode.Add("signed artifact") artifactNode.AddPair("media type", signature.SignedArtifact.MediaType) artifactNode.AddPair("digest", signature.SignedArtifact.Digest.String()) @@ -333,3 +365,13 @@ func addMapToTree(node *tree.Node, m map[string]string) { node.Add("(empty)") } } + +func addCertificatesToTree(node *tree.Node, name string, certs []certificateOutput) { + certListNode := node.Add(name) + for _, cert := range certs { + certNode := certListNode.AddPair("SHA256 fingerprint", cert.SHA256Fingerprint) + certNode.AddPair("issued to", cert.IssuedTo) + certNode.AddPair("issued by", cert.IssuedBy) + certNode.AddPair("expiry", cert.Expiry) + } +} diff --git a/test/e2e/suite/command/inspect.go b/test/e2e/suite/command/inspect.go index 49ba33e77..e8f570b72 100644 --- a/test/e2e/suite/command/inspect.go +++ b/test/e2e/suite/command/inspect.go @@ -14,34 +14,64 @@ package command import ( + "path/filepath" + . "github.com/notaryproject/notation/test/e2e/internal/notation" "github.com/notaryproject/notation/test/e2e/internal/utils" . "github.com/notaryproject/notation/test/e2e/suite/common" . "github.com/onsi/ginkgo/v2" ) -var inspectSuccessfully = []string{ - "└── application/vnd.cncf.notary.signature", - "└── sha256:", - "├── media type:", - "├── signature algorithm:", - "├── signed attributes", - "signingTime:", - "signingScheme:", - "├── user defined attributes", - "│ └── (empty)", - "├── unsigned attributes", - "│ └── signingAgent: Notation/", - "├── certificates", - "│ └── SHA256 fingerprint:", - "issued to:", - "issued by:", - "expiry:", - "└── signed artifact", - "media type:", - "digest:", - "size:", -} +var ( + inspectSuccessfully = []string{ + "└── application/vnd.cncf.notary.signature", + "└── sha256:", + "├── media type:", + "├── signature algorithm:", + "├── signed attributes", + "signingTime:", + "signingScheme:", + "├── user defined attributes", + "│ └── (empty)", + "├── unsigned attributes", + "│ └── signingAgent: Notation/", + "├── certificates", + "│ └── SHA256 fingerprint:", + "issued to:", + "issued by:", + "expiry:", + "└── signed artifact", + "media type:", + "digest:", + "size:", + } + + inspectSuccessfullyWithTimestamp = []string{ + "└── application/vnd.cncf.notary.signature", + "└── sha256:", + "├── media type:", + "├── signature algorithm:", + "├── signed attributes", + "signingTime:", + "signingScheme:", + "├── user defined attributes", + "│ └── (empty)", + "├── unsigned attributes", + "│ └── signingAgent: Notation/", + "│ └── timestampSignature", + "│ ├── timestamp:", + "│ └── timestampCertificates", + "├── certificates", + "│ └── SHA256 fingerprint:", + "issued to:", + "issued by:", + "expiry:", + "└── signed artifact", + "media type:", + "digest:", + "size:", + } +) var _ = Describe("notation inspect", func() { It("all signatures of an image", func() { @@ -131,4 +161,14 @@ var _ = Describe("notation inspect", func() { MatchKeyWords(inspectSuccessfully...) }) }) + + It("with timestamping", func() { + Host(BaseOptions(), func(notation *utils.ExecOpts, artifact *Artifact, vhost *utils.VirtualHost) { + notation.Exec("sign", "--timestamp-url", "http://rfc3161timestamp.globalsign.com/advanced", "--timestamp-root-cert", filepath.Join(NotationE2EConfigPath, "timestamp", "globalsignTSARoot.cer"), artifact.ReferenceWithDigest()). + MatchKeyWords(SignSuccessfully) + + notation.Exec("inspect", artifact.ReferenceWithDigest()). + MatchKeyWords(inspectSuccessfullyWithTimestamp...) + }) + }) }) From e6198355d1409fa2e45c6098e64245939ded4838 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 26 Jul 2024 15:33:00 +0800 Subject: [PATCH 02/11] fixed E2E test Signed-off-by: Patrick Zheng --- specs/commandline/inspect.md | 62 ++++++++++++++++++++++++++----- test/e2e/suite/command/inspect.go | 7 ++-- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/specs/commandline/inspect.md b/specs/commandline/inspect.md index b068bff2c..8137ec627 100644 --- a/specs/commandline/inspect.md +++ b/specs/commandline/inspect.md @@ -78,7 +78,13 @@ localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee │ ├── user defined attributes │ │ └── io.wabbit-networks.buildId: 123 //user defined metadata │ ├── unsigned attributes - │ │ ├── io.cncf.notary.timestampSignature: //TSA response + │ │ ├── timestampSignature //TSA response + | │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + | │ │ └── timestampCertificates + | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 + | │ │ ├── issued to: wabbit-com Software Timestamp + | │ │ ├── issued by: wabbit-com Software Trusted Timestamping + | │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ │ └── io.cncf.notary.signingAgent: notation/1.0.0 //client version │ ├── certificates │ │ ├── SHA256 fingerprint: E8C15B4C98AD91E051EE5AF5F524A8729050B2A @@ -106,7 +112,13 @@ localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee │ ├── expiry: Sat Jun 29 22:04:01 2024 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes - │ ├── io.cncf.notary.timestampSignature: + │ ├── timestampSignature + │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + │ │ └── timestampCertificates + │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 + │ │ ├── issued to: wabbit-com Software Timestamp + │ │ ├── issued by: wabbit-com Software Trusted Timestamping + │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ └── io.cncf.notary.signingAgent: notation/1.0.0 ├── certificates │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d @@ -157,7 +169,13 @@ localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380 │ ├── user defined attributes │ │ └── io.wabbit-networks.buildId: 123 │ ├── unsigned attributes - │ │ ├── io.cncf.notary.timestampSignature: + │ │ ├── timestampSignature + | │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + | │ │ └── timestampCertificates + | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 + | │ │ ├── issued to: wabbit-com Software Timestamp + | │ │ ├── issued by: wabbit-com Software Trusted Timestamping + | │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ │ └── io.cncf.notary.signingAgent: notation/1.0.0 │ ├── certificates │ │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d @@ -185,7 +203,13 @@ localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380 │ ├── expiry: Sat Jun 29 22:04:01 2024 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes - │ ├── io.cncf.notary.timestampSignature: + │ ├── timestampSignature + │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + │ │ └── timestampCertificates + │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 + │ │ ├── issued to: wabbit-com Software Timestamp + │ │ ├── issued by: wabbit-com Software Trusted Timestamping + │ │ └── expiry: Fri Oct 13 23:59:59 2034 │ └── io.cncf.notary.signingAgent: notation/1.0.0 ├── certificates │ ├── SHA256 fingerprint: b13a843be16b1f461f08d61c14f3eab7d87c073570da077217541a7eb31c084d @@ -230,8 +254,18 @@ An example output: "io.wabbit-networks.buildId": "123" }, "unsignedAttributes": { - "io.cncf.notary.timestampSignature": "", - "io.cncf.notary.signingAgent": "notation/1.0.0" + "timestampSignature": { + "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]", + "timestampCertificates": [ + { + "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828", + "issuedTo": "wabbit-com Software Timestamp", + "issuedBy": "wabbit-com Software Trusted Timestamping", + "expiry": "2034-10-13T23:59:59Z" + } + ] + }, + "signingAgent": "notation/1.0.0" }, "certificates": [ { @@ -269,9 +303,19 @@ An example output: "expiry": "2023-02-06T20:50:17Z", "io.cncf.notary.verificationPlugin": "com.example.nv2plugin" }, - "unsignedAttributes": { - "io.cncf.notary.timestampSignature": "", - "io.cncf.notary.signingAgent": "notation/1.0.0" + "unsignedAttributes": { + "timestampSignature": { + "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]", + "timestampCertificates": [ + { + "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828", + "issuedTo": "wabbit-com Software Timestamp", + "issuedBy": "wabbit-com Software Trusted Timestamping", + "expiry": "2034-10-13T23:59:59Z" + } + ] + }, + "signingAgent": "notation/1.0.0" }, "certificates": [ { diff --git a/test/e2e/suite/command/inspect.go b/test/e2e/suite/command/inspect.go index e8f570b72..755c78032 100644 --- a/test/e2e/suite/command/inspect.go +++ b/test/e2e/suite/command/inspect.go @@ -58,9 +58,10 @@ var ( "│ └── (empty)", "├── unsigned attributes", "│ └── signingAgent: Notation/", - "│ └── timestampSignature", - "│ ├── timestamp:", - "│ └── timestampCertificates", + "│ ├── timestampSignature", + "│ │ ├── timestamp:", + "│ │ └── timestampCertificates", + "│ │ ├── SHA256 fingerprint:", "├── certificates", "│ └── SHA256 fingerprint:", "issued to:", From 387cd5b527b407a260d4845c06071c0b0cfd90cb Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 26 Jul 2024 15:40:10 +0800 Subject: [PATCH 03/11] fixed E2E test Signed-off-by: Patrick Zheng --- test/e2e/suite/command/inspect.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/e2e/suite/command/inspect.go b/test/e2e/suite/command/inspect.go index 755c78032..f228ac45d 100644 --- a/test/e2e/suite/command/inspect.go +++ b/test/e2e/suite/command/inspect.go @@ -57,11 +57,11 @@ var ( "├── user defined attributes", "│ └── (empty)", "├── unsigned attributes", - "│ └── signingAgent: Notation/", - "│ ├── timestampSignature", - "│ │ ├── timestamp:", - "│ │ └── timestampCertificates", - "│ │ ├── SHA256 fingerprint:", + "signingAgent: Notation/", + "timestampSignature", + "timestamp:", + "timestampCertificates", + "SHA256 fingerprint:", "├── certificates", "│ └── SHA256 fingerprint:", "issued to:", From feba5b135228d608cda7863a8c86fa1ba6a9baa7 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 26 Jul 2024 15:54:01 +0800 Subject: [PATCH 04/11] update Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 9 ++++++++- specs/commandline/inspect.md | 8 ++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index de543a56e..111aaa897 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -265,8 +265,15 @@ func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeCo return nil, fmt.Errorf("failed to get timestamp from timestamp countersignature with error: %w", err) } certificates := getCertificates(outputFormat, signedToken.Certificates) + var formatTimestamp string + switch outputFormat { + case cmd.OutputJSON: + formatTimestamp = timestamp.Format(time.RFC3339) + default: + formatTimestamp = timestamp.Format(time.ANSIC) + } unsignedAttributes["timestampSignature"] = timestampOutput{ - Timestamp: timestamp.Format(time.RFC3339), + Timestamp: formatTimestamp, TimestampCertificates: certificates, } } diff --git a/specs/commandline/inspect.md b/specs/commandline/inspect.md index 8137ec627..7cc748f6c 100644 --- a/specs/commandline/inspect.md +++ b/specs/commandline/inspect.md @@ -79,7 +79,7 @@ localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee │ │ └── io.wabbit-networks.buildId: 123 //user defined metadata │ ├── unsigned attributes │ │ ├── timestampSignature //TSA response - | │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + | │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] | │ │ └── timestampCertificates | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 | │ │ ├── issued to: wabbit-com Software Timestamp @@ -113,7 +113,7 @@ localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes │ ├── timestampSignature - │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] │ │ └── timestampCertificates │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 │ │ ├── issued to: wabbit-com Software Timestamp @@ -170,7 +170,7 @@ localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380 │ │ └── io.wabbit-networks.buildId: 123 │ ├── unsigned attributes │ │ ├── timestampSignature - | │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + | │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] | │ │ └── timestampCertificates | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 | │ │ ├── issued to: wabbit-com Software Timestamp @@ -204,7 +204,7 @@ localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes │ ├── timestampSignature - │ │ ├── timestamp: [2023-06-23T22:04:37Z, 2023-06-23T22:04:38Z] + │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] │ │ └── timestampCertificates │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 │ │ ├── issued to: wabbit-com Software Timestamp From e00452f3dc47b58a60df5c52e59e9e1ece344288 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Fri, 26 Jul 2024 18:56:56 +0800 Subject: [PATCH 05/11] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 47 +++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index 111aaa897..b8c6e169e 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -71,8 +71,9 @@ type certificateOutput struct { } type timestampOutput struct { - Timestamp string `json:"timestamp"` - TimestampCertificates []certificateOutput `json:"timestampCertificates"` + Timestamp string `json:"timestamp,omitempty"` + TimestampCertificates []certificateOutput `json:"timestampCertificates,omitempty"` + Error error `json:"error,omitempty"` } func inspectCommand(opts *inspectOpts) *cobra.Command { @@ -181,20 +182,13 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { return nil } - unsignedAttributes, err := getUnsignedAttributes(opts.outputFormat, envelopeContent) - if err != nil { - logSkippedSignature(sigManifestDesc, err) - skippedSignatures = true - return nil - } - sig := signatureOutput{ MediaType: sigDesc.MediaType, Digest: sigManifestDesc.Digest.String(), SignatureAlgorithm: string(signatureAlgorithm), SignedAttributes: getSignedAttributes(opts.outputFormat, envelopeContent), UserDefinedAttributes: signedArtifactDesc.Annotations, - UnsignedAttributes: unsignedAttributes, + UnsignedAttributes: getUnsignedAttributes(opts.outputFormat, envelopeContent), Certificates: getCertificates(opts.outputFormat, envelopeContent.SignerInfo.CertificateChain), SignedArtifact: *signedArtifactDesc, } @@ -248,21 +242,34 @@ func getSignedAttributes(outputFormat string, envContent *signature.EnvelopeCont return signedAttributes } -func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) (map[string]any, error) { - unsignedAttributes := map[string]any{} +func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]any { + unsignedAttributes := make(map[string]any) + + if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { + unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent + } if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { signedToken, err := tspclient.ParseSignedToken(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) if err != nil { - return nil, fmt.Errorf("failed to parse timestamp countersignature with error: %w", err) + unsignedAttributes["timestampSignature"] = timestampOutput{ + Error: errors.New("failed to parse timestamp countersignature"), + } + return unsignedAttributes } info, err := signedToken.Info() if err != nil { - return nil, fmt.Errorf("failed to get the timestamp TSTInfo with error: %w", err) + unsignedAttributes["timestampSignature"] = timestampOutput{ + Error: errors.New("failed to parse timestamp countersignature"), + } + return unsignedAttributes } timestamp, err := info.Validate(envContent.SignerInfo.Signature) if err != nil { - return nil, fmt.Errorf("failed to get timestamp from timestamp countersignature with error: %w", err) + unsignedAttributes["timestampSignature"] = timestampOutput{ + Error: errors.New("failed to parse timestamp countersignature"), + } + return unsignedAttributes } certificates := getCertificates(outputFormat, signedToken.Certificates) var formatTimestamp string @@ -278,11 +285,7 @@ func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeCo } } - if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { - unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent - } - - return unsignedAttributes, nil + return unsignedAttributes } func formatTimestamp(outputFormat string, t time.Time) string { @@ -346,6 +349,10 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { unsignedAttributesNode.AddPair(k, value) case timestampOutput: timestampNode := unsignedAttributesNode.Add("timestampSignature") + if value.Error != nil { + timestampNode.AddPair("error", value.Error.Error()) + break + } timestampNode.AddPair("timestamp", value.Timestamp) addCertificatesToTree(timestampNode, "timestampCertificates", value.TimestampCertificates) } From 05f5270c05c35f2535b11e6fdcc225b192ed41ea Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 29 Jul 2024 10:28:22 +0800 Subject: [PATCH 06/11] add test Signed-off-by: Patrick Zheng --- cmd/notation/inspect_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cmd/notation/inspect_test.go b/cmd/notation/inspect_test.go index f1309fef9..78a7238f9 100644 --- a/cmd/notation/inspect_test.go +++ b/cmd/notation/inspect_test.go @@ -16,6 +16,7 @@ package main import ( "testing" + "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation/internal/cmd" ) @@ -84,3 +85,22 @@ func TestInspectCommand_MissingArgs(t *testing.T) { t.Fatal("Parse Args expected error, but ok") } } + +func TestGetUnsignedAttributes(t *testing.T) { + envContent := &signature.EnvelopeContent{ + SignerInfo: signature.SignerInfo{ + UnsignedAttributes: signature.UnsignedAttributes{ + TimestampSignature: []byte("invalid"), + }, + }, + } + expectedErrMsg := "failed to parse timestamp countersignature" + unsignedAttr := getUnsignedAttributes(cmd.OutputPlaintext, envContent) + val, ok := unsignedAttr["timestampSignature"].(timestampOutput) + if !ok { + t.Fatal("expected to have timestampSignature") + } + if val.Error == nil || val.Error.Error() != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, val.Error) + } +} From 61ffb069e4bf7ef23e7aba0829d1383fcbdad030 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 29 Jul 2024 11:00:08 +0800 Subject: [PATCH 07/11] fixed json Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 12 ++++++------ cmd/notation/inspect_test.go | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index b8c6e169e..a97949236 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -73,7 +73,7 @@ type certificateOutput struct { type timestampOutput struct { Timestamp string `json:"timestamp,omitempty"` TimestampCertificates []certificateOutput `json:"timestampCertificates,omitempty"` - Error error `json:"error,omitempty"` + Error string `json:"error,omitempty"` } func inspectCommand(opts *inspectOpts) *cobra.Command { @@ -253,21 +253,21 @@ func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeCo signedToken, err := tspclient.ParseSignedToken(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) if err != nil { unsignedAttributes["timestampSignature"] = timestampOutput{ - Error: errors.New("failed to parse timestamp countersignature"), + Error: "failed to parse timestamp countersignature", } return unsignedAttributes } info, err := signedToken.Info() if err != nil { unsignedAttributes["timestampSignature"] = timestampOutput{ - Error: errors.New("failed to parse timestamp countersignature"), + Error: "failed to parse timestamp countersignature", } return unsignedAttributes } timestamp, err := info.Validate(envContent.SignerInfo.Signature) if err != nil { unsignedAttributes["timestampSignature"] = timestampOutput{ - Error: errors.New("failed to parse timestamp countersignature"), + Error: "failed to parse timestamp countersignature", } return unsignedAttributes } @@ -349,8 +349,8 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { unsignedAttributesNode.AddPair(k, value) case timestampOutput: timestampNode := unsignedAttributesNode.Add("timestampSignature") - if value.Error != nil { - timestampNode.AddPair("error", value.Error.Error()) + if value.Error != "" { + timestampNode.AddPair("error", value.Error) break } timestampNode.AddPair("timestamp", value.Timestamp) diff --git a/cmd/notation/inspect_test.go b/cmd/notation/inspect_test.go index 78a7238f9..838225f06 100644 --- a/cmd/notation/inspect_test.go +++ b/cmd/notation/inspect_test.go @@ -100,7 +100,7 @@ func TestGetUnsignedAttributes(t *testing.T) { if !ok { t.Fatal("expected to have timestampSignature") } - if val.Error == nil || val.Error.Error() != expectedErrMsg { + if val.Error != expectedErrMsg { t.Fatalf("expected %s, but got %s", expectedErrMsg, val.Error) } } From b6d663717fb15149b8270b7c0ace891fbea42245 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 29 Jul 2024 16:09:48 +0800 Subject: [PATCH 08/11] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 67 ++++++++++++++++++------------------ cmd/notation/inspect_test.go | 2 +- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index a97949236..5da876ffb 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -250,39 +250,7 @@ func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeCo } if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { - signedToken, err := tspclient.ParseSignedToken(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) - if err != nil { - unsignedAttributes["timestampSignature"] = timestampOutput{ - Error: "failed to parse timestamp countersignature", - } - return unsignedAttributes - } - info, err := signedToken.Info() - if err != nil { - unsignedAttributes["timestampSignature"] = timestampOutput{ - Error: "failed to parse timestamp countersignature", - } - return unsignedAttributes - } - timestamp, err := info.Validate(envContent.SignerInfo.Signature) - if err != nil { - unsignedAttributes["timestampSignature"] = timestampOutput{ - Error: "failed to parse timestamp countersignature", - } - return unsignedAttributes - } - certificates := getCertificates(outputFormat, signedToken.Certificates) - var formatTimestamp string - switch outputFormat { - case cmd.OutputJSON: - formatTimestamp = timestamp.Format(time.RFC3339) - default: - formatTimestamp = timestamp.Format(time.ANSIC) - } - unsignedAttributes["timestampSignature"] = timestampOutput{ - Timestamp: formatTimestamp, - TimestampCertificates: certificates, - } + unsignedAttributes["timestampSignature"] = getTimestamp(outputFormat, envContent) } return unsignedAttributes @@ -389,3 +357,36 @@ func addCertificatesToTree(node *tree.Node, name string, certs []certificateOutp certNode.AddPair("expiry", cert.Expiry) } } + +func getTimestamp(outputFormat string, envContent *signature.EnvelopeContent) timestampOutput { + signedToken, err := tspclient.ParseSignedToken(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) + if err != nil { + return timestampOutput{ + Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), + } + } + info, err := signedToken.Info() + if err != nil { + return timestampOutput{ + Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), + } + } + timestamp, err := info.Validate(envContent.SignerInfo.Signature) + if err != nil { + return timestampOutput{ + Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), + } + } + certificates := getCertificates(outputFormat, signedToken.Certificates) + var formatTimestamp string + switch outputFormat { + case cmd.OutputJSON: + formatTimestamp = timestamp.Format(time.RFC3339) + default: + formatTimestamp = timestamp.Format(time.ANSIC) + } + return timestampOutput{ + Timestamp: formatTimestamp, + TimestampCertificates: certificates, + } +} diff --git a/cmd/notation/inspect_test.go b/cmd/notation/inspect_test.go index 838225f06..a6c20f596 100644 --- a/cmd/notation/inspect_test.go +++ b/cmd/notation/inspect_test.go @@ -94,7 +94,7 @@ func TestGetUnsignedAttributes(t *testing.T) { }, }, } - expectedErrMsg := "failed to parse timestamp countersignature" + expectedErrMsg := "failed to parse timestamp countersignature: cms: syntax error: invalid signed data: failed to convert from BER to DER: asn1: syntax error: decoding BER length octets: short form length octets value should be less or equal to the subsequent octets length" unsignedAttr := getUnsignedAttributes(cmd.OutputPlaintext, envContent) val, ok := unsignedAttr["timestampSignature"].(timestampOutput) if !ok { From d91177f579688cfa4b8ad1f1f7d4467e55b42810 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Mon, 29 Jul 2024 16:29:30 +0800 Subject: [PATCH 09/11] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index 5da876ffb..7a8d1d4ad 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -245,12 +245,12 @@ func getSignedAttributes(outputFormat string, envContent *signature.EnvelopeCont func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]any { unsignedAttributes := make(map[string]any) - if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { - unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent + if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { + unsignedAttributes["timestampSignature"] = parseTimestamp(outputFormat, envContent.SignerInfo) } - if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { - unsignedAttributes["timestampSignature"] = getTimestamp(outputFormat, envContent) + if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { + unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent } return unsignedAttributes @@ -358,8 +358,8 @@ func addCertificatesToTree(node *tree.Node, name string, certs []certificateOutp } } -func getTimestamp(outputFormat string, envContent *signature.EnvelopeContent) timestampOutput { - signedToken, err := tspclient.ParseSignedToken(envContent.SignerInfo.UnsignedAttributes.TimestampSignature) +func parseTimestamp(outputFormat string, signerInfo signature.SignerInfo) timestampOutput { + signedToken, err := tspclient.ParseSignedToken(signerInfo.UnsignedAttributes.TimestampSignature) if err != nil { return timestampOutput{ Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), @@ -371,7 +371,7 @@ func getTimestamp(outputFormat string, envContent *signature.EnvelopeContent) ti Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), } } - timestamp, err := info.Validate(envContent.SignerInfo.Signature) + timestamp, err := info.Validate(signerInfo.Signature) if err != nil { return timestampOutput{ Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), From a67038e12bb9434fce2f6f8ea46293ded544ae00 Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 30 Jul 2024 08:27:33 +0800 Subject: [PATCH 10/11] updated per code review Signed-off-by: Patrick Zheng --- cmd/notation/inspect.go | 14 +++++++------- test/e2e/suite/command/inspect.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index 7a8d1d4ad..a55595c51 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -71,9 +71,9 @@ type certificateOutput struct { } type timestampOutput struct { - Timestamp string `json:"timestamp,omitempty"` - TimestampCertificates []certificateOutput `json:"timestampCertificates,omitempty"` - Error string `json:"error,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Certificates []certificateOutput `json:"certificates,omitempty"` + Error string `json:"error,omitempty"` } func inspectCommand(opts *inspectOpts) *cobra.Command { @@ -316,13 +316,13 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { case string: unsignedAttributesNode.AddPair(k, value) case timestampOutput: - timestampNode := unsignedAttributesNode.Add("timestampSignature") + timestampNode := unsignedAttributesNode.Add("timestamp signature") if value.Error != "" { timestampNode.AddPair("error", value.Error) break } timestampNode.AddPair("timestamp", value.Timestamp) - addCertificatesToTree(timestampNode, "timestampCertificates", value.TimestampCertificates) + addCertificatesToTree(timestampNode, "certificates", value.Certificates) } } @@ -386,7 +386,7 @@ func parseTimestamp(outputFormat string, signerInfo signature.SignerInfo) timest formatTimestamp = timestamp.Format(time.ANSIC) } return timestampOutput{ - Timestamp: formatTimestamp, - TimestampCertificates: certificates, + Timestamp: formatTimestamp, + Certificates: certificates, } } diff --git a/test/e2e/suite/command/inspect.go b/test/e2e/suite/command/inspect.go index f228ac45d..a6f6c1880 100644 --- a/test/e2e/suite/command/inspect.go +++ b/test/e2e/suite/command/inspect.go @@ -58,9 +58,9 @@ var ( "│ └── (empty)", "├── unsigned attributes", "signingAgent: Notation/", - "timestampSignature", + "timestamp signature", "timestamp:", - "timestampCertificates", + "certificates", "SHA256 fingerprint:", "├── certificates", "│ └── SHA256 fingerprint:", From 737596623f997d9abecdddbb22b4decd55ab422e Mon Sep 17 00:00:00 2001 From: Patrick Zheng Date: Tue, 30 Jul 2024 08:30:06 +0800 Subject: [PATCH 11/11] updated per code review Signed-off-by: Patrick Zheng --- specs/commandline/inspect.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/specs/commandline/inspect.md b/specs/commandline/inspect.md index 7cc748f6c..708b48ee7 100644 --- a/specs/commandline/inspect.md +++ b/specs/commandline/inspect.md @@ -78,9 +78,9 @@ localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee │ ├── user defined attributes │ │ └── io.wabbit-networks.buildId: 123 //user defined metadata │ ├── unsigned attributes - │ │ ├── timestampSignature //TSA response + │ │ ├── timestamp signature //TSA response | │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] - | │ │ └── timestampCertificates + | │ │ └── certificates | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 | │ │ ├── issued to: wabbit-com Software Timestamp | │ │ ├── issued by: wabbit-com Software Trusted Timestamping @@ -112,9 +112,9 @@ localhost:5000/net-monitor@sha256:b94d27b9934d3e08a52e52d7da7dabfac4efe37a5380ee │ ├── expiry: Sat Jun 29 22:04:01 2024 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes - │ ├── timestampSignature + │ ├── timestamp signature │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] - │ │ └── timestampCertificates + │ │ └── certificates │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 │ │ ├── issued to: wabbit-com Software Timestamp │ │ ├── issued by: wabbit-com Software Trusted Timestamping @@ -169,9 +169,9 @@ localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380 │ ├── user defined attributes │ │ └── io.wabbit-networks.buildId: 123 │ ├── unsigned attributes - │ │ ├── timestampSignature + │ │ ├── timestamp signature | │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] - | │ │ └── timestampCertificates + | │ │ └── certificates | │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 | │ │ ├── issued to: wabbit-com Software Timestamp | │ │ ├── issued by: wabbit-com Software Trusted Timestamping @@ -203,9 +203,9 @@ localhost:5000/net-monitor@sha256:ca5427b5567d3e06a72e52d7da7dabfac484efe37a5380 │ ├── expiry: Sat Jun 29 22:04:01 2024 │ └── io.cncf.notary.verificationPlugin: com.example.nv2plugin ├── unsigned attributes - │ ├── timestampSignature + │ ├── timestamp signature │ │ ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023] - │ │ └── timestampCertificates + │ │ └── certificates │ │ └── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828 │ │ ├── issued to: wabbit-com Software Timestamp │ │ ├── issued by: wabbit-com Software Trusted Timestamping @@ -256,7 +256,7 @@ An example output: "unsignedAttributes": { "timestampSignature": { "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]", - "timestampCertificates": [ + "certificates": [ { "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828", "issuedTo": "wabbit-com Software Timestamp", @@ -306,7 +306,7 @@ An example output: "unsignedAttributes": { "timestampSignature": { "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]", - "timestampCertificates": [ + "certificates": [ { "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828", "issuedTo": "wabbit-com Software Timestamp",