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

Define a new sigstore Bundle type to include additional verification data and materials #2422

Closed
wants to merge 2 commits into from
Closed
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
4 changes: 2 additions & 2 deletions cmd/cosign/cli/attest/attest.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (

type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)

func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*cbundle.Bundle, error) {
rekorBytes, err := sv.Bytes(ctx)
if err != nil {
return nil, err
Expand All @@ -62,7 +62,7 @@ func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string,
return nil, err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
return cbundle.EntryToBundle(entry), nil
return cbundle.EntryToBundle(entry, []byte{}, []byte{}, []byte{}, ""), nil
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use nil instead of []byte{}?

}

// nolint
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/sign/sign_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, regOpts options.Re
return nil, err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
signedPayload.Bundle = cbundle.EntryToBundle(entry)
signedPayload.Bundle = cbundle.EntryToBundle(entry, []byte{}, []byte{}, []byte{}, "")
}

// if bundle is specified, just do that and ignore the rest
Expand Down
8 changes: 4 additions & 4 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ type VerifyBlobCmd struct {
// nolint
func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
var cert *x509.Certificate
var bundle *bundle.RekorBundle
var bundle *bundle.Bundle

if !options.OneOf(c.KeyRef, c.Sk, c.CertRef) && !options.EnableExperimental() && c.BundlePath == "" {
return &options.PubKeyParseError{}
Expand Down Expand Up @@ -312,7 +312,7 @@ We recommend requesting the certificate/signature from the original signer of th
// clean up the args into CheckOpts or use KeyOpts here to resolve different KeyOpts.
func verifyBlob(ctx context.Context, co *cosign.CheckOpts,
blobBytes []byte, sig string, cert *x509.Certificate,
bundle *bundle.RekorBundle, e *models.LogEntryAnon) error {
bundle *bundle.Bundle, e *models.LogEntryAnon) error {
if cert != nil {
// This would have already be done in the main entrypoint, but do this for robustness.
var err error
Expand Down Expand Up @@ -504,7 +504,7 @@ func payloadBytes(blobRef string) ([]byte, error) {
return blobBytes, nil
}

func verifyRekorBundle(ctx context.Context, bundle *bundle.RekorBundle,
func verifyRekorBundle(ctx context.Context, bundle *bundle.Bundle,
blobBytes []byte, sig string, pubKeyBytes []byte) (*bundle.RekorPayload, error) {
if err := verifyBundleMatchesData(ctx, bundle, blobBytes, pubKeyBytes, []byte(sig)); err != nil {
return nil, err
Expand All @@ -530,7 +530,7 @@ func verifyRekorBundle(ctx context.Context, bundle *bundle.RekorBundle,
return &bundle.Payload, nil
}

func verifyBundleMatchesData(ctx context.Context, bundle *bundle.RekorBundle, blobBytes, certBytes, sigBytes []byte) error {
func verifyBundleMatchesData(ctx context.Context, bundle *bundle.Bundle, blobBytes, certBytes, sigBytes []byte) error {
eimpl, kind, apiVersion, err := unmarshalEntryImpl(bundle.Payload.Body.(string))
if err != nil {
return err
Expand Down
38 changes: 23 additions & 15 deletions cmd/cosign/cli/verify/verify_blob_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ func TestVerifyBlob(t *testing.T) {
co.RekorClient = &mClient
}

var bundle *bundle.RekorBundle
var bundle *bundle.Bundle
b, err := cosign.FetchLocalSignedPayloadFromPath(tt.bundlePath)
if err == nil && b.Bundle != nil {
bundle = b.Bundle
Expand Down Expand Up @@ -650,14 +650,18 @@ func makeLocalBundle(t *testing.T, rekorSigner signature.ECDSASignerVerifier,
b := cosign.LocalSignedPayload{
Base64Signature: base64.StdEncoding.EncodeToString(sig),
Cert: string(svBytes),
Bundle: &bundle.RekorBundle{
Payload: bundle.RekorPayload{
Body: e.Body,
IntegratedTime: *e.IntegratedTime,
LogIndex: *e.LogIndex,
LogID: *e.LogID,
Bundle: &bundle.Bundle{
VerificationData: bundle.VerificationData{
Payload: bundle.RekorPayload{
Body: e.Body,
IntegratedTime: *e.IntegratedTime,
LogIndex: *e.LogIndex,
LogID: *e.LogID,
},
TimestampVerificationData: bundle.TimestampVerificationData{
SignedEntryTimestamp: e.Verification.SignedEntryTimestamp,
},
},
SignedEntryTimestamp: e.Verification.SignedEntryTimestamp,
},
}

Expand Down Expand Up @@ -1263,13 +1267,17 @@ func createBundle(_ *testing.T, sig []byte, certPem []byte, logID string, integr
b := &cosign.LocalSignedPayload{
Base64Signature: base64.StdEncoding.EncodeToString(sig),
Cert: string(certPem),
Bundle: &bundle.RekorBundle{
SignedEntryTimestamp: []byte{},
Payload: bundle.RekorPayload{
LogID: logID,
IntegratedTime: integratedTime,
LogIndex: 1,
Body: rekorEntry,
Bundle: &bundle.Bundle{
VerificationData: bundle.VerificationData{
TimestampVerificationData: bundle.TimestampVerificationData{
SignedEntryTimestamp: []byte{},
},
Payload: bundle.RekorPayload{
LogID: logID,
IntegratedTime: integratedTime,
LogIndex: 1,
Body: rekorEntry,
},
},
},
}
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/cosign/rekor/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ import (

type tlogUploadFn func(*client.Rekor, []byte) (*models.LogEntryAnon, error)

func uploadToTlog(rekorBytes []byte, rClient *client.Rekor, upload tlogUploadFn) (*cbundle.RekorBundle, error) {
func uploadToTlog(rekorBytes []byte, rClient *client.Rekor, upload tlogUploadFn) (*cbundle.Bundle, error) {
entry, err := upload(rClient, rekorBytes)
if err != nil {
return nil, err
}
fmt.Fprintln(os.Stderr, "tlog entry created with index:", *entry.LogIndex)
return cbundle.EntryToBundle(entry), nil
return cbundle.EntryToBundle(entry, []byte{}, []byte{}, []byte{}, ""), nil
}

// signerWrapper calls a wrapped, inner signer then uploads either the Cert or Pub(licKey) of the results to Rekor, then adds the resulting `Bundle`
Expand Down
86 changes: 86 additions & 0 deletions pkg/cosign/bundle/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2022 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bundle

import (
"github.com/sigstore/rekor/pkg/generated/models"
)

type Bundle struct {
VerificationData
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the difference between VerificationData and VerificationMaterial? I wonder if we could come up with more descriptive names so it's more clear :)

Copy link
Contributor

Choose a reason for hiding this comment

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

It might be better to port that comment to the upstream bundle, but these are terms being used by those protos.
Data is extra auxiliary data for verification (timestamps, rekor entries), but verification material is the direct material (key hint, certificates)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am gonna port these and other questions to the upstream bundle. Thanks.

VerificationMaterial
}

// VerificationData contains extra data that can be used to verify things
// such as transparency logs and timestamped verifications.
type VerificationData struct {
// RekorPayload holds metadata about recording a Signature's ephemeral key to
// a Rekor transparency log.
// Use Payload instead of TransparencyLogEntry to keep backwards compatibility.
Payload RekorPayload
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I kept the Payload field instead of renaming it to TransparencyLogEntry, as specified in the protobuf spec, to keep backwards compatibility.


Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am awaiting feedback before adding other fields such as the messageSignature included in the spec.

Copy link
Contributor

Choose a reason for hiding this comment

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

Those fields overlap with LocalSignedPayload too. The top level fields in that would ideally be removed, or else it would open mismatch issues.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! That could easily happen.

// TimestampVerificationData holds metadata about a timestamped verification.
TimestampVerificationData
}

// VerificationMaterial captures details on the materials used to verify
// signatures or any additional timestamped verifications.
type VerificationMaterial struct {
// A chain of X.509 certificates.
CertBytes []byte
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd expect this to be a crypto/x509.Certificate

I guess really...is this struct a data format or an object? My preference is always to move parsing as early as possible so we don't have to handle parse errors in the middle of the program logic.


// PublicKeyIdentifier optional unauthenticated hint on which key to use.
PublicKeyIdentifier string
}

// TimestampVerificationData contains various timestamped data following RFC3161.
Copy link
Contributor

Choose a reason for hiding this comment

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

As we discussed in the protobuf-specs repo, SETs are not just timestamp verification data, hence why we went with this nested format - https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto#L53-L59

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I created one issue here sigstore/protobuf-specs#13. Hopefully we can get more ppl involved on this discussion.

type TimestampVerificationData struct {
// SignedEntryTimestamp holds metadata about a timestamped counter signature over the artifacts signature.
SignedEntryTimestamp []byte

// EntryTimestampAuthority contains the recorded entry from timestamp authority server.
EntryTimestampAuthority []byte
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer a name that includes RFC3161 rather than Timestamp Authority, as the former makes it clear what the format of the time record is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it makes sense. I like it.

}

func EntryToBundle(tLogEntry *models.LogEntryAnon, signedEntryTimestamp, entryTimestampAuthority, certBytes []byte, pubKeyID string) *Bundle {
Copy link
Contributor

Choose a reason for hiding this comment

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

Tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we need tests but I wanted to firstly get some feedback, as I expected things might change.

b := &Bundle{}
// If none of the verification data is configured then return nil
if (tLogEntry == nil || tLogEntry.Verification == nil) && len(entryTimestampAuthority) == 0 {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

why return nil over returning an empty bundle? id be worried about accidental dereferences later

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with you but tLogEntry == nil || tLogEntry.Verification == nil is reusing the old behavior that also returned a nil bundle.

}
// Add Transparency log entry and a signed timestamp value
if tLogEntry != nil && tLogEntry.Verification != nil {
b.Payload = RekorPayload{
Body: tLogEntry.Body,
IntegratedTime: *tLogEntry.IntegratedTime,
LogIndex: *tLogEntry.LogIndex,
LogID: *tLogEntry.LogID,
}
b.SignedEntryTimestamp = tLogEntry.Verification.SignedEntryTimestamp

if len(signedEntryTimestamp) > 0 {
b.SignedEntryTimestamp = signedEntryTimestamp
}
}
// Set the EntryTimestampAuthority from the timestamp authority server
if len(entryTimestampAuthority) > 0 {
b.EntryTimestampAuthority = entryTimestampAuthority
}
if len(certBytes) > 0 || pubKeyID != "" {
b.CertBytes = certBytes
b.PublicKeyIdentifier = pubKeyID
}
return b
}
24 changes: 1 addition & 23 deletions pkg/cosign/bundle/rekor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,11 @@

package bundle

import "github.com/sigstore/rekor/pkg/generated/models"

// RekorBundle holds metadata about recording a Signature's ephemeral key to
// RekorPayload holds metadata about recording a Signature's ephemeral key to
// a Rekor transparency log.
type RekorBundle struct {
SignedEntryTimestamp []byte
Payload RekorPayload
}

type RekorPayload struct {
Body interface{} `json:"body"`
IntegratedTime int64 `json:"integratedTime"`
LogIndex int64 `json:"logIndex"`
LogID string `json:"logID"`
}

func EntryToBundle(entry *models.LogEntryAnon) *RekorBundle {
if entry.Verification == nil {
return nil
}
return &RekorBundle{
SignedEntryTimestamp: entry.Verification.SignedEntryTimestamp,
Payload: RekorPayload{
Body: entry.Body,
IntegratedTime: *entry.IntegratedTime,
LogIndex: *entry.LogIndex,
LogID: *entry.LogID,
},
}
}
8 changes: 4 additions & 4 deletions pkg/cosign/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ type SignedPayload struct {
Payload []byte
Cert *x509.Certificate
Chain []*x509.Certificate
Bundle *bundle.RekorBundle
Bundle *bundle.Bundle
}

type LocalSignedPayload struct {
Base64Signature string `json:"base64Signature"`
Cert string `json:"cert,omitempty"`
Bundle *bundle.RekorBundle `json:"rekorBundle,omitempty"`
Base64Signature string `json:"base64Signature"`
Cert string `json:"cert,omitempty"`
Bundle *bundle.Bundle `json:"rekorBundle,omitempty"`
}

type Signatures struct {
Expand Down
12 changes: 8 additions & 4 deletions pkg/cosign/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ func signEntry(ctx context.Context, t *testing.T, signer signature.Signer, entry
return signature
}

func CreateTestBundle(ctx context.Context, t *testing.T, rekor signature.Signer, leaf []byte) *bundle.RekorBundle {
func CreateTestBundle(ctx context.Context, t *testing.T, rekor signature.Signer, leaf []byte) *bundle.Bundle {
// generate log ID according to rekor public key
pk, _ := rekor.PublicKey(nil)
keyID, _ := getLogID(pk)
Expand All @@ -211,9 +211,13 @@ func CreateTestBundle(ctx context.Context, t *testing.T, rekor signature.Signer,
}
// Sign with root.
signature := signEntry(ctx, t, rekor, pyld)
b := &bundle.RekorBundle{
SignedEntryTimestamp: strfmt.Base64(signature),
Payload: pyld,
b := &bundle.Bundle{
VerificationData: bundle.VerificationData{
Payload: pyld,
TimestampVerificationData: bundle.TimestampVerificationData{
SignedEntryTimestamp: strfmt.Base64(signature),
},
},
}
return b
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/oci/internal/signature/layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,12 +104,12 @@ func (s *sigLayer) Chain() ([]*x509.Certificate, error) {
}

// Bundle implements oci.Signature
func (s *sigLayer) Bundle() (*bundle.RekorBundle, error) {
func (s *sigLayer) Bundle() (*bundle.Bundle, error) {
val := s.desc.Annotations[BundleKey]
if val == "" {
return nil, nil
}
var b bundle.RekorBundle
var b bundle.Bundle
if err := json.Unmarshal([]byte(val), &b); err != nil {
return nil, fmt.Errorf("unmarshaling bundle: %w", err)
}
Expand Down
20 changes: 12 additions & 8 deletions pkg/oci/internal/signature/layer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestSignature(t *testing.T) {
wantCertErr error
wantChain int
wantChainErr error
wantBundle *bundle.RekorBundle
wantBundle *bundle.Bundle
wantBundleErr error
}{{
name: "just payload and signature",
Expand Down Expand Up @@ -152,13 +152,17 @@ func TestSignature(t *testing.T) {
},
},
wantSig: "blah",
wantBundle: &bundle.RekorBundle{
SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
Payload: bundle.RekorPayload{
Body: "REMOVED",
IntegratedTime: 1631646761,
LogIndex: 693591,
LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
wantBundle: &bundle.Bundle{
VerificationData: bundle.VerificationData{
Payload: bundle.RekorPayload{
Body: "REMOVED",
IntegratedTime: 1631646761,
LogIndex: 693591,
LogID: "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
},
TimestampVerificationData: bundle.TimestampVerificationData{
SignedEntryTimestamp: mustDecode("MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
},
},
},
}, {
Expand Down
4 changes: 2 additions & 2 deletions pkg/oci/mutate/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func WithReplaceOp(ro ReplaceOp) SignOption {

type signatureOpts struct {
annotations map[string]string
bundle *bundle.RekorBundle
bundle *bundle.Bundle
cert []byte
chain []byte
mediaType types.MediaType
Expand All @@ -77,7 +77,7 @@ func WithAnnotations(annotations map[string]string) SignatureOption {
}

// WithBundle specifies the new Bundle the Signature should have.
func WithBundle(b *bundle.RekorBundle) SignatureOption {
func WithBundle(b *bundle.Bundle) SignatureOption {
return func(so *signatureOpts) {
so.bundle = b
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/oci/mutate/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type sigWrapper struct {
wrapped oci.Signature

annotations map[string]string
bundle *bundle.RekorBundle
bundle *bundle.Bundle
cert *x509.Certificate
chain []*x509.Certificate
mediaType types.MediaType
Expand Down Expand Up @@ -85,7 +85,7 @@ func (sw *sigWrapper) Chain() ([]*x509.Certificate, error) {
}

// Bundle implements oci.Signature.
func (sw *sigWrapper) Bundle() (*bundle.RekorBundle, error) {
func (sw *sigWrapper) Bundle() (*bundle.Bundle, error) {
if sw.bundle != nil {
return sw.bundle, nil
}
Expand Down
Loading