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

Add support for verifying root checksum in cosign initialize #3953

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 <sha512>`,
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 Cosing release soon after TODO. Please provide the initial root checksum " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"a Cosing release soon after TODO. Please provide the initial root checksum " +
"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 sha512 by default, can be changed to sha256 by providing sha256:<checksum>")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we flip this and have sha256 as the default? sha512 is probably overkill.

}
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 := sha512.New()
checksumValue := checksumParts[len(checksumParts)-1]
if len(checksumParts) == 2 {
switch checksumParts[0] {
case "sha512": // the default set above
case "sha256":
checksumAlgo = sha256.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 sha512
actual, err := LoadFileOrURLWithChecksum(
server.URL,
"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)
}

// override checksum algo to sha256
actual, err = LoadFileOrURLWithChecksum(
server.URL,
"sha256: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)
}

// 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)
}
}
Loading