Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ cross:
golangci-lint:
rm -f $(GOLANGCI_LINT_BIN) || :
set -e ;\
GOBIN=$(GOLANGCI_LINT_DIR) $(GOEXE) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 ;\
GOBIN=$(GOLANGCI_LINT_DIR) $(GOEXE) install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.2.2 ;\

lint: golangci-lint ## Run golangci-lint linter
$(GOLANGCI_LINT_BIN) run -n
Expand Down
111 changes: 65 additions & 46 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"strings"
"time"

intotov1 "github.com/in-toto/attestation/go/v1"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
Expand Down Expand Up @@ -60,6 +61,7 @@ type AttestBlobCommand struct {

ArtifactHash string

StatementPath string
PredicatePath string
PredicateType string

Expand All @@ -80,8 +82,8 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
return &options.KeyParseError{}
}

if c.PredicatePath == "" {
return fmt.Errorf("predicate cannot be empty")
if options.NOf(c.PredicatePath, c.StatementPath) != 1 {
return fmt.Errorf("one of --predicate or --statement must be set")
}

if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" {
Expand All @@ -98,38 +100,6 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
return errors.New("expected either new bundle or an rfc3161-timestamp path when using a TSA server")
}

var artifact []byte
var hexDigest string
var err error

if c.ArtifactHash == "" {
if artifactPath == "-" {
artifact, err = io.ReadAll(os.Stdin)
} else {
fmt.Fprintln(os.Stderr, "Using payload from:", artifactPath)
artifact, err = os.ReadFile(filepath.Clean(artifactPath))
}
if err != nil {
return err
}
}

if c.ArtifactHash == "" {
digest, _, err := signature.ComputeDigestForSigning(bytes.NewReader(artifact), crypto.SHA256, []crypto.Hash{crypto.SHA256, crypto.SHA384})
if err != nil {
return err
}
hexDigest = strings.ToLower(hex.EncodeToString(digest))
} else {
hexDigest = c.ArtifactHash
}

predicate, err := predicateReader(c.PredicatePath)
if err != nil {
return fmt.Errorf("getting predicate reader: %w", err)
}
defer predicate.Close()

sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
if err != nil {
return fmt.Errorf("getting signer: %w", err)
Expand All @@ -139,19 +109,60 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error

base := path.Base(artifactPath)

sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
Predicate: predicate,
Type: c.PredicateType,
Digest: hexDigest,
Repo: base,
})
if err != nil {
return err
}
var payload []byte

payload, err := json.Marshal(sh)
if err != nil {
return err
if c.StatementPath != "" {
fmt.Fprintln(os.Stderr, "Using statement from:", c.StatementPath)
payload, err = os.ReadFile(filepath.Clean(c.StatementPath))
if err != nil {
return fmt.Errorf("could not read statement: %w", err)
}
if _, err := validateStatement(payload); err != nil {
return fmt.Errorf("invalid statement: %w", err)
}

} else {
var artifact []byte
var hexDigest string
if c.ArtifactHash == "" {
if artifactPath == "-" {
artifact, err = io.ReadAll(os.Stdin)
} else {
fmt.Fprintln(os.Stderr, "Using payload from:", artifactPath)
artifact, err = os.ReadFile(filepath.Clean(artifactPath))
}
if err != nil {
return err
}
}

if c.ArtifactHash == "" {
digest, _, err := signature.ComputeDigestForSigning(bytes.NewReader(artifact), crypto.SHA256, []crypto.Hash{crypto.SHA256, crypto.SHA384})
if err != nil {
return err
}
hexDigest = strings.ToLower(hex.EncodeToString(digest))
} else {
hexDigest = c.ArtifactHash
}
predicate, err := predicateReader(c.PredicatePath)
if err != nil {
return fmt.Errorf("getting predicate reader: %w", err)
}
defer predicate.Close()
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
Predicate: predicate,
Type: c.PredicateType,
Digest: hexDigest,
Repo: base,
})
if err != nil {
return err
}
payload, err = json.Marshal(sh)
if err != nil {
return err
}
}

sig, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx))
Expand Down Expand Up @@ -363,3 +374,11 @@ func makeNewBundle(sv *sign.SignerVerifier, rekorEntry *models.LogEntryAnon, pay

return contents, nil
}

func validateStatement(payload []byte) (string, error) {
var statement *intotov1.Statement
if err := json.Unmarshal(payload, &statement); err != nil {
return "", fmt.Errorf("invalid statement: %w", err)
}
return statement.PredicateType, nil
}
34 changes: 34 additions & 0 deletions cmd/cosign/cli/attest/attest_blob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/sigstore/cosign/v2/test"
"github.com/sigstore/sigstore/pkg/signature"
"github.com/sigstore/sigstore/pkg/signature/dsse"
"github.com/stretchr/testify/assert"
)

// TestAttestBlobCmdLocalKeyAndSk verifies the AttestBlobCmd returns an error
Expand Down Expand Up @@ -307,3 +308,36 @@ func TestBadRekorEntryType(t *testing.T) {
})
}
}

func TestStatementPath(t *testing.T) {
ctx := context.Background()
td := t.TempDir()

keys, _ := cosign.GenerateKeyPair(nil)
keyRef := writeFile(t, td, string(keys.PrivateBytes), "key.pem")

statement := `{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "foo",
"digest": {
"sha256": "deadbeef"
}
}
],
"predicateType": "https://example.com/CustomPredicate/v1",
"predicate": {
"foo": "bar"
}
}`
statementPath := writeFile(t, td, statement, "statement.json")

at := AttestBlobCommand{
KeyOpts: options.KeyOpts{KeyRef: keyRef},
StatementPath: statementPath,
RekorEntryType: "dsse",
}
err := at.Exec(ctx, "")
assert.NoError(t, err)
}
11 changes: 9 additions & 2 deletions cmd/cosign/cli/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ func AttestBlob() *cobra.Command {
# supply attestation via stdin
echo <PAYLOAD> | cosign attest-blob --predicate - --yes`,

Args: cobra.ExactArgs(1),
PersistentPreRun: options.BindViper,
RunE: func(cmd *cobra.Command, args []string) error {
if o.Predicate.Statement == "" && len(args) != 1 {
return cobra.ExactArgs(1)(cmd, args)
}
oidcClientSecret, err := o.OIDC.ClientSecret()
if err != nil {
return err
Expand Down Expand Up @@ -100,13 +102,18 @@ func AttestBlob() *cobra.Command {
TlogUpload: o.TlogUpload,
PredicateType: o.Predicate.Type,
PredicatePath: o.Predicate.Path,
StatementPath: o.Predicate.Statement,
OutputSignature: o.OutputSignature,
OutputAttestation: o.OutputAttestation,
OutputCertificate: o.OutputCertificate,
Timeout: ro.Timeout,
RekorEntryType: o.RekorEntryType,
}
return v.Exec(cmd.Context(), args[0])
var artifactPath string
if len(args) == 1 {
artifactPath = args[0]
}
return v.Exec(cmd.Context(), artifactPath)
},
}
o.AddFlags(cmd)
Expand Down
10 changes: 7 additions & 3 deletions cmd/cosign/cli/options/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ func ParsePredicateType(t string) (string, error) {
// PredicateLocalOptions is the wrapper for predicate related options.
type PredicateLocalOptions struct {
PredicateOptions
Path string
Path string
Statement string
}

var _ Interface = (*PredicateLocalOptions)(nil)
Expand All @@ -92,8 +93,11 @@ func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) {

cmd.Flags().StringVar(&o.Path, "predicate", "",
"path to the predicate file.")
_ = cmd.MarkFlagFilename("predicate", sbomExts...)
_ = cmd.MarkFlagRequired("predicate")

cmd.Flags().StringVar(&o.Statement, "statement", "",
"path to the statement file.")

cmd.MarkFlagsOneRequired("predicate", "statement")
}

// PredicateRemoteOptions is the wrapper for remote predicate related options.
Expand Down
11 changes: 10 additions & 1 deletion cmd/cosign/cli/options/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ type VerifyBlobAttestationOptions struct {
CommonVerifyOptions CommonVerifyOptions

RFC3161TimestampPath string

Digest string
DigestAlg string
}

var _ Interface = (*VerifyBlobOptions)(nil)
Expand All @@ -257,8 +260,14 @@ func (o *VerifyBlobAttestationOptions) AddFlags(cmd *cobra.Command) {
"path to bundle FILE")

cmd.Flags().BoolVar(&o.CheckClaims, "check-claims", true,
"if true, verifies the provided blob's sha256 digest exists as an in-toto subject within the attestation. If false, only the DSSE envelope is verified.")
"if true, verifies the digest exists in the in-toto subject (using either the provided digest and digest algorithm or the provided blob's sha256 digest). If false, only the DSSE envelope is verified.")

cmd.Flags().StringVar(&o.RFC3161TimestampPath, "rfc3161-timestamp", "",
"path to RFC3161 timestamp FILE")

cmd.Flags().StringVar(&o.Digest, "digest", "",
"Digest to use for verifying in-toto subject (instead of providing a blob)")

cmd.Flags().StringVar(&o.DigestAlg, "digestAlg", "",
"Digest algorithm to use for verifying in-toto subject (instead of providing a blob)")
}
6 changes: 4 additions & 2 deletions cmd/cosign/cli/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,12 @@ The blob may be specified as a path to a file.`,
IgnoreTlog: o.CommonVerifyOptions.IgnoreTlog,
UseSignedTimestamps: o.CommonVerifyOptions.UseSignedTimestamps,
TrustedRootPath: o.CommonVerifyOptions.TrustedRootPath,
Digest: o.Digest,
DigestAlg: o.DigestAlg,
}
// We only use the blob if we are checking claims.
if len(args) == 0 && o.CheckClaims {
return fmt.Errorf("no path to blob passed in, run `cosign verify-blob-attestation -h` for more help")
if o.CheckClaims && len(args) == 0 && (o.Digest == "" || o.DigestAlg == "") {
return fmt.Errorf("must provide path to blob or digest and digestAlg; run `cosign verify-blob-attestation -h` for more help")
}
var path string
if len(args) > 0 {
Expand Down
63 changes: 40 additions & 23 deletions cmd/cosign/cli/verify/verify_blob_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ type VerifyBlobAttestationCommand struct {

SignaturePath string // Path to the signature
UseSignedTimestamps bool

Digest string
DigestAlg string
}

// Exec runs the verification command
Expand Down Expand Up @@ -155,30 +158,44 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
var h v1.Hash
var digest []byte
if c.CheckClaims {
// Get the actual digest of the blob
var payload internal.HashReader
f, err := os.Open(filepath.Clean(artifactPath))
if err != nil {
return err
}
defer f.Close()
fileInfo, err := f.Stat()
if err != nil {
return err
}
err = payloadsize.CheckSize(uint64(fileInfo.Size()))
if err != nil {
return err
}
if artifactPath != "" {
if c.Digest != "" && c.DigestAlg != "" {
ui.Warnf(ctx, "Ignoring provided digest and digestAlg in favor of provided blob")
}
// Get the actual digest of the blob
var payload internal.HashReader
f, err := os.Open(filepath.Clean(artifactPath))
if err != nil {
return err
}
defer f.Close()
fileInfo, err := f.Stat()
if err != nil {
return err
}
err = payloadsize.CheckSize(uint64(fileInfo.Size()))
if err != nil {
return err
}

payload = internal.NewHashReader(f, sha256.New())
if _, err := io.ReadAll(&payload); err != nil {
return err
}
digest = payload.Sum(nil)
h = v1.Hash{
Hex: hex.EncodeToString(digest),
Algorithm: "sha256",
payload = internal.NewHashReader(f, sha256.New())
if _, err := io.ReadAll(&payload); err != nil {
return err
}
digest = payload.Sum(nil)
h = v1.Hash{
Hex: hex.EncodeToString(digest),
Algorithm: "sha256",
}
} else if c.Digest != "" && c.DigestAlg != "" {
digest, err = hex.DecodeString(c.Digest)
if err != nil {
return fmt.Errorf("unable to decode provided digest: %w", err)
}
h = v1.Hash{
Hex: c.Digest,
Algorithm: c.DigestAlg,
}
}
co.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
}
Expand Down
Loading
Loading