Skip to content

Commit 8fece0b

Browse files
authored
Require new bundle format for attest-blob (#5088)
* Require new bundle format for attest-blob Signed-off-by: Aaron Lew <64337293+aaronlew02@users.noreply.github.com> * Assert AttestBlobOptions implements Interface This change fixes the interface assertion in the `attest-blob` options, which asserted `AttestOptions` instead of `AttestBlobOptions`. Signed-off-by: Aaron Lew <64337293+aaronlew02@users.noreply.github.com> * Remove obsolete new bundle case in TestAttestBlobCmdLocalKeyAndCert This change removes the "new bundle generation" case and unused `newBundle` field in `TestAttestBlobCmdLocalKeyAndCert`, since the new bundle format is now the only format. Signed-off-by: Aaron Lew <64337293+aaronlew02@users.noreply.github.com> --------- Signed-off-by: Aaron Lew <64337293+aaronlew02@users.noreply.github.com>
1 parent c8a4781 commit 8fece0b

6 files changed

Lines changed: 151 additions & 312 deletions

File tree

‎cmd/cosign/cli/attest/attest_blob.go‎

Lines changed: 7 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,7 @@ import (
3333
"github.com/sigstore/cosign/v3/cmd/cosign/cli/signcommon"
3434
"github.com/sigstore/cosign/v3/internal/ui"
3535
"github.com/sigstore/cosign/v3/pkg/cosign/attestation"
36-
cbundle "github.com/sigstore/cosign/v3/pkg/cosign/bundle"
37-
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
3836
"github.com/sigstore/sigstore/pkg/signature"
39-
"google.golang.org/protobuf/encoding/protojson"
4037
)
4138

4239
// nolint
@@ -51,14 +48,7 @@ type AttestBlobCommand struct {
5148
PredicatePath string
5249
PredicateType string
5350

54-
TlogUpload bool
55-
Timeout time.Duration
56-
57-
OutputSignature string
58-
OutputAttestation string
59-
OutputCertificate string
60-
61-
RekorEntryType string
51+
Timeout time.Duration
6252
}
6353

6454
// nolint
@@ -72,10 +62,6 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
7262
return fmt.Errorf("one of --predicate or --statement must be set")
7363
}
7464

75-
if c.RekorEntryType != "dsse" && c.RekorEntryType != "intoto" {
76-
return fmt.Errorf("unknown value for rekor-entry-type")
77-
}
78-
7965
if c.Timeout != 0 {
8066
var cancelFn context.CancelFunc
8167
ctx, cancelFn = context.WithTimeout(ctx, c.Timeout)
@@ -147,14 +133,10 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
147133
}
148134

149135
if c.SigningConfig == nil {
150-
var err error
151-
c.SigningConfig, err = signcommon.NewSigningConfigFromKeyOpts(c.KeyOpts)
152-
if err != nil {
153-
return fmt.Errorf("creating signing config: %w", err)
154-
}
136+
c.SigningConfig = signcommon.NewEmptySigningConfig()
155137
}
156-
if !signcommon.ShouldUploadToTlog(ctx, c.KeyOpts, nil, c.TlogUpload) {
157-
c.SigningConfig = c.SigningConfig.WithRekorLogURLs()
138+
if err := signcommon.RequireFulcioForCertificate(c.KeyOpts); err != nil {
139+
return err
158140
}
159141
if err := signcommon.ConfirmPrivacyStatement(ctx, c.KeyOpts); err != nil {
160142
return err
@@ -165,77 +147,10 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error
165147
return fmt.Errorf("creating bundle: %w", err)
166148
}
167149

168-
if c.NewBundleFormat {
169-
if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil {
170-
return fmt.Errorf("create bundle file: %w", err)
171-
}
172-
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
173-
return nil
174-
}
175-
176-
var pb protobundle.Bundle
177-
if err := protojson.Unmarshal(bundleBytes, &pb); err != nil {
178-
return fmt.Errorf("unmarshalling bundle: %w", err)
179-
}
180-
181-
bundleComponents, err := signcommon.ExtractComponentsFromProtoBundle(&pb)
182-
if err != nil {
183-
return err
184-
}
185-
186-
if c.BundlePath != "" {
187-
contents, err := signcommon.NewLegacyBundleFromProtoBundleComponents(bundleComponents)
188-
if err != nil {
189-
return fmt.Errorf("creating legacy bundle: %w", err)
190-
}
191-
192-
if err := os.WriteFile(c.BundlePath, contents, 0600); err != nil {
193-
return fmt.Errorf("create bundle file: %w", err)
194-
}
195-
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
196-
}
197-
198-
if c.OutputSignature != "" {
199-
if err := os.WriteFile(c.OutputSignature, bundleComponents.Signature, 0600); err != nil {
200-
return fmt.Errorf("create signature file: %w", err)
201-
}
202-
fmt.Fprintf(os.Stderr, "Signature written in %s\n", c.OutputSignature)
203-
} else {
204-
fmt.Fprintln(os.Stdout, string(bundleComponents.Signature))
205-
}
206-
207-
if c.OutputAttestation != "" {
208-
if err := os.WriteFile(c.OutputAttestation, payload, 0600); err != nil {
209-
return fmt.Errorf("create attestation file: %w", err)
210-
}
211-
fmt.Fprintf(os.Stderr, "Attestation written in %s\n", c.OutputAttestation)
212-
}
213-
214-
if c.OutputCertificate != "" {
215-
if len(bundleComponents.Certificates) == 0 {
216-
return fmt.Errorf("no certificate found in bundle")
217-
}
218-
certPem, _ := signcommon.EncodeCertificatesToPEM(bundleComponents.Certificates)
219-
if err := os.WriteFile(c.OutputCertificate, certPem, 0600); err != nil {
220-
return fmt.Errorf("create certificate file: %w", err)
221-
}
222-
fmt.Fprintln(os.Stderr, "Certificate written to file ", c.OutputCertificate)
223-
}
224-
225-
if c.RFC3161TimestampPath != "" {
226-
if len(bundleComponents.RFC3161Timestamps) == 0 {
227-
return fmt.Errorf("no RFC3161 timestamp found in bundle")
228-
}
229-
legacyTimestamp := cbundle.TimestampToRFC3161Timestamp(bundleComponents.RFC3161Timestamps[0].GetSignedTimestamp())
230-
ts, err := json.Marshal(legacyTimestamp)
231-
if err != nil {
232-
return fmt.Errorf("marshalling timestamp: %w", err)
233-
}
234-
if err := os.WriteFile(c.RFC3161TimestampPath, ts, 0600); err != nil {
235-
return fmt.Errorf("create timestamp file: %w", err)
236-
}
237-
fmt.Fprintln(os.Stderr, "Timestamp wrote in the file ", c.RFC3161TimestampPath)
150+
if err := os.WriteFile(c.BundlePath, bundleBytes, 0600); err != nil {
151+
return fmt.Errorf("create bundle file: %w", err)
238152
}
153+
ui.Infof(ctx, "Wrote bundle to file %s", c.BundlePath)
239154

240155
return nil
241156
}

‎cmd/cosign/cli/attest/attest_blob_test.go‎

Lines changed: 55 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,11 @@ import (
3737
"github.com/sigstore/cosign/v3/internal/test"
3838
"github.com/sigstore/cosign/v3/pkg/cosign"
3939
"github.com/sigstore/cosign/v3/pkg/cosign/attestation"
40+
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
4041
"github.com/sigstore/sigstore/pkg/signature"
4142
"github.com/sigstore/sigstore/pkg/signature/dsse"
4243
"github.com/stretchr/testify/assert"
44+
"google.golang.org/protobuf/encoding/protojson"
4345
)
4446

4547
// TestAttestBlobCmdLocalKeyAndSk verifies the AttestBlobCmd returns an error
@@ -73,6 +75,31 @@ func writeFile(t *testing.T, td string, blob string, name string) string {
7375
return blobPath
7476
}
7577

78+
// assertOfflineBundle asserts that a key-signed bundle file carries no material
79+
// from online services (certificates, tlog entries, or signed timestamps).
80+
func assertOfflineBundle(t *testing.T, bundlePath string) {
81+
t.Helper()
82+
bundleBytes, err := os.ReadFile(bundlePath)
83+
if err != nil {
84+
t.Fatalf("failed to read bundle: %v", err)
85+
}
86+
var bundle protobundle.Bundle
87+
if err := protojson.Unmarshal(bundleBytes, &bundle); err != nil {
88+
t.Fatalf("failed to unmarshal bundle: %v", err)
89+
}
90+
vm := bundle.GetVerificationMaterial()
91+
// The content is a oneof, so a public key hint also rules out a certificate.
92+
if vm.GetPublicKey() == nil {
93+
t.Errorf("expected a public key hint, got %T", vm.GetContent())
94+
}
95+
if n := len(vm.GetTlogEntries()); n != 0 {
96+
t.Errorf("expected no transparency log entries, got %d", n)
97+
}
98+
if n := len(vm.GetTimestampVerificationData().GetRfc3161Timestamps()); n != 0 {
99+
t.Errorf("expected no RFC 3161 timestamps, got %d", n)
100+
}
101+
}
102+
76103
func makeSLSA02PredicateFile(t *testing.T, td string) string {
77104
predicate := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }`
78105
return writeFile(t, td, predicate, "predicate02.json")
@@ -124,7 +151,6 @@ func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) {
124151
keyref string
125152
certref string
126153
certchainref string
127-
newBundle bool
128154
errString string
129155
}{
130156
{
@@ -136,24 +162,12 @@ func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) {
136162
keyref: keyRef,
137163
certref: certRef,
138164
},
139-
{
140-
name: "new bundle generation",
141-
keyref: keyRef,
142-
certref: certRef,
143-
newBundle: true,
144-
},
145165
{
146166
name: "fail: cert no match key",
147167
keyref: keyRef,
148168
certref: subCertPem,
149169
errString: "public key in certificate does not match the provided public key",
150170
},
151-
{
152-
name: "cert chain matches key",
153-
keyref: keyRef,
154-
certref: certRef,
155-
certchainref: subCertPem,
156-
},
157171
{
158172
name: "cert chain partial",
159173
keyref: keyRef,
@@ -175,18 +189,13 @@ func TestAttestBlobCmdLocalKeyAndCert(t *testing.T) {
175189
},
176190
} {
177191
t.Run(tc.name, func(t *testing.T) {
178-
keyOpts := options.KeyOpts{KeyRef: tc.keyref}
179-
if tc.newBundle {
180-
keyOpts.NewBundleFormat = true
181-
keyOpts.BundlePath = filepath.Join(td, "output.bundle")
182-
}
192+
keyOpts := options.KeyOpts{KeyRef: tc.keyref, BundlePath: filepath.Join(td, "output.bundle")}
183193
at := AttestBlobCommand{
184-
KeyOpts: keyOpts,
185-
CertPath: tc.certref,
186-
CertChainPath: tc.certchainref,
187-
PredicatePath: predicatePath,
188-
PredicateType: predicateType,
189-
RekorEntryType: "dsse",
194+
KeyOpts: keyOpts,
195+
CertPath: tc.certref,
196+
CertChainPath: tc.certchainref,
197+
PredicatePath: predicatePath,
198+
PredicateType: predicateType,
190199
}
191200
err := at.Exec(ctx, blob)
192201
if err != nil {
@@ -228,25 +237,34 @@ func TestAttestBlob(t *testing.T) {
228237

229238
for predicateType, predicatePath := range predicates {
230239
t.Run(predicateType, func(t *testing.T) {
231-
dssePath := filepath.Join(td, "dsse.intoto.jsonl")
240+
bundlePath := filepath.Join(td, "bundle.json")
241+
keyOpts := options.KeyOpts{KeyRef: keyRef, BundlePath: bundlePath}
232242
at := AttestBlobCommand{
233-
KeyOpts: options.KeyOpts{KeyRef: keyRef},
234-
PredicatePath: predicatePath,
235-
PredicateType: predicateType,
236-
OutputSignature: dssePath,
237-
RekorEntryType: "dsse",
243+
KeyOpts: keyOpts,
244+
PredicatePath: predicatePath,
245+
PredicateType: predicateType,
238246
}
239247
err := at.Exec(ctx, blobPath)
240248
if err != nil {
241249
t.Fatal(err)
242250
}
251+
assertOfflineBundle(t, bundlePath)
243252

244-
// Load the attestation.
245-
dsseBytes, _ := os.ReadFile(dssePath)
246-
env := &ssldsse.Envelope{}
247-
if err := json.Unmarshal(dsseBytes, env); err != nil {
253+
// Load the attestation bundle.
254+
bundleBytes, err := os.ReadFile(bundlePath)
255+
if err != nil {
248256
t.Fatal(err)
249257
}
258+
var bundleJSON struct {
259+
DsseEnvelope *ssldsse.Envelope `json:"dsseEnvelope"`
260+
}
261+
if err := json.Unmarshal(bundleBytes, &bundleJSON); err != nil {
262+
t.Fatal(err)
263+
}
264+
env := bundleJSON.DsseEnvelope
265+
if env == nil {
266+
t.Fatal("expected dsse envelope in bundle")
267+
}
250268

251269
if len(env.Signatures) != 1 {
252270
t.Fatalf("expected 1 signature, got %d", len(env.Signatures))
@@ -284,38 +302,6 @@ func TestAttestBlob(t *testing.T) {
284302
}
285303
}
286304

287-
func TestBadRekorEntryType(t *testing.T) {
288-
ctx := context.Background()
289-
td := t.TempDir()
290-
291-
keys, _ := cosign.GenerateKeyPair(nil)
292-
keyRef := writeFile(t, td, string(keys.PrivateBytes), "key.pem")
293-
294-
blob := []byte("foo")
295-
blobPath := writeFile(t, td, string(blob), "foo.txt")
296-
297-
predicates := map[string]string{}
298-
predicates["slsaprovenance"] = makeSLSA02PredicateFile(t, td)
299-
predicates["slsaprovenance1"] = makeSLSA1PredicateFile(t, td)
300-
301-
for predicateType, predicatePath := range predicates {
302-
t.Run(predicateType, func(t *testing.T) {
303-
dssePath := filepath.Join(td, "dsse.intoto.jsonl")
304-
at := AttestBlobCommand{
305-
KeyOpts: options.KeyOpts{KeyRef: keyRef},
306-
PredicatePath: predicatePath,
307-
PredicateType: predicateType,
308-
OutputSignature: dssePath,
309-
RekorEntryType: "badvalue",
310-
}
311-
err := at.Exec(ctx, blobPath)
312-
if err == nil || err.Error() != "unknown value for rekor-entry-type" {
313-
t.Fatal("expected an error due to unknown rekor entry type")
314-
}
315-
})
316-
}
317-
}
318-
319305
func TestStatementPath(t *testing.T) {
320306
ctx := context.Background()
321307
td := t.TempDir()
@@ -340,10 +326,10 @@ func TestStatementPath(t *testing.T) {
340326
}`
341327
statementPath := writeFile(t, td, statement, "statement.json")
342328

329+
keyOpts := options.KeyOpts{KeyRef: keyRef, BundlePath: filepath.Join(td, "bundle.json")}
343330
at := AttestBlobCommand{
344-
KeyOpts: options.KeyOpts{KeyRef: keyRef},
345-
StatementPath: statementPath,
346-
RekorEntryType: "dsse",
331+
KeyOpts: keyOpts,
332+
StatementPath: statementPath,
347333
}
348334
err := at.Exec(ctx, "")
349335
assert.NoError(t, err)

0 commit comments

Comments
 (0)