Skip to content

Commit

Permalink
Add support for attesting with complete statement
Browse files Browse the repository at this point in the history
  • Loading branch information
codysoyland committed Feb 3, 2025
1 parent 37740f0 commit 6a2d714
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 40 deletions.
1 change: 1 addition & 0 deletions cmd/cosign/cli/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ func Attest() *cobra.Command {
CertPath: o.Cert,
CertChainPath: o.CertChain,
NoUpload: o.NoUpload,
StatementPath: o.Predicate.Statement,
PredicatePath: o.Predicate.Path,
PredicateType: o.Predicate.Type,
Replace: o.Replace,
Expand Down
54 changes: 37 additions & 17 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
_ "crypto/sha256" // for `crypto.SHA256`
"encoding/json"
"fmt"
"io"
"os"
"time"

Expand Down Expand Up @@ -74,6 +75,7 @@ type AttestCommand struct {
CertPath string
CertChainPath string
NoUpload bool
StatementPath string
PredicatePath string
PredicateType string
Replace bool
Expand Down Expand Up @@ -140,26 +142,44 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType)
dd := cremote.NewDupeDetector(sv)

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

sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
Predicate: predicate,
Type: c.PredicateType,
Digest: h.Hex,
Repo: digest.Repository.String(),
})
if err != nil {
return err
}
if c.StatementPath != "" {
statement, err := predicateReader(c.StatementPath)
if err != nil {
return fmt.Errorf("getting statement reader: %w", err)
}
defer statement.Close()
payload, err = io.ReadAll(statement)
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 {
predicate, err := predicateReader(c.PredicatePath)
if err != nil {
return fmt.Errorf("getting predicate reader: %w", err)
}
defer predicate.Close()

payload, err := json.Marshal(sh)
if err != nil {
return err
sh, err := attestation.GenerateStatement(attestation.GenerateOpts{
Predicate: predicate,
Type: c.PredicateType,
Digest: h.Hex,
Repo: digest.Repository.String(),
})
if err != nil {
return err
}

payload, err = json.Marshal(sh)
if err != nil {
return err
}
}

signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx))
if err != nil {
return fmt.Errorf("signing: %w", err)
Expand Down
66 changes: 46 additions & 20 deletions cmd/cosign/cli/attest/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (

"errors"

"github.com/in-toto/in-toto-golang/in_toto"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"google.golang.org/protobuf/encoding/protojson"

Expand Down Expand Up @@ -62,6 +63,7 @@ type AttestBlobCommand struct {

ArtifactHash string

StatementPath string
PredicatePath string
PredicateType string

Expand All @@ -82,8 +84,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("exactly one predicate or statement path must be set")
}

if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" {
Expand Down Expand Up @@ -126,12 +128,6 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
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 @@ -141,19 +137,41 @@ 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 != "" {
statement, err := predicateReader(c.StatementPath)
if err != nil {
return fmt.Errorf("getting statement reader: %w", err)
}
defer statement.Close()
payload, err = io.ReadAll(statement)
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 {
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 @@ -367,3 +385,11 @@ func makeNewBundle(sv *sign.SignerVerifier, rekorEntry *models.LogEntryAnon, pay

return contents, nil
}

func validateStatement(payload []byte) error {
var statement *in_toto.Statement
if err := json.Unmarshal(payload, &statement); err != nil {
return fmt.Errorf("invalid statement: %w", err)
}
return nil
}
5 changes: 4 additions & 1 deletion cmd/cosign/cli/attest_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,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 @@ -83,6 +85,7 @@ 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,
Expand Down
9 changes: 7 additions & 2 deletions cmd/cosign/cli/options/predicate.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,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 @@ -94,7 +95,11 @@ func (o *PredicateLocalOptions) AddFlags(cmd *cobra.Command) {

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

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

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

// PredicateRemoteOptions is the wrapper for remote predicate related options.
Expand Down

0 comments on commit 6a2d714

Please sign in to comment.