Skip to content

Commit

Permalink
Add support for verifying root checksum in cosign initialize (#3953)
Browse files Browse the repository at this point in the history
* Add support for verifying root checksum in cosign initialize

Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>

* Regenerate docs

Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>

* Update cmd/cosign/cli/options/deprecate.go

Co-authored-by: Hayden B <hblauzvern@google.com>
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>

* Use sha256 by default with the option to switch to sha512

Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>

---------

Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
Co-authored-by: Hayden B <hblauzvern@google.com>
  • Loading branch information
bkabrda and haydentherapper authored Jan 7, 2025
1 parent 8288480 commit f056c3e
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 13 deletions.
11 changes: 7 additions & 4 deletions cmd/cosign/cli/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,22 @@ Any updated TUF repository will be written to $HOME/.sigstore/root/.
Trusted keys and certificate used in cosign verification (e.g. verifying Fulcio issued certificates
with Fulcio root CA) are pulled form the trusted metadata.`,
Example: `cosign initialize -mirror <url> -out <file>
Example: `cosign initialize --mirror <url> --out <file>
# initialize root with distributed root keys, default mirror, and default out path.
cosign initialize
# initialize with an out-of-band root key file, using the default mirror.
cosign initialize -root <url>
cosign initialize --root <url>
# initialize with an out-of-band root key file and custom repository mirror.
cosign initialize -mirror <url> -root <url>`,
cosign initialize --mirror <url> --root <url>
# initialize with an out-of-band root key file and custom repository mirror while verifying root checksum.
cosign initialize --mirror <url> --root <url> --root-checksum <sha256>`,
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, _ []string) error {
return initialize.DoInitialize(cmd.Context(), o.Root, o.Mirror)
return initialize.DoInitializeWithRootChecksum(cmd.Context(), o.Root, o.Mirror, o.RootChecksum)
},
}

Expand Down
23 changes: 22 additions & 1 deletion cmd/cosign/cli/initialize/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,38 @@ import (
_ "embed" // To enable the `go:embed` directive.
"encoding/json"
"fmt"
"os"
"strings"

"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/pkg/blob"
"github.com/sigstore/sigstore/pkg/tuf"
)

func DoInitialize(ctx context.Context, root, mirror string) error {
return doInitialize(ctx, root, mirror, "", true)
}

func DoInitializeWithRootChecksum(ctx context.Context, root, mirror, rootChecksum string) error {
return doInitialize(ctx, root, mirror, rootChecksum, false)
}

func doInitialize(ctx context.Context, root, mirror, rootChecksum string, forceSkipChecksumValidation bool) error {
// Get the initial trusted root contents.
var rootFileBytes []byte
var err error
if root != "" {
rootFileBytes, err = blob.LoadFileOrURL(root)
if !forceSkipChecksumValidation {
if rootChecksum == "" && (strings.HasPrefix(root, "http://") || strings.HasPrefix(root, "https://")) {
fmt.Fprintln(os.Stderr, options.RootWithoutChecksumDeprecation)
}
}
verifyChecksum := !forceSkipChecksumValidation && (rootChecksum != "")
if verifyChecksum {
rootFileBytes, err = blob.LoadFileOrURLWithChecksum(root, rootChecksum)
} else {
rootFileBytes, err = blob.LoadFileOrURL(root)
}
if err != nil {
return err
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/cosign/cli/options/deprecate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ const SBOMAttachmentDeprecation = "WARNING: SBOM attachments are deprecated " +
"and support will be removed in a Cosign release soon after 2024-02-22 " +
"(see https://github.com/sigstore/cosign/issues/2755). " +
"Instead, please use SBOM attestations."

const RootWithoutChecksumDeprecation = "WARNING: Fetching initial root from URL " +
"without providing its checksum is deprecated and will be disallowed in " +
"a future Cosign release. Please provide the initial root checksum " +
"via the --root-checksum argument."
8 changes: 6 additions & 2 deletions cmd/cosign/cli/options/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ import (

// InitializeOptions is the top level wrapper for the initialize command.
type InitializeOptions struct {
Mirror string
Root string
Mirror string
Root string
RootChecksum string
}

var _ Interface = (*InitializeOptions)(nil)
Expand All @@ -36,4 +37,7 @@ func (o *InitializeOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.Root, "root", "",
"path to trusted initial root. defaults to embedded root")
_ = cmd.Flags().SetAnnotation("root", cobra.BashCompSubdirsInDir, []string{})

cmd.Flags().StringVar(&o.RootChecksum, "root-checksum", "",
"checksum of the initial root, required if root is downloaded via http(s). expects sha256 by default, can be changed to sha512 by providing sha512:<checksum>")
}
16 changes: 10 additions & 6 deletions doc/cosign_initialize.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions pkg/blob/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package blob

import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"io"
"net/http"
Expand Down Expand Up @@ -72,3 +75,35 @@ func LoadFileOrURL(fileRef string) ([]byte, error) {
}
return raw, nil
}

func LoadFileOrURLWithChecksum(fileRef string, checksum string) ([]byte, error) {
checksumParts := strings.Split(checksum, ":")
if len(checksumParts) >= 3 {
return nil, fmt.Errorf("wrong checksum input format, must have at most 1 colon: %s", checksum)
}

checksumAlgo := sha256.New()
checksumValue := checksumParts[len(checksumParts)-1]
if len(checksumParts) == 2 {
switch checksumParts[0] {
case "sha256": // the default set above
case "sha512":
checksumAlgo = sha512.New()
default:
return nil, fmt.Errorf("unsupported checksum algorithm: %s", checksumParts[0])
}
}

fileContent, err := LoadFileOrURL(fileRef)
if err != nil {
return nil, err
}

checksumAlgo.Write(fileContent)
computedChecksum := hex.EncodeToString(checksumAlgo.Sum(nil))
if computedChecksum != checksumValue {
return nil, fmt.Errorf("incorrect checksum for file %s: expected %s but got %s", fileRef, checksumValue, computedChecksum)
}

return fileContent, nil
}
50 changes: 50 additions & 0 deletions pkg/blob/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"
"path"
"runtime"
"strings"
"testing"
)

Expand Down Expand Up @@ -97,3 +98,52 @@ func TestLoadURL(t *testing.T) {
t.Error("LoadFileOrURL(): expected error for invalid scheme")
}
}

func TestLoadURLWithChecksum(t *testing.T) {
data := []byte("test")

server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.Write(data)
}))
defer server.Close()

// default behavior with sha256
actual, err := LoadFileOrURLWithChecksum(
server.URL,
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
)
if err != nil {
t.Errorf("Reading from HTTP failed: %v", err)
} else if !bytes.Equal(actual, data) {
t.Errorf("LoadFileOrURL(HTTP) = '%s'; want '%s'", actual, data)
}

// override checksum algo to sha512
actual, err = LoadFileOrURLWithChecksum(
server.URL,
"sha512:ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff",
)
if err != nil {
t.Errorf("Reading from HTTP failed: %v", err)
} else if !bytes.Equal(actual, data) {
t.Errorf("LoadFileOrURL(HTTP) = '%s'; want '%s'", actual, data)
}

// ensure it fails with the wrong checksum
_, err = LoadFileOrURLWithChecksum(
server.URL,
"certainly not a correct checksum value",
)
if err == nil || !strings.Contains(err.Error(), "incorrect checksum") {
t.Errorf("Expected an 'incorrect checksum' error, got: %v", err)
}

// ensure it fails with incorrect algorithm
_, err = LoadFileOrURLWithChecksum(
server.URL,
"sha321123:foobar",
)
if err == nil || !strings.Contains(err.Error(), "unsupported checksum") {
t.Errorf("Expected an 'unsupported checksum' error, got: %v", err)
}
}

0 comments on commit f056c3e

Please sign in to comment.