Repository: notaryproject/notation (go)

Issue:
Notation inspect command timestamp signature support
### Is your feature request related to a problem?

![image](https://github.com/user-attachments/assets/15be01bc-e671-41b5-b215-50168467c711)
The timestamp signature field is base64 encoded; however, it is not readable.

### What solution do you propose?

For the notation inspect command, we need to provide readable content for humans. Therefore, for the timestamp signature, we may need to show the actual timestamp, issuer, and the content of the timestamping certificate chain.

### What alternatives have you considered?

No

### Any additional context?

No

Pull request:
feat: update inspect command with timestamping
This PR updates `notation inspect` command with timestamping. Resolves #997 
Example results:
```
notation inspect $IMAGE

Inspecting all signatures for signed artifact
myRegistry/myRepo@sha256:c0669ef34cdc14332c0f1ab0c2c01acb91d96014b172f1a76f3a39e63d1f0bda
└── application/vnd.cncf.notary.signature
    └── sha256:20171244b2291f7e854e595a20842faa581d82ebcd85266543390d5ca4b73551
        ├── media type: application/cose
        ├── signature algorithm: RSASSA-PSS-SHA-256
        ├── signed attributes
        │   ├── signingScheme: notary.x509
        │   └── signingTime: Fri Jul 26 14:50:29 2024
        ├── user defined attributes
        │   └── (empty)
        ├── unsigned attributes
        │   ├── timestamp signature
        │   │   ├── timestamp: [Fri Jul 26 06:50:37 2024, Fri Jul 26 06:50:37 2024]
        │   │   └── certificates
        │   │       ├── SHA256 fingerprint: d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828
        │   │       │   ├── issued to: CN=DigiCert Timestamp 2023,O=DigiCert\, Inc.,C=US
        │   │       │   ├── issued by: CN=DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA,O=DigiCert\, Inc.,C=US
        │   │       │   └── expiry: Fri Oct 13 23:59:59 2034
        │   │       ├── SHA256 fingerprint: 281734d4592d1291d27190709cb510b07e22c405d5e0d6119b70e73589f98acf
        │   │       │   ├── issued to: CN=DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA,O=DigiCert\, Inc.,C=US
        │   │       │   ├── issued by: CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US
        │   │       │   └── expiry: Sun Mar 22 23:59:59 2037
        │   │       └── SHA256 fingerprint: 33846b545a49c9be4903c60e01713c1bd4e4ef31ea65cd95d69e62794f30b941
        │   │           ├── issued to: CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US
        │   │           ├── issued by: CN=DigiCert Assured ID Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
        │   │           └── expiry: Sun Nov  9 23:59:59 2031
        │   └── signingAgent: Notation/1.0.0 azure-kv/1.2.0
......
```
Example of JSON output:
```
notation inspect $IMAGE -o json

{
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "Signatures": [
        {
            "mediaType": "application/cose",
            "digest": "sha256:20171244b2291f7e854e595a20842faa581d82ebcd85266543390d5ca4b73551",
            "signatureAlgorithm": "RSASSA-PSS-SHA-256",
            "signedAttributes": {
                "signingScheme": "notary.x509",
                "signingTime": "2024-07-26T14:50:29+08:00"
            },
            "userDefinedAttributes": null,
            "unsignedAttributes": {
                "signingAgent": "Notation/1.0.0 azure-kv/1.2.0",
                "timestampSignature": {
                    "timestamp": "[2024-07-26T06:50:37Z, 2024-07-26T06:50:37Z]",
                    "certificates": [
                        {
                            "SHA256Fingerprint": "d2f6e46ded7422ccd1d440576841366f828ada559aae3316af4d1a9ad40c7828",
                            "issuedTo": "CN=DigiCert Timestamp 2023,O=DigiCert\\, Inc.,C=US",
                            "issuedBy": "CN=DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA,O=DigiCert\\, Inc.,C=US",
                            "expiry": "2034-10-13T23:59:59Z"
                        },
                        {
                            "SHA256Fingerprint": "281734d4592d1291d27190709cb510b07e22c405d5e0d6119b70e73589f98acf",
                            "issuedTo": "CN=DigiCert Trusted G4 RSA4096 SHA256 TimeStamping CA,O=DigiCert\\, Inc.,C=US",
                            "issuedBy": "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
                            "expiry": "2037-03-22T23:59:59Z"
                        },
                        {
                            "SHA256Fingerprint": "33846b545a49c9be4903c60e01713c1bd4e4ef31ea65cd95d69e62794f30b941",
                            "issuedTo": "CN=DigiCert Trusted Root G4,OU=www.digicert.com,O=DigiCert Inc,C=US",
                            "issuedBy": "CN=DigiCert Assured ID Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US",
                            "expiry": "2031-11-09T23:59:59Z"
                        }
                    ]
                }
            },
......
```

Example when failed to parse timestamp countersignature:
```
{
    "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
    "Signatures": [
        {
            "mediaType": "application/cose",
            "digest": "sha256:85f04ea6891da367b825866c53b27999e6cdf3bae8b2fe33eb1433d938c0f3e1",
            "signatureAlgorithm": "RSASSA-PSS-SHA-256",
            "signedAttributes": {
                "signingScheme": "notary.x509",
                "signingTime": "2024-07-29T10:40:55+08:00"
            },
            "userDefinedAttributes": null,
            "unsignedAttributes": {
                "signingAgent": "Notation/1.0.0 azure-kv/1.0.1",
                "timestampSignature": {
                    "error": "failed to parse timestamp countersignature"
                }
            },
......
```

Patch:
diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go
index df91dbe..a55595c 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,12 @@ type certificateOutput struct {
 	Expiry            string `json:"expiry"`
 }
 
+type timestampOutput struct {
+	Timestamp    string              `json:"timestamp,omitempty"`
+	Certificates []certificateOutput `json:"certificates,omitempty"`
+	Error        string              `json:"error,omitempty"`
+}
+
 func inspectCommand(opts *inspectOpts) *cobra.Command {
 	if opts == nil {
 		opts = &inspectOpts{}
@@ -181,8 +188,8 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error {
 			SignatureAlgorithm:    string(signatureAlgorithm),
 			SignedAttributes:      getSignedAttributes(opts.outputFormat, envelopeContent),
 			UserDefinedAttributes: signedArtifactDesc.Annotations,
-			UnsignedAttributes:    getUnsignedAttributes(envelopeContent),
-			Certificates:          getCertificates(opts.outputFormat, envelopeContent),
+			UnsignedAttributes:    getUnsignedAttributes(opts.outputFormat, envelopeContent),
+			Certificates:          getCertificates(opts.outputFormat, envelopeContent.SignerInfo.CertificateChain),
 			SignedArtifact:        *signedArtifactDesc,
 		}
 
@@ -235,11 +242,11 @@ 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 {
+	unsignedAttributes := make(map[string]any)
 
 	if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil {
-		unsignedAttributes["timestampSignature"] = b64.StdEncoding.EncodeToString(envContent.SignerInfo.UnsignedAttributes.TimestampSignature)
+		unsignedAttributes["timestampSignature"] = parseTimestamp(outputFormat, envContent.SignerInfo)
 	}
 
 	if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" {
@@ -258,10 +265,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 +311,23 @@ 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("timestamp signature")
+				if value.Error != "" {
+					timestampNode.AddPair("error", value.Error)
+					break
+				}
+				timestampNode.AddPair("timestamp", value.Timestamp)
+				addCertificatesToTree(timestampNode, "certificates", value.Certificates)
+			}
 		}
 
+		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 +347,46 @@ 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)
+	}
+}
+
+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()),
+		}
+	}
+	info, err := signedToken.Info()
+	if err != nil {
+		return timestampOutput{
+			Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()),
+		}
+	}
+	timestamp, err := info.Validate(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,
+		Certificates: certificates,
+	}
+}
diff --git a/specs/commandline/inspect.md b/specs/commandline/inspect.md
index b068bff..708b48e 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: <Base64(TimeStampToken)> //TSA response
+    │   │   ├── timestamp signature                                         //TSA response
+    |   │   │   ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023]
+    |   │   │   └── certificates
+    |   │   │       └── 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: <Base64(TimeStampToken)>
+        │   ├── timestamp signature
+        │   │   ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023]
+        │   │   └── certificates
+        │   │       └── 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: <Base64(TimeStampToken)>
+    │   │   ├── timestamp signature
+    |   │   │   ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023]
+    |   │   │   └── certificates
+    |   │   │       └── 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: <Base64(TimeStampToken)>
+        │   ├── timestamp signature
+        │   │   ├── timestamp: [Fri Jun 23 22:04:31 2023, Fri Jun 23 22:04:31 2023]
+        │   │   └── certificates
+        │   │       └── 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": "<Base64(TimeStampToken)>",
-        "io.cncf.notary.signingAgent": "notation/1.0.0"
+         "timestampSignature": {
+            "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]",
+            "certificates": [
+                {
+                    "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": "<Base64(TimeStampToken)>",
-        "io.cncf.notary.signingAgent": "notation/1.0.0"
+     "unsignedAttributes": {
+         "timestampSignature": {
+            "timestamp": "[2022-02-06T20:50:37Z, 2022-02-06T20:50:37Z]",
+            "certificates": [
+                {
+                    "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": [
         {


Test patch:
diff --git a/cmd/notation/inspect_test.go b/cmd/notation/inspect_test.go
index f1309fe..a6c20f5 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: 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 {
+		t.Fatal("expected to have timestampSignature")
+	}
+	if val.Error != expectedErrMsg {
+		t.Fatalf("expected %s, but got %s", expectedErrMsg, val.Error)
+	}
+}
diff --git a/test/e2e/suite/command/inspect.go b/test/e2e/suite/command/inspect.go
index 49ba33e..a6f6c18 100644
--- a/test/e2e/suite/command/inspect.go
+++ b/test/e2e/suite/command/inspect.go
@@ -14,34 +14,65 @@
 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/",
+		"timestamp signature",
+		"timestamp:",
+		"certificates",
+		"SHA256 fingerprint:",
+		"├── 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 +162,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...)
+		})
+	})
 })