Skip to content

Commit 5ab865e

Browse files
authored
compute payload and envelope hashes upon validating intoto proposed entries (sigstore#967)
* Ensure envelope hash is computed at validation time Previously the digest of the envelope contents was computed before returning the canonicalized byte stream that is inserted into the log, but was not stored in the internal object representation so it was not available when IndexKeys was called. This fixes it to be available, and also has the benefit of ignoring the client provided value (as we should be relying only on server side computations for these keys). Signed-off-by: Bob Callaway <bcallaway@google.com> * also hydrate payloadHash upon unmarshalling Signed-off-by: Bob Callaway <bcallaway@google.com> Signed-off-by: Bob Callaway <bcallaway@google.com>
1 parent f9f283e commit 5ab865e

File tree

2 files changed

+48
-60
lines changed

2 files changed

+48
-60
lines changed

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

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,11 @@ func (v V001Entry) IndexKeys() ([]string, error) {
9898
}
9999

100100
// 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[:]))))
101+
if v.IntotoObj.Content != nil && v.IntotoObj.Content.PayloadHash != nil {
102+
payloadHash := strings.ToLower(fmt.Sprintf("%s:%s", swag.StringValue(v.IntotoObj.Content.PayloadHash.Algorithm), swag.StringValue(v.IntotoObj.Content.PayloadHash.Value)))
103+
result = append(result, payloadHash)
105104
} else {
106-
log.Logger.Errorf("error decoding intoto payload to compute digest: %w", err)
105+
log.Logger.Error("could not find payload digest to include in index keys")
107106
}
108107

109108
switch v.env.PayloadType {
@@ -194,23 +193,19 @@ func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) {
194193
}
195194
pkb := strfmt.Base64(pk)
196195

197-
h := sha256.Sum256([]byte(v.IntotoObj.Content.Envelope))
198-
199196
canonicalEntry := models.IntotoV001Schema{
200197
PublicKey: &pkb,
201198
Content: &models.IntotoV001SchemaContent{
202199
Hash: &models.IntotoV001SchemaContentHash{
203-
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
204-
Value: swag.String(hex.EncodeToString(h[:])),
200+
Algorithm: v.IntotoObj.Content.Hash.Algorithm,
201+
Value: v.IntotoObj.Content.Hash.Value,
202+
},
203+
PayloadHash: &models.IntotoV001SchemaContentPayloadHash{
204+
Algorithm: v.IntotoObj.Content.PayloadHash.Algorithm,
205+
Value: v.IntotoObj.Content.PayloadHash.Value,
205206
},
206207
},
207208
}
208-
if attKey, attValue := v.AttestationKeyValue(); attValue != nil {
209-
canonicalEntry.Content.PayloadHash = &models.IntotoV001SchemaContentPayloadHash{
210-
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
211-
Value: swag.String(strings.Replace(attKey, fmt.Sprintf("%s:", models.IntotoV001SchemaContentHashAlgorithmSha256), "", 1)),
212-
}
213-
}
214209

215210
itObj := models.Intoto{}
216211
itObj.APIVersion = swag.String(APIVERSION)
@@ -237,7 +232,27 @@ func (v *V001Entry) validate() error {
237232
if err := dsseVerifier.VerifySignature(strings.NewReader(v.IntotoObj.Content.Envelope), nil); err != nil {
238233
return err
239234
}
240-
return json.Unmarshal([]byte(v.IntotoObj.Content.Envelope), &v.env)
235+
if err := json.Unmarshal([]byte(v.IntotoObj.Content.Envelope), &v.env); err != nil {
236+
return err
237+
}
238+
239+
attBytes, err := base64.StdEncoding.DecodeString(v.env.Payload)
240+
if err != nil {
241+
return err
242+
}
243+
// validation logic complete without errors, hydrate local object
244+
attHash := sha256.Sum256(attBytes)
245+
v.IntotoObj.Content.PayloadHash = &models.IntotoV001SchemaContentPayloadHash{
246+
Algorithm: swag.String(models.IntotoV001SchemaContentPayloadHashAlgorithmSha256),
247+
Value: swag.String(hex.EncodeToString(attHash[:])),
248+
}
249+
250+
h := sha256.Sum256([]byte(v.IntotoObj.Content.Envelope))
251+
v.IntotoObj.Content.Hash = &models.IntotoV001SchemaContentHash{
252+
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
253+
Value: swag.String(hex.EncodeToString(h[:])),
254+
}
255+
return nil
241256
}
242257

243258
// AttestationKey returns the digest of the attestation that was uploaded, to be used to lookup the attestation from storage
@@ -256,9 +271,7 @@ func (v *V001Entry) AttestationKeyValue() (string, []byte) {
256271
return "", nil
257272
}
258273
attBytes, _ := base64.StdEncoding.DecodeString(v.env.Payload)
259-
attHash := sha256.Sum256(attBytes)
260-
attKey := fmt.Sprintf("%s:%s", models.IntotoV001SchemaContentHashAlgorithmSha256, hex.EncodeToString(attHash[:]))
261-
return attKey, attBytes
274+
return v.AttestationKey(), attBytes
262275
}
263276

264277
func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) {

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

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"encoding/hex"
2929
"encoding/json"
3030
"encoding/pem"
31+
"errors"
3132
"fmt"
3233
"math/big"
3334
"reflect"
@@ -240,6 +241,10 @@ func TestV001Entry_Unmarshal(t *testing.T) {
240241
if err := v.validate(); err != nil {
241242
return err
242243
}
244+
if v.IntotoObj.Content.Hash == nil || v.IntotoObj.Content.Hash.Algorithm != tt.it.Content.Hash.Algorithm || v.IntotoObj.Content.Hash.Value != tt.it.Content.Hash.Value {
245+
return errors.New("missing envelope hash in validated object")
246+
}
247+
243248
keysWanted := tt.additionalIndexKeys
244249
if tt.it.PublicKey != nil {
245250
h := sha256.Sum256(*tt.it.PublicKey)
@@ -252,7 +257,8 @@ func TestV001Entry_Unmarshal(t *testing.T) {
252257
keysWanted = append(keysWanted, "sha256:"+payloadHash)
253258
hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *tt.it.Content.Hash.Algorithm, *tt.it.Content.Hash.Value))
254259
keysWanted = append(keysWanted, hashkey)
255-
if got, _ := v.IndexKeys(); !cmp.Equal(got, keysWanted, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
260+
got, _ := v.IndexKeys()
261+
if !cmp.Equal(got, keysWanted, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
256262
t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, keysWanted)
257263
}
258264
canonicalBytes, err := v.Canonicalize(context.Background())
@@ -276,6 +282,10 @@ func TestV001Entry_Unmarshal(t *testing.T) {
276282
if canonicalV001.AttestationKey() != "" && *canonicalV001.IntotoObj.Content.PayloadHash.Value != payloadHash {
277283
t.Errorf("payload hashes do not match post canonicalization: %v %v", canonicalV001.IntotoObj.Content.PayloadHash.Value, payloadHash)
278284
}
285+
canonicalIndexKeys, _ := canonicalV001.IndexKeys()
286+
if !cmp.Equal(got, canonicalIndexKeys, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
287+
t.Errorf("index keys from hydrated object do not match those generated from canonicalized (and re-hydrated) object: %v %v", got, canonicalIndexKeys)
288+
}
279289

280290
return nil
281291
}
@@ -343,13 +353,18 @@ func TestV001Entry_IndexKeys(t *testing.T) {
343353
t.Fatal(err)
344354
}
345355
payload := base64.StdEncoding.EncodeToString(b)
356+
payloadHash := sha256.Sum256(b)
346357
v := V001Entry{
347358
IntotoObj: models.IntotoV001Schema{
348359
Content: &models.IntotoV001SchemaContent{
349360
Hash: &models.IntotoV001SchemaContentHash{
350361
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
351362
Value: swag.String(dataSHA),
352363
},
364+
PayloadHash: &models.IntotoV001SchemaContentPayloadHash{
365+
Algorithm: swag.String(models.IntotoV001SchemaContentPayloadHashAlgorithmSha256),
366+
Value: swag.String(hex.EncodeToString(payloadHash[:])),
367+
},
353368
},
354369
},
355370
env: dsse.Envelope{
@@ -370,43 +385,3 @@ func TestV001Entry_IndexKeys(t *testing.T) {
370385
})
371386
}
372387
}
373-
374-
func TestIndexKeysNoContentHash(t *testing.T) {
375-
statement := in_toto.Statement{
376-
Predicate: "hello",
377-
StatementHeader: in_toto.StatementHeader{
378-
Subject: []in_toto.Subject{
379-
{
380-
Name: "myimage",
381-
Digest: slsa.DigestSet{
382-
"sha256": "mysha256digest",
383-
},
384-
},
385-
},
386-
},
387-
}
388-
b, err := json.Marshal(statement)
389-
if err != nil {
390-
t.Fatal(err)
391-
}
392-
payload := base64.StdEncoding.EncodeToString(b)
393-
v := V001Entry{
394-
env: dsse.Envelope{
395-
Payload: payload,
396-
PayloadType: in_toto.PayloadType,
397-
},
398-
}
399-
sha := sha256.Sum256(b)
400-
// Always start with the hash
401-
want := []string{"sha256:" + hex.EncodeToString(sha[:])}
402-
want = append(want, "sha256:mysha256digest")
403-
got, err := v.IndexKeys()
404-
if err != nil {
405-
t.Fatal(err)
406-
}
407-
sort.Strings(got)
408-
sort.Strings(want)
409-
if !cmp.Equal(got, want) {
410-
t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, want)
411-
}
412-
}

0 commit comments

Comments
 (0)