Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: blob verify command #1137

Merged
merged 59 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
1619d18
blob signing
Two-Hearts Dec 24, 2024
5870423
blob signing
Two-Hearts Dec 25, 2024
c6da7dc
update
Two-Hearts Dec 26, 2024
c13744e
Merge branch 'notaryproject:main' into blobsign
Two-Hearts Dec 26, 2024
97c3fcd
add tests
Two-Hearts Dec 26, 2024
f0db92f
fix test
Two-Hearts Dec 26, 2024
9cf65b8
fix test
Two-Hearts Dec 26, 2024
e8fe8c6
fix e2e test
Two-Hearts Dec 26, 2024
cb997c7
add e2e tests
Two-Hearts Dec 26, 2024
07d0ffb
add e2e tests
Two-Hearts Dec 26, 2024
cf40ba2
fix e2e tests
Two-Hearts Dec 26, 2024
1051ad4
fix e2e test
Two-Hearts Dec 26, 2024
cf5fecb
add more e2e tests
Two-Hearts Dec 27, 2024
fc4dcfc
add more e2e tests
Two-Hearts Dec 27, 2024
576a286
fix e2e test
Two-Hearts Dec 27, 2024
0c984a0
add more tests
Two-Hearts Dec 27, 2024
8474d58
Merge branch 'notaryproject:main' into blobsign
Two-Hearts Dec 31, 2024
14a1f3e
update
Two-Hearts Dec 31, 2024
752afe6
fix e2e test
Two-Hearts Dec 31, 2024
c30e199
resolve conflicts
Two-Hearts Jan 6, 2025
bc8f710
update
Two-Hearts Jan 6, 2025
9ce5ac9
initial commit
Two-Hearts Jan 6, 2025
9e84a3f
update
Two-Hearts Jan 7, 2025
2659ff7
fix tests
Two-Hearts Jan 7, 2025
a1d8562
update
Two-Hearts Jan 9, 2025
d6eab1b
fix test
Two-Hearts Jan 9, 2025
94b586a
fix tests
Two-Hearts Jan 9, 2025
ce63a53
fix tests
Two-Hearts Jan 9, 2025
3602591
update
Two-Hearts Jan 10, 2025
eeaf02c
added e2e tests
Two-Hearts Jan 10, 2025
8cc2aeb
fix e2e tests
Two-Hearts Jan 10, 2025
32b009b
fix e2e tests
Two-Hearts Jan 10, 2025
54ecf6e
fix e2e tests
Two-Hearts Jan 10, 2025
fa50e18
add more e2e tests
Two-Hearts Jan 10, 2025
07c66b4
add more e2e tests
Two-Hearts Jan 10, 2025
4c07098
added more e2e tests
Two-Hearts Jan 10, 2025
2bbf48c
resolve conflicts
Two-Hearts Jan 14, 2025
1294938
update
Two-Hearts Jan 14, 2025
c81e175
resolve conflicts
Two-Hearts Feb 6, 2025
210a79c
resolve conflicts
Two-Hearts Feb 11, 2025
d998243
resolved conflicts
Two-Hearts Feb 11, 2025
b2973be
resolve conflicts
Two-Hearts Feb 11, 2025
31c1537
fix unit test
Two-Hearts Feb 11, 2025
13bfb9e
blob verify
Two-Hearts Feb 11, 2025
399b72a
resolve conflicts
Two-Hearts Feb 11, 2025
2e05eca
blob verify
Two-Hearts Feb 11, 2025
5e2dbbe
blob verify
Two-Hearts Feb 11, 2025
3b15156
blob verify
Two-Hearts Feb 12, 2025
b0f6bd7
blob verify
Two-Hearts Feb 12, 2025
db669c0
fix e2e test
Two-Hearts Feb 12, 2025
3e223d0
update
Two-Hearts Feb 12, 2025
e425c4d
fix e2e tests
Two-Hearts Feb 12, 2025
ea95204
update
Two-Hearts Feb 12, 2025
b9a0b46
update
Two-Hearts Feb 12, 2025
ace6b69
update
Two-Hearts Feb 13, 2025
cffbf3d
update
Two-Hearts Feb 13, 2025
0e906a4
update
Two-Hearts Feb 13, 2025
35d5e0c
update
Two-Hearts Feb 14, 2025
4c3571c
update
Two-Hearts Feb 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions cmd/notation/blob/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ func Cmd() *cobra.Command {
Short: "Commands for blob",
Long: "Sign, verify, inspect signatures of blob. Configure blob trust policy.",
}

command.AddCommand(
signCommand(nil),
verifyCommand(nil),
policy.Cmd(),
)

return command
}
3 changes: 2 additions & 1 deletion cmd/notation/blob/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation-go/log"
"github.com/notaryproject/notation/cmd/notation/internal/cmdutil"
"github.com/notaryproject/notation/cmd/notation/internal/signer"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/envelope"
"github.com/notaryproject/notation/internal/httputil"
Expand Down Expand Up @@ -138,7 +139,7 @@ func runBlobSign(command *cobra.Command, cmdOpts *blobSignOpts) error {
ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context())
logger := log.GetLogger(ctx)

blobSigner, err := cmd.GetSigner(ctx, &cmdOpts.SignerFlagOpts)
blobSigner, err := signer.GetSigner(ctx, &cmdOpts.SignerFlagOpts)
if err != nil {
return err
}
Expand Down
170 changes: 170 additions & 0 deletions cmd/notation/blob/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// 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 blob

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation/cmd/notation/internal/display"
"github.com/notaryproject/notation/cmd/notation/internal/option"
"github.com/notaryproject/notation/cmd/notation/internal/verifier"
"github.com/notaryproject/notation/internal/cmd"
"github.com/notaryproject/notation/internal/envelope"
"github.com/notaryproject/notation/internal/ioutil"
"github.com/spf13/cobra"
)

type blobVerifyOpts struct {
cmd.LoggingFlagOpts
option.Common
blobPath string
signaturePath string
pluginConfig []string
userMetadata []string
policyStatementName string
blobMediaType string
}

func verifyCommand(opts *blobVerifyOpts) *cobra.Command {
if opts == nil {
opts = &blobVerifyOpts{}
}
longMessage := `Verify a signature associated with a blob.

Prerequisite: added a certificate into trust store and created a trust policy.

Example - Verify a signature on a blob artifact:
notation blob verify --signature <signature_path> <blob_path>

Example - Verify the signature on a blob artifact with user metadata:
notation blob verify --user-metadata <metadata> --signature <signature_path> <blob_path>

Example - Verify the signature on a blob artifact with media type:
notation blob verify --media-type <media_type> --signature <signature_path> <blob_path>

Example - Verify the signature on a blob artifact using a policy statement name:
notation blob verify --policy-name <policy_name> --signature <signature_path> <blob_path>
`
command := &cobra.Command{
Use: "verify [flags] --signature <signature_path> <blob_path>",
Short: "Verify a signature associated with a blob",
Long: longMessage,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing path to the blob artifact: use `notation blob verify --help` to see what parameters are required")
}
opts.blobPath = args[0]
return nil
},
PreRunE: func(cmd *cobra.Command, args []string) error {
if opts.signaturePath == "" {
return errors.New("filepath of the signature cannot be empty")
}
if cmd.Flags().Changed("media-type") && opts.blobMediaType == "" {
return errors.New("--media-type is set but with empty value")
}

Check warning on line 81 in cmd/notation/blob/verify.go

View check run for this annotation

Codecov / codecov/patch

cmd/notation/blob/verify.go#L80-L81

Added lines #L80 - L81 were not covered by tests
opts.Common.Parse(cmd)
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return runVerify(cmd, opts)
},
}
opts.LoggingFlagOpts.ApplyFlags(command.Flags())
command.Flags().StringVar(&opts.signaturePath, "signature", "", "filepath of the signature to be verified")
command.Flags().StringArrayVar(&opts.pluginConfig, "plugin-config", nil, "{key}={value} pairs that are passed as it is to a plugin, if the verification is associated with a verification plugin, refer plugin documentation to set appropriate values")
command.Flags().StringVar(&opts.blobMediaType, "media-type", "", "media type of the blob to verify")
command.Flags().StringVar(&opts.policyStatementName, "policy-name", "", "policy name to verify against. If not provided, the global policy is used if exists")
cmd.SetPflagUserMetadata(command.Flags(), &opts.userMetadata, cmd.PflagUserMetadataVerifyUsage)
command.MarkFlagRequired("signature")
return command
}

func runVerify(command *cobra.Command, cmdOpts *blobVerifyOpts) error {
// set log level
ctx := cmdOpts.LoggingFlagOpts.InitializeLogger(command.Context())

// initialize
displayHandler := display.NewBlobVerifyHandler(cmdOpts.Printer)
blobFile, err := os.Open(cmdOpts.blobPath)
if err != nil {
return err
}
defer blobFile.Close()

signatureBytes, err := os.ReadFile(cmdOpts.signaturePath)
if err != nil {
return err
}
blobVerifier, err := verifier.GetBlobVerifier(ctx)
if err != nil {
return err
}

// set up verification plugin config
pluginConfigs, err := cmd.ParseFlagMap(cmdOpts.pluginConfig, cmd.PflagPluginConfig.Name)
if err != nil {
return err
}

// set up user metadata
userMetadata, err := cmd.ParseFlagMap(cmdOpts.userMetadata, cmd.PflagUserMetadata.Name)
if err != nil {
return err
}
signatureMediaType, err := parseSignatureMediaType(cmdOpts.signaturePath)
if err != nil {
return err
}
verifyBlobOpts := notation.VerifyBlobOptions{
BlobVerifierVerifyOptions: notation.BlobVerifierVerifyOptions{
SignatureMediaType: signatureMediaType,
PluginConfig: pluginConfigs,
UserMetadata: userMetadata,
TrustPolicyName: cmdOpts.policyStatementName,
},
ContentMediaType: cmdOpts.blobMediaType,
}
_, outcome, err := notation.VerifyBlob(ctx, blobVerifier, blobFile, signatureBytes, verifyBlobOpts)
outcomes := []*notation.VerificationOutcome{outcome}
err = ioutil.ComposeBlobVerificationFailurePrintout(outcomes, cmdOpts.blobPath, err)
if err != nil {
return err
}
displayHandler.OnVerifySucceeded(outcomes, cmdOpts.blobPath)
return displayHandler.Render()
}

// parseSignatureMediaType returns the media type of the signature file.
// `application/jose+json` and `application/cose` are supported.
func parseSignatureMediaType(signaturePath string) (string, error) {
signatureFileName := filepath.Base(signaturePath)
if strings.ToLower(filepath.Ext(signatureFileName)) != ".sig" {
return "", fmt.Errorf("invalid signature filename %s. The file extension must be .sig", signatureFileName)
}
sigFilenameArr := strings.Split(signatureFileName, ".")

// a valid signature file name has at least 3 parts.
// for example, `myFile.jws.sig`
if len(sigFilenameArr) < 3 {
return "", fmt.Errorf("invalid signature filename %s. A valid signature file name must contain signature format and .sig file extension", signatureFileName)
}
sigFormat := sigFilenameArr[len(sigFilenameArr)-2]
return envelope.GetEnvelopeMediaType(strings.ToLower(sigFormat))
}
73 changes: 73 additions & 0 deletions cmd/notation/blob/verify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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 blob

import (
"reflect"
"testing"
)

func TestVerifyCommand_BasicArgs(t *testing.T) {
opts := &blobVerifyOpts{}
command := verifyCommand(opts)
expected := &blobVerifyOpts{
blobPath: "blob_path",
signaturePath: "sig_path",
}
if err := command.ParseFlags([]string{
expected.blobPath,
"--signature", expected.signaturePath}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
t.Fatalf("Parse args failed: %v", err)
}
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect blob verify opts: %v, got: %v", expected, opts)
}
}

func TestVerifyCommand_MoreArgs(t *testing.T) {
opts := &blobVerifyOpts{}
command := verifyCommand(opts)
expected := &blobVerifyOpts{
blobPath: "blob_path",
signaturePath: "sig_path",
pluginConfig: []string{"key1=val1", "key2=val2"},
}
if err := command.ParseFlags([]string{
expected.blobPath,
"--signature", expected.signaturePath,
"--plugin-config", "key1=val1",
"--plugin-config", "key2=val2",
}); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := command.Args(command, command.Flags().Args()); err != nil {
t.Fatalf("Parse args failed: %v", err)
}
if !reflect.DeepEqual(*expected, *opts) {
t.Fatalf("Expect verify opts: %v, got: %v", expected, opts)
}
}

func TestVerifyCommand_MissingArgs(t *testing.T) {
cmd := verifyCommand(nil)
if err := cmd.ParseFlags(nil); err != nil {
t.Fatalf("Parse Flag failed: %v", err)
}
if err := cmd.Args(cmd, cmd.Flags().Args()); err == nil {
t.Fatal("Parse Args expected error, but ok")
}
}
8 changes: 7 additions & 1 deletion cmd/notation/internal/display/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,13 @@ func NewInpsectHandler(printer *output.Printer, format option.Format) (metadata.
}

// NewVerifyHandler creates a new metadata VerifyHandler for printing
// veriifcation result and warnings.
// verification result and warnings.
func NewVerifyHandler(printer *output.Printer) metadata.VerifyHandler {
return text.NewVerifyHandler(printer)
}

// NewBlobVerifyHandler creates a new metadata BlobVerifyHandler for printing
// blob verification result and warnings.
func NewBlobVerifyHandler(printer *output.Printer) metadata.BlobVerifyHandler {
return text.NewBlobVerifyHandler(printer)
}
13 changes: 13 additions & 0 deletions cmd/notation/internal/display/metadata/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,16 @@ type VerifyHandler interface {
// outcomes must not be nil or empty.
OnVerifySucceeded(outcomes []*notation.VerificationOutcome, digestReference string)
}

// BlobVerifyHandler is a handler for rendering metadata information of
// blob verification outcome.
//
// It only supports text format for now.
type BlobVerifyHandler interface {
Renderer

// OnVerifySucceeded sets the successful verification result for the handler.
//
// outcomes must not be nil or empty.
OnVerifySucceeded(outcomes []*notation.VerificationOutcome, blobPath string)
}
48 changes: 48 additions & 0 deletions cmd/notation/internal/display/metadata/text/blobverify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// 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 text

import (
"github.com/notaryproject/notation-go"
"github.com/notaryproject/notation/cmd/notation/internal/display/output"
)

// BlobVerifyHandler is a handler for rendering output for blob verify command
// in human-readable format.
// It implements metadata/BlobVerifyHandler.
type BlobVerifyHandler struct {
printer *output.Printer
outcome *notation.VerificationOutcome
blobPath string
}

// NewBlobVerifyHandler creates a new BlobVerifyHandler.
func NewBlobVerifyHandler(printer *output.Printer) *BlobVerifyHandler {
return &BlobVerifyHandler{
printer: printer,
}
}

// OnVerifySucceeded sets the successful verification result for the handler.
//
// outcomes must not be nil or empty.
func (h *BlobVerifyHandler) OnVerifySucceeded(outcomes []*notation.VerificationOutcome, blobPath string) {
h.outcome = outcomes[0]
h.blobPath = blobPath
}

// Render prints out the verification results in human-readable format.
func (h *BlobVerifyHandler) Render() error {
return printVerificationSuccess(h.printer, h.outcome, h.blobPath, false)
}
Loading
Loading