diff --git a/cmd/notation/blob/inspect.go b/cmd/notation/blob/inspect.go index 3e2c98b9e..60bf0dac0 100644 --- a/cmd/notation/blob/inspect.go +++ b/cmd/notation/blob/inspect.go @@ -84,8 +84,7 @@ func runInspect(opts *inspectOpts) error { case cmd.OutputJSON: return ioutil.PrintObjectAsJSON(sig) case cmd.OutputPlaintext: - treeNode := sig.ToTreeNode(opts.sigPath) - treeNode.Print() + sig.SignatureNode(opts.sigPath).Print() } return nil } diff --git a/cmd/notation/inspect.go b/cmd/notation/inspect.go index a55595c51..79bca07e9 100644 --- a/cmd/notation/inspect.go +++ b/cmd/notation/inspect.go @@ -14,18 +14,10 @@ package main import ( - "crypto/sha256" - "crypto/x509" - "encoding/hex" "errors" "fmt" "os" - "strconv" - "strings" - "time" - "github.com/notaryproject/notation-core-go/signature" - "github.com/notaryproject/notation-go/plugin/proto" "github.com/notaryproject/notation-go/registry" cmderr "github.com/notaryproject/notation/cmd/notation/internal/errors" "github.com/notaryproject/notation/cmd/notation/internal/experimental" @@ -33,7 +25,6 @@ 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" ) @@ -48,32 +39,8 @@ type inspectOpts struct { } type inspectOutput struct { - MediaType string `json:"mediaType"` - Signatures []signatureOutput -} - -type signatureOutput struct { - MediaType string `json:"mediaType"` - Digest string `json:"digest"` - SignatureAlgorithm string `json:"signatureAlgorithm"` - SignedAttributes map[string]string `json:"signedAttributes"` - UserDefinedAttributes map[string]string `json:"userDefinedAttributes"` - UnsignedAttributes map[string]any `json:"unsignedAttributes"` - Certificates []certificateOutput `json:"certificates"` - SignedArtifact ocispec.Descriptor `json:"signedArtifact"` -} - -type certificateOutput struct { - SHA256Fingerprint string `json:"SHA256Fingerprint"` - IssuedTo string `json:"issuedTo"` - IssuedBy string `json:"issuedBy"` - Expiry string `json:"expiry"` -} - -type timestampOutput struct { - Timestamp string `json:"timestamp,omitempty"` - Certificates []certificateOutput `json:"certificates,omitempty"` - Error string `json:"error,omitempty"` + MediaType string `json:"mediaType"` + Signatures []*envelope.SignatureInfo `json:"signatures"` } func inspectCommand(opts *inspectOpts) *cobra.Command { @@ -144,7 +111,7 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { if err != nil { return err } - output := inspectOutput{MediaType: manifestDesc.MediaType, Signatures: []signatureOutput{}} + output := inspectOutput{MediaType: manifestDesc.MediaType, Signatures: []*envelope.SignatureInfo{}} skippedSignatures := false err = listSignatures(ctx, sigRepo, manifestDesc, opts.maxSignatures, func(sigManifestDesc ocispec.Descriptor) error { sigBlob, sigDesc, err := sigRepo.FetchSignatureBlob(ctx, sigManifestDesc) @@ -154,49 +121,20 @@ func runInspect(command *cobra.Command, opts *inspectOpts) error { return nil } - sigEnvelope, err := signature.ParseEnvelope(sigDesc.MediaType, sigBlob) - if err != nil { - logSkippedSignature(sigManifestDesc, err) - skippedSignatures = true - return nil - } - - envelopeContent, err := sigEnvelope.Content() - if err != nil { - logSkippedSignature(sigManifestDesc, err) - skippedSignatures = true - return nil - } - - signedArtifactDesc, err := envelope.DescriptorFromSignaturePayload(&envelopeContent.Payload) - if err != nil { - logSkippedSignature(sigManifestDesc, err) - skippedSignatures = true - return nil - } - - signatureAlgorithm, err := proto.EncodeSigningAlgorithm(envelopeContent.SignerInfo.SignatureAlgorithm) + sig, err := envelope.Parse(sigBlob, sigDesc.MediaType) 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(opts.outputFormat, envelopeContent), - Certificates: getCertificates(opts.outputFormat, envelopeContent.SignerInfo.CertificateChain), - SignedArtifact: *signedArtifactDesc, - } - // clearing annotations from the SignedArtifact field since they're already // displayed as UserDefinedAttributes sig.SignedArtifact.Annotations = nil + // adding digest to the signature info + sig.Digest = sigManifestDesc.Digest.String() + output.Signatures = append(output.Signatures, sig) return nil @@ -225,66 +163,6 @@ func logSkippedSignature(sigDesc ocispec.Descriptor, err error) { fmt.Fprintf(os.Stderr, "Warning: Skipping signature %s because of error: %v\n", sigDesc.Digest.String(), err) } -func getSignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]string { - signedAttributes := map[string]string{ - "signingScheme": string(envContent.SignerInfo.SignedAttributes.SigningScheme), - "signingTime": formatTimestamp(outputFormat, envContent.SignerInfo.SignedAttributes.SigningTime), - } - expiry := envContent.SignerInfo.SignedAttributes.Expiry - if !expiry.IsZero() { - signedAttributes["expiry"] = formatTimestamp(outputFormat, expiry) - } - - for _, attribute := range envContent.SignerInfo.SignedAttributes.ExtendedAttributes { - signedAttributes[fmt.Sprint(attribute.Key)] = fmt.Sprint(attribute.Value) - } - - return signedAttributes -} - -func getUnsignedAttributes(outputFormat string, envContent *signature.EnvelopeContent) map[string]any { - unsignedAttributes := make(map[string]any) - - if envContent.SignerInfo.UnsignedAttributes.TimestampSignature != nil { - unsignedAttributes["timestampSignature"] = parseTimestamp(outputFormat, envContent.SignerInfo) - } - - if envContent.SignerInfo.UnsignedAttributes.SigningAgent != "" { - unsignedAttributes["signingAgent"] = envContent.SignerInfo.UnsignedAttributes.SigningAgent - } - - return unsignedAttributes -} - -func formatTimestamp(outputFormat string, t time.Time) string { - switch outputFormat { - case cmd.OutputJSON: - return t.Format(time.RFC3339) - default: - return t.Format(time.ANSIC) - } -} - -func getCertificates(outputFormat string, certChain []*x509.Certificate) []certificateOutput { - certificates := []certificateOutput{} - - for _, cert := range certChain { - h := sha256.Sum256(cert.Raw) - fingerprint := strings.ToLower(hex.EncodeToString(h[:])) - - certificate := certificateOutput{ - SHA256Fingerprint: fingerprint, - IssuedTo: cert.Subject.String(), - IssuedBy: cert.Issuer.String(), - Expiry: formatTimestamp(outputFormat, cert.NotAfter), - } - - certificates = append(certificates, certificate) - } - - return certificates -} - func printOutput(outputFormat string, ref string, output inspectOutput) error { if outputFormat == cmd.OutputJSON { return ioutil.PrintObjectAsJSON(output) @@ -300,93 +178,9 @@ func printOutput(outputFormat string, ref string, output inspectOutput) error { cncfSigNode := root.Add(registry.ArtifactTypeNotation) for _, signature := range output.Signatures { - sigNode := cncfSigNode.Add(signature.Digest) - sigNode.AddPair("media type", signature.MediaType) - sigNode.AddPair("signature algorithm", signature.SignatureAlgorithm) - - signedAttributesNode := sigNode.Add("signed attributes") - addMapToTree(signedAttributesNode, signature.SignedAttributes) - - userDefinedAttributesNode := sigNode.Add("user defined attributes") - addMapToTree(userDefinedAttributesNode, signature.UserDefinedAttributes) - - unsignedAttributesNode := sigNode.Add("unsigned attributes") - 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()) - artifactNode.AddPair("size", strconv.FormatInt(signature.SignedArtifact.Size, 10)) + cncfSigNode.Children = append(cncfSigNode.Children, signature.SignatureNode(signature.Digest)) } root.Print() return nil } - -func addMapToTree(node *tree.Node, m map[string]string) { - if len(m) > 0 { - for k, v := range m { - node.AddPair(k, v) - } - } else { - 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/cmd/notation/inspect_test.go b/cmd/notation/inspect_test.go index a6c20f596..f1309fef9 100644 --- a/cmd/notation/inspect_test.go +++ b/cmd/notation/inspect_test.go @@ -16,7 +16,6 @@ package main import ( "testing" - "github.com/notaryproject/notation-core-go/signature" "github.com/notaryproject/notation/internal/cmd" ) @@ -85,22 +84,3 @@ 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/internal/envelope/signature.go b/internal/envelope/signature.go index ee4fc3794..fd4bfaeb3 100644 --- a/internal/envelope/signature.go +++ b/internal/envelope/signature.go @@ -34,7 +34,7 @@ import ( // SignatureInfo is the signature envelope with human readable fields. type SignatureInfo struct { MediaType string `json:"mediaType"` - Digest string `json:"digest"` + Digest string `json:"digest,omitempty"` SignatureAlgorithm plugin.SignatureAlgorithm `json:"signatureAlgorithm"` SignedAttributes map[string]any `json:"signedAttributes"` UserDefinedAttributes map[string]string `json:"userDefinedAttributes"` @@ -158,15 +158,14 @@ func parseTimestamp(signerInfo signature.SignerInfo) TimestampInfo { Error: fmt.Sprintf("failed to parse timestamp countersignature: %s", err.Error()), } } - certificates := getCertificates(signedToken.Certificates) - return TimestampInfo{ Timestamp: ioutil.Timestamp(*timestamp), - Certificates: certificates, + Certificates: getCertificates(signedToken.Certificates), } } -func (s *SignatureInfo) ToTreeNode(sigName string) *tree.Node { +// SignatureNode returns a tree node that represents the signature. +func (s *SignatureInfo) SignatureNode(sigName string) *tree.Node { sigNode := tree.New(sigName) sigNode.AddPair("signature algorithm", s.SignatureAlgorithm) sigNode.AddPair("signature envelope type", s.MediaType) diff --git a/internal/envelope/signature_test.go b/internal/envelope/signature_test.go new file mode 100644 index 000000000..8ef6f4e8f --- /dev/null +++ b/internal/envelope/signature_test.go @@ -0,0 +1,39 @@ +// Copyright The Notary Project Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package envelope + +import ( + "testing" + + "github.com/notaryproject/notation-core-go/signature" +) + +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(envContent) + val, ok := unsignedAttr["timestampSignature"].(TimestampInfo) + if !ok { + t.Fatal("expected to have timestampSignature") + } + if val.Error != expectedErrMsg { + t.Fatalf("expected %s, but got %s", expectedErrMsg, val.Error) + } +}