Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compute payload and envelope hashes upon validating intoto proposed entries #967

Merged
merged 2 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 32 additions & 19 deletions pkg/types/intoto/v0.0.1/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,11 @@ func (v V001Entry) IndexKeys() ([]string, error) {
}

// add digest base64-decoded payload inside of DSSE envelope
payloadBytes, err := base64.StdEncoding.DecodeString(v.env.Payload)
if err == nil {
payloadHash := sha256.Sum256(payloadBytes)
result = append(result, fmt.Sprintf("sha256:%s", strings.ToLower(hex.EncodeToString(payloadHash[:]))))
if v.IntotoObj.Content != nil && v.IntotoObj.Content.PayloadHash != nil {
payloadHash := strings.ToLower(fmt.Sprintf("%s:%s", swag.StringValue(v.IntotoObj.Content.PayloadHash.Algorithm), swag.StringValue(v.IntotoObj.Content.PayloadHash.Value)))
result = append(result, payloadHash)
} else {
log.Logger.Errorf("error decoding intoto payload to compute digest: %w", err)
log.Logger.Error("could not find payload digest to include in index keys")
}

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

h := sha256.Sum256([]byte(v.IntotoObj.Content.Envelope))

canonicalEntry := models.IntotoV001Schema{
PublicKey: &pkb,
Content: &models.IntotoV001SchemaContent{
Hash: &models.IntotoV001SchemaContentHash{
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
Value: swag.String(hex.EncodeToString(h[:])),
Algorithm: v.IntotoObj.Content.Hash.Algorithm,
Value: v.IntotoObj.Content.Hash.Value,
},
PayloadHash: &models.IntotoV001SchemaContentPayloadHash{
Algorithm: v.IntotoObj.Content.PayloadHash.Algorithm,
Value: v.IntotoObj.Content.PayloadHash.Value,
},
},
}
if attKey, attValue := v.AttestationKeyValue(); attValue != nil {
canonicalEntry.Content.PayloadHash = &models.IntotoV001SchemaContentPayloadHash{
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
Value: swag.String(strings.Replace(attKey, fmt.Sprintf("%s:", models.IntotoV001SchemaContentHashAlgorithmSha256), "", 1)),
}
}

itObj := models.Intoto{}
itObj.APIVersion = swag.String(APIVERSION)
Expand All @@ -237,7 +232,27 @@ func (v *V001Entry) validate() error {
if err := dsseVerifier.VerifySignature(strings.NewReader(v.IntotoObj.Content.Envelope), nil); err != nil {
return err
}
return json.Unmarshal([]byte(v.IntotoObj.Content.Envelope), &v.env)
if err := json.Unmarshal([]byte(v.IntotoObj.Content.Envelope), &v.env); err != nil {
return err
}

attBytes, err := base64.StdEncoding.DecodeString(v.env.Payload)
if err != nil {
return err
}
// validation logic complete without errors, hydrate local object
attHash := sha256.Sum256(attBytes)
v.IntotoObj.Content.PayloadHash = &models.IntotoV001SchemaContentPayloadHash{
Algorithm: swag.String(models.IntotoV001SchemaContentPayloadHashAlgorithmSha256),
Value: swag.String(hex.EncodeToString(attHash[:])),
}

h := sha256.Sum256([]byte(v.IntotoObj.Content.Envelope))
v.IntotoObj.Content.Hash = &models.IntotoV001SchemaContentHash{
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
Value: swag.String(hex.EncodeToString(h[:])),
}
return nil
}

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

func (v V001Entry) CreateFromArtifactProperties(_ context.Context, props types.ArtifactProperties) (models.ProposedEntry, error) {
Expand Down
57 changes: 16 additions & 41 deletions pkg/types/intoto/v0.0.1/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"encoding/hex"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"math/big"
"reflect"
Expand Down Expand Up @@ -240,6 +241,10 @@ func TestV001Entry_Unmarshal(t *testing.T) {
if err := v.validate(); err != nil {
return err
}
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 {
return errors.New("missing envelope hash in validated object")
}

keysWanted := tt.additionalIndexKeys
if tt.it.PublicKey != nil {
h := sha256.Sum256(*tt.it.PublicKey)
Expand All @@ -252,7 +257,8 @@ func TestV001Entry_Unmarshal(t *testing.T) {
keysWanted = append(keysWanted, "sha256:"+payloadHash)
hashkey := strings.ToLower(fmt.Sprintf("%s:%s", *tt.it.Content.Hash.Algorithm, *tt.it.Content.Hash.Value))
keysWanted = append(keysWanted, hashkey)
if got, _ := v.IndexKeys(); !cmp.Equal(got, keysWanted, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
got, _ := v.IndexKeys()
if !cmp.Equal(got, keysWanted, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, keysWanted)
}
canonicalBytes, err := v.Canonicalize(context.Background())
Expand All @@ -276,6 +282,10 @@ func TestV001Entry_Unmarshal(t *testing.T) {
if canonicalV001.AttestationKey() != "" && *canonicalV001.IntotoObj.Content.PayloadHash.Value != payloadHash {
t.Errorf("payload hashes do not match post canonicalization: %v %v", canonicalV001.IntotoObj.Content.PayloadHash.Value, payloadHash)
}
canonicalIndexKeys, _ := canonicalV001.IndexKeys()
if !cmp.Equal(got, canonicalIndexKeys, cmpopts.SortSlices(func(x, y string) bool { return x < y })) {
t.Errorf("index keys from hydrated object do not match those generated from canonicalized (and re-hydrated) object: %v %v", got, canonicalIndexKeys)
}

return nil
}
Expand Down Expand Up @@ -343,13 +353,18 @@ func TestV001Entry_IndexKeys(t *testing.T) {
t.Fatal(err)
}
payload := base64.StdEncoding.EncodeToString(b)
payloadHash := sha256.Sum256(b)
v := V001Entry{
IntotoObj: models.IntotoV001Schema{
Content: &models.IntotoV001SchemaContent{
Hash: &models.IntotoV001SchemaContentHash{
Algorithm: swag.String(models.IntotoV001SchemaContentHashAlgorithmSha256),
Value: swag.String(dataSHA),
},
PayloadHash: &models.IntotoV001SchemaContentPayloadHash{
Algorithm: swag.String(models.IntotoV001SchemaContentPayloadHashAlgorithmSha256),
Value: swag.String(hex.EncodeToString(payloadHash[:])),
},
},
},
env: dsse.Envelope{
Expand All @@ -370,43 +385,3 @@ func TestV001Entry_IndexKeys(t *testing.T) {
})
}
}

func TestIndexKeysNoContentHash(t *testing.T) {
statement := in_toto.Statement{
Predicate: "hello",
StatementHeader: in_toto.StatementHeader{
Subject: []in_toto.Subject{
{
Name: "myimage",
Digest: slsa.DigestSet{
"sha256": "mysha256digest",
},
},
},
},
}
b, err := json.Marshal(statement)
if err != nil {
t.Fatal(err)
}
payload := base64.StdEncoding.EncodeToString(b)
v := V001Entry{
env: dsse.Envelope{
Payload: payload,
PayloadType: in_toto.PayloadType,
},
}
sha := sha256.Sum256(b)
// Always start with the hash
want := []string{"sha256:" + hex.EncodeToString(sha[:])}
want = append(want, "sha256:mysha256digest")
got, err := v.IndexKeys()
if err != nil {
t.Fatal(err)
}
sort.Strings(got)
sort.Strings(want)
if !cmp.Equal(got, want) {
t.Errorf("V001Entry.IndexKeys() = %v, want %v", got, want)
}
}