Repository navigation
Expand file tree
/
Copy pathbackend.go
More file actions
331 lines (295 loc) · 11.2 KB
/
Copy pathbackend.go
File metadata and controls
331 lines (295 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
// SPDX-FileCopyrightText: Copyright 2026 Carabiner Systems, Inc
// SPDX-License-Identifier: Apache-2.0
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
package signer
import (
"context"
"errors"
"fmt"
"sync"
protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1"
protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
sbundle "github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/sign"
"github.com/carabiner-dev/signer/bundle"
"github.com/carabiner-dev/signer/dsse"
"github.com/carabiner-dev/signer/key"
"github.com/carabiner-dev/signer/options"
)
// Backend is one of the three signing implementations the Signer
// dispatches to. Real implementations are constructed by
// Signer.resolveBackend based on Signer.Options.Backend (or per-call
// options.WithKey overrides). A counterfeiter-generated fake lives at
// signerfakes.FakeBackend.
//
// Backends are self-contained: they hold the state they need
// (credentials, bundle/dsse signers, configured keys) and the
// per-instance caches that make repeated Sign calls cheap (one OIDC
// flow + one Fulcio cert request shared across calls).
//
//counterfeiter:generate . Backend
type Backend interface {
// Name reports which backend this is.
Name() options.Backend
// SignStatement signs an in-toto attestation. Bundle backends
// produce *BundleArtifact; the key backend produces
// *EnvelopeArtifact.
SignStatement(data []byte, funcs ...options.SignOptFn) (SignedArtifact, error)
// SignMessage signs a raw payload. Bundle backends wrap it in a
// sigstore bundle; the key backend wraps it in a DSSE envelope
// (caller must supply options.WithPayloadType).
SignMessage(data []byte, funcs ...options.SignOptFn) (SignedArtifact, error)
}
// bundleBackendBase carries the shared state and signing helpers used
// by both sigstoreBackend and spiffeBackend. Both produce sigstore
// bundles from a bundle.CredentialProvider. It is embedded by both
// the per-backend prepare logic (auto-build creds for sigstore, require
// pre-set creds for spiffe) lives on the wrapping types.
type bundleBackendBase struct {
options *options.Signer
creds bundle.CredentialProvider
bundleSigner bundle.Signer
// mu guards the lazy preparation state (ready, creds, bundleOpts).
// prepare errors are not cached: ready is only set on success, so
// later sign calls retry after a transient failure.
mu sync.Mutex
ready bool
bundleOpts *sign.BundleOptions
}
// signBundleContent is the shared finishing step: hand the wrapped
// content to bundle.Signer.SignBundle, attach intermediates if the
// credential provider exposes any, and wrap the result for callers.
func (b *bundleBackendBase) signBundleContent(content sign.Content) (*sbundle.Bundle, error) {
bndl, err := b.bundleSigner.SignBundle(content, b.creds.Keypair(), b.bundleOpts)
if err != nil {
return nil, fmt.Errorf("signing bundle: %w", err)
}
if err := attachIntermediates(b.creds, bndl); err != nil {
return nil, fmt.Errorf("attaching intermediates: %w", err)
}
return &sbundle.Bundle{Bundle: bndl}, nil
}
// sigstoreBackend signs through Fulcio + sigstore-go. When creds is
// nil at sign time, prepare lazily builds a sigstore.CredentialProvider
// from the configured Options.
type sigstoreBackend struct {
bundleBackendBase
}
func newSigstoreBackend(opts *options.Signer, creds bundle.CredentialProvider, bs bundle.Signer) *sigstoreBackend {
return &sigstoreBackend{
bundleBackendBase: bundleBackendBase{options: opts, creds: creds, bundleSigner: bs},
}
}
func (b *sigstoreBackend) Name() options.Backend { return options.BackendSigstore }
func (b *sigstoreBackend) SignStatement(data []byte, _ ...options.SignOptFn) (SignedArtifact, error) {
if err := b.bundleSigner.VerifyAttestationContent(b.options, data); err != nil {
return nil, fmt.Errorf("verifying content: %w", err)
}
if err := b.prepare(); err != nil {
return nil, err
}
content := b.bundleSigner.WrapData("application/vnd.in-toto+json", data)
bndl, err := b.signBundleContent(content)
if err != nil {
return nil, err
}
return &BundleArtifact{Bundle: bndl}, nil
}
func (b *sigstoreBackend) SignMessage(data []byte, _ ...options.SignOptFn) (SignedArtifact, error) {
if err := b.prepare(); err != nil {
return nil, err
}
content := b.bundleSigner.BuildMessage(data)
bndl, err := b.signBundleContent(content)
if err != nil {
return nil, err
}
return &BundleArtifact{Bundle: bndl}, nil
}
// prepare builds (lazily, once) the sigstore credentials + bundle
// options. Auto-builds creds from Options when not pre-set (a
// sigstore-specific behavior). Safe for concurrent use.
func (b *sigstoreBackend) prepare() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.ready {
return nil
}
if b.creds == nil {
if err := b.options.Validate(); err != nil {
return fmt.Errorf("validating options for signing: %w", err)
}
b.creds = b.options.BuildSigstoreCredentials()
}
if err := b.creds.Prepare(context.TODO()); err != nil {
return fmt.Errorf("preparing signing credentials: %w", err)
}
bo, err := b.bundleSigner.BuildBundleOptions(b.options, b.creds)
if err != nil {
return fmt.Errorf("building bundle options: %w", err)
}
b.bundleOpts = bo
b.ready = true
return nil
}
// spiffeBackend signs with an X.509-SVID. The credential provider must
// be supplied at construction time — this backend cannot build SPIFFE
// credentials from Options alone, so the caller has to wire up a
// *spiffe.CredentialProvider externally.
type spiffeBackend struct {
bundleBackendBase
}
func newSpiffeBackend(opts *options.Signer, creds bundle.CredentialProvider, bs bundle.Signer) (*spiffeBackend, error) {
if creds == nil {
return nil, errors.New("BackendSpiffe requires Signer.Credentials to be set with a *spiffe.CredentialProvider; signer cannot build SPIFFE credentials from Options alone")
}
return &spiffeBackend{
bundleBackendBase: bundleBackendBase{options: opts, creds: creds, bundleSigner: bs},
}, nil
}
func (b *spiffeBackend) Name() options.Backend { return options.BackendSpiffe }
func (b *spiffeBackend) SignStatement(data []byte, _ ...options.SignOptFn) (SignedArtifact, error) {
if err := b.bundleSigner.VerifyAttestationContent(b.options, data); err != nil {
return nil, fmt.Errorf("verifying content: %w", err)
}
if err := b.prepare(); err != nil {
return nil, err
}
content := b.bundleSigner.WrapData("application/vnd.in-toto+json", data)
bndl, err := b.signBundleContent(content)
if err != nil {
return nil, err
}
return &BundleArtifact{Bundle: bndl}, nil
}
func (b *spiffeBackend) SignMessage(data []byte, _ ...options.SignOptFn) (SignedArtifact, error) {
if err := b.prepare(); err != nil {
return nil, err
}
content := b.bundleSigner.BuildMessage(data)
bndl, err := b.signBundleContent(content)
if err != nil {
return nil, err
}
return &BundleArtifact{Bundle: bndl}, nil
}
// prepare prepares the SVID and computes bundle options. Unlike
// sigstoreBackend.prepare, this does not auto-build creds: the
// constructor enforces a non-nil creds value. Safe for concurrent use.
func (b *spiffeBackend) prepare() error {
b.mu.Lock()
defer b.mu.Unlock()
if b.ready {
return nil
}
if err := b.creds.Prepare(context.TODO()); err != nil {
return fmt.Errorf("preparing svid credentials: %w", err)
}
bo, err := b.bundleSigner.BuildBundleOptions(b.options, b.creds)
if err != nil {
return fmt.Errorf("building bundle options: %w", err)
}
b.bundleOpts = bo
b.ready = true
return nil
}
// keyBackend signs with raw private keys, producing a bare DSSE
// envelope. Two construction shapes:
//
// - Persistent: built from Signer.Options.Keys when Backend is
// BackendKey. keys is set; per-call options.WithKey replaces it
// for that single call.
// - Transient: built per call from per-call options.WithKey when the
// persistent backend is something else. In this case keys is
// already the per-call set.
type keyBackend struct {
dsseSigner dsse.Signer
keys []key.PrivateKeyProvider
}
func newKeyBackend(ds dsse.Signer, keys []key.PrivateKeyProvider) *keyBackend {
return &keyBackend{dsseSigner: ds, keys: keys}
}
func (b *keyBackend) Name() options.Backend { return options.BackendKey }
func (b *keyBackend) SignStatement(data []byte, funcs ...options.SignOptFn) (SignedArtifact, error) {
return b.sign("https://in-toto.io/Statement/v1", data, funcs...)
}
func (b *keyBackend) SignMessage(data []byte, funcs ...options.SignOptFn) (SignedArtifact, error) {
so := options.DefaultSign
for _, f := range funcs {
if err := f(&so); err != nil {
return nil, err
}
}
return b.sign(so.PayloadType, data, funcs...)
}
func (b *keyBackend) sign(payloadType string, data []byte, funcs ...options.SignOptFn) (SignedArtifact, error) {
if payloadType == "" {
return nil, errors.New("payload type not defined")
}
so := options.DefaultSign
for _, f := range funcs {
if err := f(&so); err != nil {
return nil, err
}
}
// Per-call options.WithKey REPLACES the configured keys for this
// call. When the per-call set is empty, fall back to the
// configured keys.
keys := so.Keys
if len(keys) == 0 {
keys = b.keys
}
if len(keys) == 0 {
return nil, errors.New("no signing keys; pass options.WithKey(...) or set Signer.Options.Backend=BackendKey + Signer.Options.Keys")
}
env, err := b.dsseSigner.WrapPayload(payloadType, data)
if err != nil {
return nil, fmt.Errorf("wrapping payload: %w", err)
}
if err := b.dsseSigner.Sign(env, keys); err != nil {
return nil, fmt.Errorf("signing envelope: %w", err)
}
return &EnvelopeArtifact{Envelope: env}, nil
}
// attachIntermediates rewrites the bundle's VerificationMaterial to
// carry [leaf, ...intermediates] when the credential provider exposes
// intermediates. Called after SignBundle so the leaf DER is already
// present in VerificationMaterial.Content. When the provider returns
// an empty chain (the sigstore case) the bundle is left untouched.
func attachIntermediates(creds bundle.CredentialProvider, bndl *protobundle.Bundle) error {
if creds == nil {
return nil
}
ints := creds.Intermediates()
if len(ints) == 0 {
return nil
}
if bndl.GetVerificationMaterial() == nil {
return errors.New("bundle has no verification material")
}
leaf, ok := bndl.GetVerificationMaterial().GetContent().(*protobundle.VerificationMaterial_Certificate)
if !ok || leaf.Certificate == nil {
// Already a chain, a public key, or nothing — leave as-is.
return nil
}
chain := &protocommon.X509CertificateChain{
Certificates: make([]*protocommon.X509Certificate, 0, 1+len(ints)),
}
chain.Certificates = append(chain.Certificates, &protocommon.X509Certificate{
RawBytes: leaf.Certificate.GetRawBytes(),
})
for _, c := range ints {
chain.Certificates = append(chain.Certificates, &protocommon.X509Certificate{
RawBytes: c.Raw,
})
}
bndl.VerificationMaterial.Content = &protobundle.VerificationMaterial_X509CertificateChain{
X509CertificateChain: chain,
}
// sigstore-go's sbundle.NewBundle rejects X509CertificateChain
// content at v0.3 (only single Certificate allowed). v0.2 permits
// the chain variant, which is exactly what SPIFFE signing needs to
// carry intermediates.
bndl.MediaType = "application/vnd.dev.sigstore.bundle+json;version=0.2"
return nil
}