Skip to content

Commit ee986f2

Browse files
Add support for SigningConfig in sign/attest
This will indirectly add support for signing with Rekor v2, since signing will be handled by sigstore-go rather than Cosign. This also brings sign/attest up to par with sign-blob/attest-blob with respect to signing with a key and providing a trusted root when providing a signing config. This feature is gated behind one of two signing config flags, which in a later version of Cosign will be flipped to on by default. Signed-off-by: Hayden <[email protected]>
1 parent 6d27d43 commit ee986f2

File tree

9 files changed

+392
-47
lines changed

9 files changed

+392
-47
lines changed

cmd/cosign/cli/attest.go

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/sigstore/cosign/v2/internal/ui"
2626
"github.com/sigstore/cosign/v2/pkg/cosign"
2727
"github.com/sigstore/cosign/v2/pkg/cosign/env"
28+
"github.com/sigstore/sigstore-go/pkg/root"
2829
"github.com/spf13/cobra"
2930
)
3031

@@ -101,14 +102,42 @@ func Attest() *cobra.Command {
101102
IssueCertificateForExistingKey: o.IssueCertificate,
102103
NewBundleFormat: o.NewBundleFormat,
103104
}
104-
if o.Key == "" && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" { // Get the trusted root if using fulcio for signing
105-
trustedMaterial, err := cosign.TrustedRoot()
105+
// Fetch a trusted root when:
106+
// * requesting a certificate and no CT log key is provided to verify an SCT
107+
// * using a signing config and signing using sigstore-go
108+
if (o.Key == "" && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "") ||
109+
(o.UseSigningConfig || o.SigningConfigPath != "") {
110+
if o.TrustedRootPath != "" {
111+
ko.TrustedMaterial, err = root.NewTrustedRootFromPath(o.TrustedRootPath)
112+
if err != nil {
113+
return fmt.Errorf("loading trusted root: %w", err)
114+
}
115+
} else {
116+
trustedMaterial, err := cosign.TrustedRoot()
117+
if err != nil {
118+
ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err)
119+
}
120+
ko.TrustedMaterial = trustedMaterial
121+
}
122+
}
123+
124+
if (o.UseSigningConfig || o.SigningConfigPath != "") && !o.NewBundleFormat {
125+
return fmt.Errorf("must provide --new-bundle-format with --signing-config or --use-signing-config")
126+
}
127+
if o.UseSigningConfig {
128+
signingConfig, err := cosign.SigningConfig()
106129
if err != nil {
107-
ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err)
130+
return fmt.Errorf("error getting signing config from TUF: %w", err)
108131
}
109-
ko.TrustedMaterial = trustedMaterial
132+
ko.SigningConfig = signingConfig
133+
} else if o.SigningConfigPath != "" {
134+
signingConfig, err := root.NewSigningConfigFromPath(o.SigningConfigPath)
135+
if err != nil {
136+
return fmt.Errorf("error reading signing config from file: %w", err)
137+
}
138+
ko.SigningConfig = signingConfig
110139
}
111-
// TODO(#4324): Add support for SigningConfig
140+
112141
attestCommand := attest.AttestCommand{
113142
KeyOpts: ko,
114143
RegistryOptions: o.Registry,

cmd/cosign/cli/attest/attest.go

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ import (
2929

3030
"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
3131
"github.com/sigstore/cosign/v2/cmd/cosign/cli/rekor"
32-
"github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
32+
cosign_sign "github.com/sigstore/cosign/v2/cmd/cosign/cli/sign"
33+
"github.com/sigstore/cosign/v2/internal/auth"
34+
"github.com/sigstore/cosign/v2/internal/key"
3335
"github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa"
3436
tsaclient "github.com/sigstore/cosign/v2/internal/pkg/cosign/tsa/client"
3537
"github.com/sigstore/cosign/v2/internal/ui"
@@ -43,13 +45,14 @@ import (
4345
"github.com/sigstore/cosign/v2/pkg/types"
4446
"github.com/sigstore/rekor/pkg/generated/client"
4547
"github.com/sigstore/rekor/pkg/generated/models"
48+
"github.com/sigstore/sigstore-go/pkg/sign"
4649
"github.com/sigstore/sigstore/pkg/signature/dsse"
4750
signatureoptions "github.com/sigstore/sigstore/pkg/signature/options"
4851
)
4952

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

52-
func uploadToTlog(ctx context.Context, sv *sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*models.LogEntryAnon, error) {
55+
func uploadToTlog(ctx context.Context, sv *cosign_sign.SignerVerifier, rekorURL string, upload tlogUploadFn) (*models.LogEntryAnon, error) {
5356
rekorBytes, err := sv.Bytes(ctx)
5457
if err != nil {
5558
return nil, err
@@ -132,20 +135,6 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
132135
// each access.
133136
ref = digest // nolint
134137

135-
sv, genKey, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
136-
if err != nil {
137-
return fmt.Errorf("getting signer: %w", err)
138-
}
139-
if genKey || c.IssueCertificateForExistingKey {
140-
sv, err = sign.KeylessSigner(ctx, c.KeyOpts, sv)
141-
if err != nil {
142-
return fmt.Errorf("getting Fulcio signer: %w", err)
143-
}
144-
}
145-
defer sv.Close()
146-
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType)
147-
dd := cremote.NewDupeDetector(sv)
148-
149138
predicate, err := predicateReader(c.PredicatePath)
150139
if err != nil {
151140
return fmt.Errorf("getting predicate reader: %w", err)
@@ -166,6 +155,83 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
166155
if err != nil {
167156
return err
168157
}
158+
159+
if c.SigningConfig != nil {
160+
var keypair sign.Keypair
161+
var ephemeralKeypair bool
162+
var idToken string
163+
var sv *cosign_sign.SignerVerifier
164+
var err error
165+
166+
if c.Sk || c.Slot != "" || c.KeyRef != "" || c.CertPath != "" {
167+
sv, _, err = cosign_sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
168+
if err != nil {
169+
return fmt.Errorf("getting signer: %w", err)
170+
}
171+
keypair, err = key.NewSignerVerifierKeypair(sv)
172+
if err != nil {
173+
return fmt.Errorf("creating signerverifier keypair: %w", err)
174+
}
175+
} else {
176+
keypair, err = sign.NewEphemeralKeypair(nil)
177+
if err != nil {
178+
return fmt.Errorf("generating keypair: %w", err)
179+
}
180+
ephemeralKeypair = true
181+
}
182+
defer func() {
183+
if sv != nil {
184+
sv.Close()
185+
}
186+
}()
187+
188+
if ephemeralKeypair || c.IssueCertificateForExistingKey {
189+
idToken, err = auth.RetrieveIDToken(ctx, auth.IDTokenConfig{
190+
TokenOrPath: c.IDToken,
191+
DisableProviders: c.OIDCDisableProviders,
192+
Provider: c.OIDCProvider,
193+
AuthFlow: c.FulcioAuthFlow,
194+
SkipConfirm: c.SkipConfirmation,
195+
OIDCServices: c.SigningConfig.OIDCProviderURLs(),
196+
ClientID: c.OIDCClientID,
197+
ClientSecret: c.OIDCClientSecret,
198+
RedirectURL: c.OIDCRedirectURL,
199+
})
200+
if err != nil {
201+
return fmt.Errorf("retrieving ID token: %w", err)
202+
}
203+
}
204+
205+
content := &sign.DSSEData{
206+
Data: payload,
207+
PayloadType: "application/vnd.in-toto+json",
208+
}
209+
bundle, err := cbundle.SignData(content, keypair, idToken, c.SigningConfig, c.TrustedMaterial)
210+
if err != nil {
211+
return fmt.Errorf("signing bundle: %w", err)
212+
}
213+
214+
ociremoteOpts, err := c.RegistryOptions.ClientOpts(ctx)
215+
if err != nil {
216+
return err
217+
}
218+
return ociremote.WriteAttestationNewBundleFormat(digest, bundle, types.CosignSignPredicateType, ociremoteOpts...)
219+
}
220+
221+
sv, genKey, err := cosign_sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts)
222+
if err != nil {
223+
return fmt.Errorf("getting signer: %w", err)
224+
}
225+
if genKey || c.IssueCertificateForExistingKey {
226+
sv, err = cosign_sign.KeylessSigner(ctx, c.KeyOpts, sv)
227+
if err != nil {
228+
return fmt.Errorf("getting Fulcio signer: %w", err)
229+
}
230+
}
231+
defer sv.Close()
232+
wrapped := dsse.WrapSigner(sv, types.IntotoPayloadType)
233+
dd := cremote.NewDupeDetector(sv)
234+
169235
signedPayload, err := wrapped.SignMessage(bytes.NewReader(payload), signatureoptions.WithContext(ctx))
170236
if err != nil {
171237
return fmt.Errorf("signing: %w", err)
@@ -227,7 +293,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error {
227293
opts = append(opts, static.WithAnnotations(predicateTypeAnnotation))
228294

229295
// Check whether we should be uploading to the transparency log
230-
shouldUpload, err := sign.ShouldUploadToTlog(ctx, c.KeyOpts, digest, c.TlogUpload)
296+
shouldUpload, err := cosign_sign.ShouldUploadToTlog(ctx, c.KeyOpts, digest, c.TlogUpload)
231297
if err != nil {
232298
return fmt.Errorf("should upload to tlog: %w", err)
233299
}

cmd/cosign/cli/options/attest.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ type AttestOptions struct {
3939
RekorEntryType string
4040
RecordCreationTimestamp bool
4141
NewBundleFormat bool
42+
UseSigningConfig bool
43+
SigningConfigPath string
44+
TrustedRootPath string
4245

4346
Rekor RekorOptions
4447
Fulcio FulcioOptions
@@ -111,5 +114,16 @@ func (o *AttestOptions) AddFlags(cmd *cobra.Command) {
111114
cmd.Flags().BoolVar(&o.IssueCertificate, "issue-certificate", false,
112115
"issue a code signing certificate from Fulcio, even if a key is provided")
113116

117+
// TODO: have this default to true as a breaking change
114118
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "attach a Sigstore bundle using OCI referrers API")
119+
120+
// TODO: have this default to true as a breaking change
121+
cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", false,
122+
"whether to use a TUF-provided signing config for the service URLs. Must set --new-bundle-format, which will store verification material in the new format")
123+
124+
cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "",
125+
"path to a signing config file. Must provide --new-bundle-format, which will store verification material in the new format")
126+
127+
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
128+
"optional path to a TrustedRoot JSON file to verify a signature after signing")
115129
}

cmd/cosign/cli/options/sign.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ type SignOptions struct {
4343
SignContainerIdentity string
4444
RecordCreationTimestamp bool
4545
NewBundleFormat bool
46+
UseSigningConfig bool
47+
SigningConfigPath string
48+
TrustedRootPath string
4649

4750
Rekor RekorOptions
4851
Fulcio FulcioOptions
@@ -139,5 +142,16 @@ func (o *SignOptions) AddFlags(cmd *cobra.Command) {
139142

140143
cmd.Flags().BoolVar(&o.RecordCreationTimestamp, "record-creation-timestamp", false, "set the createdAt timestamp in the signature artifact to the time it was created; by default, cosign sets this to the zero value")
141144

145+
// TODO: have this default to true as a breaking change
142146
cmd.Flags().BoolVar(&o.NewBundleFormat, "new-bundle-format", false, "expect the signature/attestation to be packaged in a Sigstore bundle")
147+
148+
// TODO: have this default to true as a breaking change
149+
cmd.Flags().BoolVar(&o.UseSigningConfig, "use-signing-config", false,
150+
"whether to use a TUF-provided signing config for the service URLs. Must set --new-bundle-format, which will store verification material in the new format")
151+
152+
cmd.Flags().StringVar(&o.SigningConfigPath, "signing-config", "",
153+
"path to a signing config file. Must provide --new-bundle-format, which will store verification material in the new format")
154+
155+
cmd.Flags().StringVar(&o.TrustedRootPath, "trusted-root", "",
156+
"optional path to a TrustedRoot JSON file to verify a signature after signing")
143157
}

cmd/cosign/cli/sign.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/sigstore/cosign/v2/internal/ui"
2727
"github.com/sigstore/cosign/v2/pkg/cosign"
2828
"github.com/sigstore/cosign/v2/pkg/cosign/env"
29+
"github.com/sigstore/sigstore-go/pkg/root"
2930
"github.com/spf13/cobra"
3031
)
3132

@@ -131,13 +132,42 @@ race conditions or (worse) malicious tampering.
131132
TSAServerURL: o.TSAServerURL,
132133
IssueCertificateForExistingKey: o.IssueCertificate,
133134
}
134-
if (o.Key == "" || o.IssueCertificate) && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "" {
135-
trustedMaterial, err := cosign.TrustedRoot()
135+
// Fetch a trusted root when:
136+
// * requesting a certificate and no CT log key is provided to verify an SCT
137+
// * using a signing config and signing using sigstore-go
138+
if ((o.Key == "" || o.IssueCertificate) && env.Getenv(env.VariableSigstoreCTLogPublicKeyFile) == "") ||
139+
(o.UseSigningConfig || o.SigningConfigPath != "") {
140+
if o.TrustedRootPath != "" {
141+
ko.TrustedMaterial, err = root.NewTrustedRootFromPath(o.TrustedRootPath)
142+
if err != nil {
143+
return fmt.Errorf("loading trusted root: %w", err)
144+
}
145+
} else {
146+
trustedMaterial, err := cosign.TrustedRoot()
147+
if err != nil {
148+
ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err)
149+
}
150+
ko.TrustedMaterial = trustedMaterial
151+
}
152+
}
153+
154+
if (o.UseSigningConfig || o.SigningConfigPath != "") && !o.NewBundleFormat {
155+
return fmt.Errorf("must provide --new-bundle-format with --signing-config or --use-signing-config")
156+
}
157+
if o.UseSigningConfig {
158+
signingConfig, err := cosign.SigningConfig()
136159
if err != nil {
137-
ui.Warnf(context.Background(), "Could not fetch trusted_root.json from the TUF repository. Continuing with individual targets. Error from TUF: %v", err)
160+
return fmt.Errorf("error getting signing config from TUF: %w", err)
138161
}
139-
ko.TrustedMaterial = trustedMaterial
162+
ko.SigningConfig = signingConfig
163+
} else if o.SigningConfigPath != "" {
164+
signingConfig, err := root.NewSigningConfigFromPath(o.SigningConfigPath)
165+
if err != nil {
166+
return fmt.Errorf("error reading signing config from file: %w", err)
167+
}
168+
ko.SigningConfig = signingConfig
140169
}
170+
141171
if err := sign.SignCmd(ro, ko, *o, args); err != nil {
142172
if o.Attachment == "" {
143173
return fmt.Errorf("signing %v: %w", args, err)

0 commit comments

Comments
 (0)