Skip to content

Commit dad3e0e

Browse files
committed
fix(controlplane): report an unretrievable attestation bundle as not verified
When keyless signing is configured and a run has an attestation digest but its bundle cannot be loaded, the verification result is now a failure instead of no result. Also add tests for a valid keyless certificate with a signature that does not match the payload. Assisted-by: Claude Code Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev> Chainloop-Trace-Sessions: 3ed0fe4b-8701-40f8-bb35-c85ce99f6fd1
1 parent 49b88f7 commit dad3e0e

2 files changed

Lines changed: 39 additions & 4 deletions

File tree

‎app/controlplane/pkg/biz/workflowrun.go‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -741,10 +741,19 @@ func (uc *WorkflowRunUseCase) VerifyRun(ctx context.Context, run *WorkflowRun) (
741741

742742
// Without keyless signing there is nothing to verify against, and a run
743743
// that has no attestation yet has nothing to verify
744-
if !uc.signingUseCase.KeylessEnabled() || run.Attestation == nil || len(run.Attestation.Bundle) == 0 {
744+
if !uc.signingUseCase.KeylessEnabled() || run.Attestation == nil {
745745
return nil, nil
746746
}
747747

748+
if len(run.Attestation.Bundle) == 0 {
749+
if run.Attestation.Digest == "" {
750+
return nil, nil
751+
}
752+
// The run has an attestation, but its bundle could not be loaded, for
753+
// example because the CAS backend is unavailable
754+
return &VerificationResult{Result: false, FailureReason: "the attestation bundle could not be retrieved"}, nil
755+
}
756+
748757
opts, err := verifyOptionsForRun(run)
749758
if err != nil {
750759
return nil, err

‎app/controlplane/pkg/biz/workflowrun_verification_test.go‎

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ const (
6161
bundleWithoutMaterial
6262
// a raw DSSE envelope instead of a Sigstore bundle
6363
bundleRawEnvelope
64+
// carries a valid keyless certificate, but the signature does not match the payload
65+
bundleWithCertTamperedSignature
6466
)
6567

6668
// newSignedTestBundle signs the test attestation with a certificate issued by
@@ -82,7 +84,13 @@ func newSignedTestBundle(t *testing.T, signing *biz.SigningUseCase, orgID string
8284
chain, err := signing.CreateSigningCert(context.Background(), orgID, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}))
8385
require.NoError(t, err)
8486

85-
digest := sha256.Sum256(dsse.PAE(env.PayloadType, payload))
87+
signedPayload := payload
88+
if kind == bundleWithCertTamperedSignature {
89+
// sign other content, so the signature does not match the payload in the envelope
90+
signedPayload = append([]byte("tampered"), payload...)
91+
}
92+
93+
digest := sha256.Sum256(dsse.PAE(env.PayloadType, signedPayload))
8694
sig, err := ecdsa.SignASN1(rand.Reader, key, digest[:])
8795
require.NoError(t, err)
8896

@@ -101,7 +109,7 @@ func newSignedTestBundle(t *testing.T, signing *biz.SigningUseCase, orgID string
101109
bundle, err := attestation.BundleFromDSSEEnvelope(signed)
102110
require.NoError(t, err)
103111

104-
if kind == bundleWithCert {
112+
if kind == bundleWithCert || kind == bundleWithCertTamperedSignature {
105113
block, _ := pem.Decode([]byte(chain[0]))
106114
require.NotNil(t, block)
107115
bundle.VerificationMaterial.Content = &protobundle.VerificationMaterial_Certificate{
@@ -134,6 +142,11 @@ func TestValidateAttestationContractEnforcesKeylessVerification(t *testing.T) {
134142
bundle: newSignedTestBundle(t, signing, otherOrgID.String(), bundleWithCert),
135143
wantRejected: true,
136144
},
145+
{
146+
name: "keyless certificate issued to the run organization, with a tampered signature",
147+
bundle: newSignedTestBundle(t, signing, orgID.String(), bundleWithCertTamperedSignature),
148+
wantRejected: true,
149+
},
137150
{
138151
name: "signed without verification material",
139152
bundle: newSignedTestBundle(t, signing, orgID.String(), bundleWithoutMaterial),
@@ -179,6 +192,7 @@ func TestVerifyRunKeyless(t *testing.T) {
179192
name string
180193
signing *biz.SigningUseCase
181194
bundle []byte
195+
digest string
182196
wantNil bool
183197
wantResult bool
184198
wantReason string
@@ -195,6 +209,18 @@ func TestVerifyRunKeyless(t *testing.T) {
195209
bundle: newSignedTestBundle(t, signing, uuid.NewString(), bundleWithCert),
196210
wantReason: "organization mismatch",
197211
},
212+
{
213+
name: "keyless certificate issued to the run organization, with a tampered signature",
214+
signing: signing,
215+
bundle: newSignedTestBundle(t, signing, orgID.String(), bundleWithCertTamperedSignature),
216+
wantReason: "validating the DSSE envelope",
217+
},
218+
{
219+
name: "attestation digest recorded, but its bundle could not be retrieved",
220+
signing: signing,
221+
digest: "sha256:0f9b2a1c",
222+
wantReason: "could not be retrieved",
223+
},
198224
{
199225
name: "no verification material with keyless signing enabled",
200226
signing: signing,
@@ -221,7 +247,7 @@ func TestVerifyRunKeyless(t *testing.T) {
221247

222248
got, err := uc.VerifyRun(context.Background(), &biz.WorkflowRun{
223249
Workflow: &biz.Workflow{OrgID: orgID},
224-
Attestation: &biz.Attestation{Bundle: tc.bundle},
250+
Attestation: &biz.Attestation{Bundle: tc.bundle, Digest: tc.digest},
225251
})
226252
require.NoError(t, err)
227253
if tc.wantNil {

0 commit comments

Comments
 (0)