Skip to content

Commit fb545de

Browse files
authored
Fix intoto index keys (sigstore#889)
* Fix intoto index keys Ensure we include all appropriate index keys including: - entire DSSE envelope SHA256 digest - envelope (base64 decoded) SHA256 digest - entire X509 signing certificate - any relevant keys extracted from X509 signing certificate Fixes: sigstore#872 Signed-off-by: Bob Callaway <bcallaway@google.com> * consistently decode payload Signed-off-by: Bob Callaway <bcallaway@google.com> * rework error flows to be non-fatal Signed-off-by: Bob Callaway <bcallaway@google.com> * use canonicalized key, consistent sprintf Signed-off-by: Bob Callaway <bcallaway@google.com>
1 parent 3936e03 commit fb545de

File tree

2 files changed

+100
-26
lines changed

2 files changed

+100
-26
lines changed

pkg/types/intoto/v0.0.1/entry.go

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,45 @@ func NewEntry() types.EntryImpl {
7373
func (v V001Entry) IndexKeys() ([]string, error) {
7474
var result []string
7575

76-
h := sha256.Sum256([]byte(v.env.Payload))
77-
payloadKey := "sha256:" + hex.EncodeToString(h[:])
78-
result = append(result, payloadKey)
76+
// add digest over entire DSSE envelope
77+
if v.IntotoObj.Content != nil && v.IntotoObj.Content.Hash != nil {
78+
hashkey := strings.ToLower(fmt.Sprintf("%s:%s", swag.StringValue(v.IntotoObj.Content.Hash.Algorithm), swag.StringValue(v.IntotoObj.Content.Hash.Value)))
79+
result = append(result, hashkey)
80+
} else {
81+
log.Logger.Error("could not find content digest to include in index keys")
82+
}
7983

80-
switch v.env.PayloadType {
81-
case in_toto.PayloadType:
82-
if v.IntotoObj.Content != nil && v.IntotoObj.Content.Hash != nil {
83-
hashkey := strings.ToLower(fmt.Sprintf("%s:%s", swag.StringValue(v.IntotoObj.Content.Hash.Algorithm), swag.StringValue(v.IntotoObj.Content.Hash.Value)))
84-
result = append(result, hashkey)
84+
// add digest over public key
85+
if v.keyObj != nil {
86+
key, err := v.keyObj.CanonicalValue()
87+
if err == nil {
88+
keyHash := sha256.Sum256(key)
89+
result = append(result, fmt.Sprintf("sha256:%s", strings.ToLower(hex.EncodeToString(keyHash[:]))))
90+
91+
// add digest over any email addresses within signing certificate
92+
result = append(result, v.keyObj.EmailAddresses()...)
93+
} else {
94+
log.Logger.Errorf("could not canonicalize public key to include in index keys: %w", err)
8595
}
96+
} else {
97+
log.Logger.Error("could not find public key to include in index keys")
98+
}
8699

100+
// add digest base64-decoded payload inside of DSSE envelope
101+
payloadBytes, err := base64.StdEncoding.DecodeString(v.env.Payload)
102+
if err == nil {
103+
payloadHash := sha256.Sum256(payloadBytes)
104+
result = append(result, fmt.Sprintf("sha256:%s", strings.ToLower(hex.EncodeToString(payloadHash[:]))))
105+
} else {
106+
log.Logger.Errorf("error decoding intoto payload to compute digest: %w", err)
107+
}
108+
109+
switch v.env.PayloadType {
110+
case in_toto.PayloadType:
87111
statement, err := parseStatement(v.env.Payload)
88112
if err != nil {
89-
return result, err
113+
log.Logger.Errorf("error parsing payload as intoto statement: %w", err)
114+
break
90115
}
91116
for _, s := range statement.Subject {
92117
for alg, ds := range s.Digest {
@@ -106,7 +131,7 @@ func (v V001Entry) IndexKeys() ([]string, error) {
106131
}
107132
}
108133
default:
109-
log.Logger.Infof("Unknown in_toto Statement Type: %s", v.env.PayloadType)
134+
log.Logger.Infof("unknown in_toto statement type (%s), cannot extract additional index keys", v.env.PayloadType)
110135
}
111136
return result, nil
112137
}
@@ -180,8 +205,7 @@ func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) {
180205
},
181206
},
182207
}
183-
attKey, attValue := v.AttestationKeyValue()
184-
if attValue != nil {
208+
if attKey, attValue := v.AttestationKeyValue(); attValue != nil {
185209
canonicalEntry.Content.PayloadHash = &models.IntotoV001SchemaContentPayloadHash{
186210
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
187211
Value: swag.String(strings.Replace(attKey, fmt.Sprintf("%s:", models.IntotoV001SchemaContentHashAlgorithmSha256), "", 1)),

pkg/types/intoto/v0.0.1/entry_test.go

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
package intoto
1717

1818
import (
19+
"bytes"
20+
"context"
1921
"crypto"
2022
"crypto/ecdsa"
2123
"crypto/elliptic"
@@ -33,13 +35,16 @@ import (
3335
"strings"
3436
"testing"
3537

38+
"github.com/go-openapi/runtime"
3639
"github.com/go-openapi/strfmt"
3740
"github.com/go-openapi/swag"
3841
"github.com/google/go-cmp/cmp"
42+
"github.com/google/go-cmp/cmp/cmpopts"
3943
"github.com/in-toto/in-toto-golang/in_toto"
4044
slsa "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2"
4145
"github.com/secure-systems-lab/go-securesystemslib/dsse"
4246
"github.com/sigstore/rekor/pkg/generated/models"
47+
"github.com/sigstore/rekor/pkg/types"
4348
"github.com/sigstore/sigstore/pkg/signature"
4449
"go.uber.org/goleak"
4550
)
@@ -105,7 +110,8 @@ func TestV001Entry_Unmarshal(t *testing.T) {
105110
}
106111

107112
ca := &x509.Certificate{
108-
SerialNumber: big.NewInt(1),
113+
SerialNumber: big.NewInt(1),
114+
EmailAddresses: []string{"joe@schmoe.com"},
109115
}
110116
caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &priv.PublicKey, priv)
111117
if err != nil {
@@ -131,10 +137,11 @@ func TestV001Entry_Unmarshal(t *testing.T) {
131137
validPayload := "hellothispayloadisvalid"
132138

133139
tests := []struct {
134-
name string
135-
want models.IntotoV001Schema
136-
it *models.IntotoV001Schema
137-
wantErr bool
140+
name string
141+
want models.IntotoV001Schema
142+
it *models.IntotoV001Schema
143+
wantErr bool
144+
additionalIndexKeys []string
138145
}{
139146
{
140147
name: "empty",
@@ -156,7 +163,20 @@ func TestV001Entry_Unmarshal(t *testing.T) {
156163
wantErr: true,
157164
},
158165
{
159-
name: "valid",
166+
name: "valid intoto",
167+
it: &models.IntotoV001Schema{
168+
PublicKey: p(pub),
169+
Content: &models.IntotoV001SchemaContent{
170+
Envelope: envelope(t, key, validPayload, "application/vnd.in-toto+json"),
171+
Hash: &models.IntotoV001SchemaContentHash{
172+
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
173+
},
174+
},
175+
},
176+
wantErr: false,
177+
},
178+
{
179+
name: "valid dsse but invalid intoto",
160180
it: &models.IntotoV001Schema{
161181
PublicKey: p(pub),
162182
Content: &models.IntotoV001SchemaContent{
@@ -179,7 +199,8 @@ func TestV001Entry_Unmarshal(t *testing.T) {
179199
},
180200
},
181201
},
182-
wantErr: false,
202+
additionalIndexKeys: []string{"joe@schmoe.com"},
203+
wantErr: false,
183204
},
184205
{
185206
name: "invalid",
@@ -213,6 +234,7 @@ func TestV001Entry_Unmarshal(t *testing.T) {
213234
v := &V001Entry{}
214235
if tt.it.Content != nil {
215236
h := sha256.Sum256([]byte(tt.it.Content.Envelope))
237+
tt.it.Content.Hash.Algorithm = swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256)
216238
tt.it.Content.Hash.Value = swag.String(hex.EncodeToString(h[:]))
217239
}
218240

@@ -227,13 +249,41 @@ func TestV001Entry_Unmarshal(t *testing.T) {
227249
if err := v.validate(); err != nil {
228250
return err
229251
}
230-
sha := sha256.Sum256([]byte(v.env.Payload))
252+
keysWanted := tt.additionalIndexKeys
253+
if tt.it.PublicKey != nil {
254+
h := sha256.Sum256(*tt.it.PublicKey)
255+
keysWanted = append(keysWanted, fmt.Sprintf("sha256:%s", hex.EncodeToString(h[:])))
256+
}
257+
payloadBytes, _ := v.env.DecodeB64Payload()
258+
payloadSha := sha256.Sum256(payloadBytes)
259+
payloadHash := hex.EncodeToString(payloadSha[:])
231260
// Always start with the hash
232-
want := []string{"sha256:" + hex.EncodeToString(sha[:])}
261+
keysWanted = append(keysWanted, "sha256:"+payloadHash)
233262
hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *tt.it.Content.Hash.Algorithm, *tt.it.Content.Hash.Value))
234-
want = append(want, hashkey)
235-
if got, _ := v.IndexKeys(); !reflect.DeepEqual(got, want) {
236-
t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, tt.want)
263+
keysWanted = append(keysWanted, hashkey)
264+
if got, _ := v.IndexKeys(); !cmp.Equal(got, keysWanted, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
265+
t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, keysWanted)
266+
}
267+
canonicalBytes, err := v.Canonicalize(context.Background())
268+
if err != nil {
269+
t.Errorf("error canonicalizing entry: %v", err)
270+
}
271+
272+
pe, err := models.UnmarshalProposedEntry(bytes.NewReader(canonicalBytes), runtime.JSONConsumer())
273+
if err != nil {
274+
t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tt.name, err)
275+
}
276+
canonicalEntry, err := types.NewEntry(pe)
277+
if err != nil {
278+
t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tt.name, err)
279+
}
280+
canonicalV001 := canonicalEntry.(*V001Entry)
281+
fmt.Printf("%v", canonicalV001.IntotoObj.Content)
282+
if *canonicalV001.IntotoObj.Content.Hash.Value != *tt.it.Content.Hash.Value {
283+
t.Errorf("envelope hashes do not match post canonicalization: %v %v", *canonicalV001.IntotoObj.Content.Hash.Value, *tt.it.Content.Hash.Value)
284+
}
285+
if canonicalV001.AttestationKey() != "" && *canonicalV001.IntotoObj.Content.PayloadHash.Value != payloadHash {
286+
t.Errorf("payload hashes do not match post canonicalization: %v %v", canonicalV001.IntotoObj.Content.PayloadHash.Value, payloadHash)
237287
}
238288

239289
return nil
@@ -316,7 +366,7 @@ func TestV001Entry_IndexKeys(t *testing.T) {
316366
PayloadType: in_toto.PayloadType,
317367
},
318368
}
319-
sha := sha256.Sum256([]byte(payload))
369+
sha := sha256.Sum256(b)
320370
// Always start with the hash
321371
want := []string{"sha256:" + hex.EncodeToString(sha[:])}
322372
want = append(want, tt.want...)
@@ -355,7 +405,7 @@ func TestIndexKeysNoContentHash(t *testing.T) {
355405
PayloadType: in_toto.PayloadType,
356406
},
357407
}
358-
sha := sha256.Sum256([]byte(payload))
408+
sha := sha256.Sum256(b)
359409
// Always start with the hash
360410
want := []string{"sha256:" + hex.EncodeToString(sha[:])}
361411
want = append(want, "sha256:mysha256digest")

0 commit comments

Comments
 (0)