Skip to content

Commit

Permalink
Merge pull request #330 from marcelamelara/add-digest-set-validation
Browse files Browse the repository at this point in the history
Add DigestSet hex encoding validation
  • Loading branch information
pxp928 authored Apr 8, 2024
2 parents ad3ec5f + dfaabea commit 15e7ca2
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 2 deletions.
47 changes: 45 additions & 2 deletions go/v1/resource_descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,58 @@ Wrapper APIs for in-toto attestation ResourceDescriptor protos.

package v1

import "errors"
import (
"encoding/hex"
"errors"
"fmt"
)

var ErrRDRequiredField = errors.New("at least one of name, URI, or digest are required")
var (
ErrIncorrectDigestLength = errors.New("digest has incorrect length")
ErrInvalidDigestEncoding = errors.New("digest is not valid hex-encoded string")
ErrRDRequiredField = errors.New("at least one of name, URI, or digest are required")
)

// Indicates if a given fixed-size hash algorithm is supported by default and returns the algorithm's
// digest size in bytes, if supported. We assume gitCommit and dirHash are aliases for sha1 and sha256, respectively.
//
// SHA digest sizes from https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
// MD5 digest size from https://www.rfc-editor.org/rfc/rfc1321.html#section-1
func isSupportedFixedSizeAlgorithm(alg string) (bool, int) {
algos := map[string]int{"md5": 16, "sha1": 20, "sha224": 28, "sha512_224": 28, "sha256": 32, "sha512_256": 32, "sha384": 48, "sha512": 64, "sha3_224": 28, "sha3_256": 32, "sha3_384": 48, "sha3_512": 64, "gitCommit": 20, "dirHash": 32}

size, ok := algos[alg]
return ok, size
}

func (d *ResourceDescriptor) Validate() error {
// at least one of name, URI or digest are required
if d.GetName() == "" && d.GetUri() == "" && len(d.GetDigest()) == 0 {
return ErrRDRequiredField
}

if len(d.GetDigest()) > 0 {
for alg, digest := range d.GetDigest() {

// Per https://github.com/in-toto/attestation/blob/main/spec/v1/digest_set.md
// check encoding and length for supported algorithms;
// use of custom, unsupported algorithms is allowed and does not not generate validation errors.
supported, size := isSupportedFixedSizeAlgorithm(alg)
if supported {
// the in-toto spec expects a hex-encoded string in DigestSets for supported algorithms
hashBytes, err := hex.DecodeString(digest)

if err != nil {
return fmt.Errorf("%w (%s: %s)", ErrInvalidDigestEncoding, alg, digest)
}

// check the length of the digest
if len(hashBytes) != size {
return fmt.Errorf("%w: got %d bytes, want %d bytes (%s: %s)", ErrIncorrectDigestLength, len(hashBytes), size, alg, digest)
}
}
}
}

return nil
}
36 changes: 36 additions & 0 deletions go/v1/resource_descriptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ import (

const wantFullRd = `{"name":"theName","uri":"https://example.com","digest":{"alg1":"abc123"},"content":"Ynl0ZXNjb250ZW50","downloadLocation":"https://example.com/test.zip","mediaType":"theMediaType","annotations":{"a1":{"keyNum": 13,"keyStr":"value1"},"a2":{"keyObj":{"subKey":"subVal"}}}}`

const supportedRdDigest = `{"digest":{"sha256":"a1234567b1234567c1234567d1234567e1234567f1234567a1234567b1234567","custom":"myCustomEnvoding","sha1":"a1234567b1234567c1234567d1234567e1234567"}}`

const badRd = `{"downloadLocation":"https://example.com/test.zip","mediaType":"theMediaType"}`

const badRdDigestEncoding = `{"digest":{"sha256":"badDigest"},"downloadLocation":"https://example.com/test.zip","mediaType":"theMediaType"}`

const badRdDigestLength = `{"digest":{"sha256":"abc123"},"downloadLocation":"https://example.com/test.zip","mediaType":"theMediaType"}`

func createTestResourceDescriptor() (*ResourceDescriptor, error) {
// Create a ResourceDescriptor
a, err := structpb.NewStruct(map[string]interface{}{
Expand Down Expand Up @@ -56,6 +62,16 @@ func TestJsonUnmarshalResourceDescriptor(t *testing.T) {
assert.True(t, proto.Equal(got, want), "Protos do not match")
}

func TestSupportedResourceDescriptorDigest(t *testing.T) {
got := &ResourceDescriptor{}
err := protojson.Unmarshal([]byte(supportedRdDigest), got)

assert.NoError(t, err, "Error during JSON unmarshalling")

err = got.Validate()
assert.NoError(t, err, "Error during validation of valid supported RD digests")
}

func TestBadResourceDescriptor(t *testing.T) {
got := &ResourceDescriptor{}
err := protojson.Unmarshal([]byte(badRd), got)
Expand All @@ -65,3 +81,23 @@ func TestBadResourceDescriptor(t *testing.T) {
err = got.Validate()
assert.ErrorIs(t, err, ErrRDRequiredField, "created malformed ResourceDescriptor")
}

func TestBadResourceDescriptorDigestEncoding(t *testing.T) {
got := &ResourceDescriptor{}
err := protojson.Unmarshal([]byte(badRdDigestEncoding), got)

assert.NoError(t, err, "Error during JSON unmarshalling")

err = got.Validate()
assert.ErrorIs(t, err, ErrInvalidDigestEncoding, "did not get expected error when validating ResourceDescriptor with invalid digest encoding")
}

func TestBadResourceDescriptorDigestLength(t *testing.T) {
got := &ResourceDescriptor{}
err := protojson.Unmarshal([]byte(badRdDigestLength), got)

assert.NoError(t, err, "Error during JSON unmarshalling")

err = got.Validate()
assert.ErrorIs(t, err, ErrIncorrectDigestLength, "did not get expected error when validating ResourceDescriptor with incorrect digest length")
}

0 comments on commit 15e7ca2

Please sign in to comment.