diff --git a/hack/configapi/delete.go b/hack/configapi/delete.go index 1a562072a..e3e2c2000 100644 --- a/hack/configapi/delete.go +++ b/hack/configapi/delete.go @@ -20,9 +20,10 @@ import ( // newDeleteCmd creates the delete command. func newDeleteCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "delete", - Short: "delete a specific version from the config api", - RunE: runDelete, + Use: "delete", + Short: "delete a specific version from the config api", + PreRunE: envCheck, + RunE: runDelete, } cmd.Flags().StringP("version", "v", "", "Name of the version to delete (without .json suffix)") must(cmd.MarkFlagRequired("version")) diff --git a/hack/configapi/main.go b/hack/configapi/main.go index 77d44086d..b3e9ab862 100644 --- a/hack/configapi/main.go +++ b/hack/configapi/main.go @@ -21,9 +21,9 @@ import ( "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/logger" + "github.com/edgelesssys/constellation/v2/internal/staticupload" "go.uber.org/zap" - "github.com/edgelesssys/constellation/v2/internal/staticupload" "github.com/spf13/cobra" ) diff --git a/internal/api/client/client.go b/internal/api/client/client.go index a6e90a3ed..bdc126847 100644 --- a/internal/api/client/client.go +++ b/internal/api/client/client.go @@ -33,7 +33,7 @@ import ( "encoding/json" "errors" "fmt" - "path" + "strings" "time" s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" @@ -266,7 +266,7 @@ func SignAndUpdate(ctx context.Context, c *Client, obj APIObject, signer sigstor } signature := signature{ - Signed: obj, + Signed: obj.JSONPath(), Signature: dataSignature, } @@ -287,7 +287,7 @@ func DeleteWithSignature(ctx context.Context, c *Client, obj APIObject) error { return fmt.Errorf("deleting %T: %w", obj, err) } - sig := signature{Signed: obj} + sig := signature{Signed: obj.JSONPath()} if err := Delete(ctx, c, sig); err != nil { return fmt.Errorf("deleting %T: %w", sig, err) } @@ -350,25 +350,29 @@ type uploadClient interface { // CloseFunc is a function that closes the client. type CloseFunc func(ctx context.Context) error -// signature wraps another APIObject and adds a signature to it. +// signature manages the signature of a object saved at location 'Signed'. type signature struct { // Signed is the object that is signed. - Signed APIObject + Signed string `json:"signed"` // Signature is the signature of `Signed`. Signature []byte `json:"signature"` } // JSONPath returns the path to the JSON file for the request to the config api. func (s signature) JSONPath() string { - return path.Join(s.Signed.JSONPath() + ".sig") + return s.Signed + ".sig" } // ValidateRequest validates the request. func (s signature) ValidateRequest() error { - return s.Signed.ValidateRequest() + if !strings.HasSuffix(s.Signed, ".json") { + return errors.New("signed object missing .json suffix") + } + + return nil } -// Validate is a No-Op at the moment. +// Validate checks that the signature is base64 encoded. func (s signature) Validate() error { - return s.Signed.Validate() + return sigstore.IsBase64([]byte(s.Signature)) } diff --git a/internal/api/fetcher/BUILD.bazel b/internal/api/fetcher/BUILD.bazel index 089b96924..3718807da 100644 --- a/internal/api/fetcher/BUILD.bazel +++ b/internal/api/fetcher/BUILD.bazel @@ -5,5 +5,8 @@ go_library( srcs = ["fetcher.go"], importpath = "github.com/edgelesssys/constellation/v2/internal/api/fetcher", visibility = ["//:__subpackages__"], - deps = ["//internal/sigstore"], + deps = [ + "//internal/constants", + "//internal/sigstore", + ], ) diff --git a/internal/api/fetcher/fetcher.go b/internal/api/fetcher/fetcher.go index 0075c366c..f00181484 100644 --- a/internal/api/fetcher/fetcher.go +++ b/internal/api/fetcher/fetcher.go @@ -20,9 +20,12 @@ package fetcher import ( "context" "encoding/json" + "errors" "fmt" "net/http" + "strings" + "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/sigstore" ) @@ -87,7 +90,11 @@ func FetchAndVerify[T apiObject](ctx context.Context, c HTTPClient, obj T, cosig return fetchedObj, fmt.Errorf("marshalling object: %w", err) } - signature, err := Fetch(ctx, c, signature{Signed: fetchedObj}) + url, err := obj.URL() + if err != nil { + return fetchedObj, fmt.Errorf("getting signed URL: %w", err) + } + signature, err := Fetch(ctx, c, signature{Signed: url}) if err != nil { return fetchedObj, fmt.Errorf("fetching signature: %w", err) } @@ -123,29 +130,33 @@ type apiObject interface { URL() (string, error) } -// signature wraps another APIObject and adds a signature to it. +// signature manages the signature of a object saved at location 'Signed'. type signature struct { // Signed is the object that is signed. - Signed apiObject `json:"-"` + Signed string `json:"signed"` // Signature is the signature of `Signed`. Signature []byte `json:"signature"` } // URL returns the URL for the request to the config api. func (s signature) URL() (string, error) { - url, err := s.Signed.URL() - if err != nil { - return "", err - } - return url + ".sig", nil + return s.Signed + ".sig", nil } // ValidateRequest validates the request. func (s signature) ValidateRequest() error { - return s.Signed.ValidateRequest() + if !strings.HasPrefix(s.Signed, constants.CDNRepositoryURL) { + return errors.New("signed object missing CDN URL prefix") + } + + if !strings.HasSuffix(s.Signed, ".json") { + return errors.New("signed object missing .json suffix") + } + + return nil } -// Validate is a No-Op at the moment. +// Validate checks that the signature is base64 encoded. func (s signature) Validate() error { - return s.Signed.Validate() + return sigstore.IsBase64(s.Signature) } diff --git a/internal/sigstore/verify.go b/internal/sigstore/verify.go index 11f50703e..a13a3285e 100644 --- a/internal/sigstore/verify.go +++ b/internal/sigstore/verify.go @@ -63,3 +63,10 @@ func (c CosignVerifier) VerifySignature(content, signature []byte) error { return nil } + +// IsBase64 checks if the given byte slice is base64 encoded. +func IsBase64(signature []byte) error { + target := make([]byte, base64.StdEncoding.DecodedLen(len(signature))) + _, err := base64.StdEncoding.Decode(target, signature) + return err +} diff --git a/internal/sigstore/verify_test.go b/internal/sigstore/verify_test.go index 0b37e7ab1..ef7952b7a 100644 --- a/internal/sigstore/verify_test.go +++ b/internal/sigstore/verify_test.go @@ -89,3 +89,33 @@ gCDlEzkuOCybCHf+q766bve799L7Y5y5oRsHY1MrUCUwYF/tL7Sg7EYMsA== }) } } + +func TestIsBase64(t *testing.T) { + tests := map[string]struct { + signature []byte + wantErr bool + }{ + "valid base64": { + signature: []byte("SGVsbG8gV29ybGQ="), + wantErr: false, + }, + "invalid base64": { + signature: []byte("not base64"), + wantErr: true, + }, + "empty input": { + signature: []byte{}, + wantErr: false, + }, + } + + for tc, tt := range tests { + t.Run(tc, func(t *testing.T) { + err := IsBase64(tt.signature) + if (err != nil) != tt.wantErr { + t.Errorf("IsBase64() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } +}