Repository: sigstore/rekor
Pull request #1777 (2023-10-18 21:55:21)

Add method to get artifact hash for an entry
This can be used to get the entry's artifact hash without requiring unmarshaling the type.

<!--
Thanks for opening a pull request! Please do not just delete this text.  The three fields below are mandatory.

Please remember to:
- This PR requires an issue. If it is a new feature, the issue should proceed the PR and will have allowed sufficent time for discussions to take place. Please use
issue tags such as "Closes #XYZ" or "Resolves sigstore/repo-name#XYZ".
  [documentation](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
- ensure your commits are signed-off, as sigstore uses the [DCO](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) using `git commit -s`, or `git commit -s --amend` if you want to amend already existing commits
- lastly, ensure there are no merge commits!

Thank you :)
-->

#### Summary
<!--
 Explain the **motivation** for making this change. What existing problem does the pull request solve? How can reviewers test this PR?
-->

#### Release Note
<!--
Add a release note for each of the following conditions:

* Config changes (additions, deletions, updates)
* API additions—new endpoint, new response fields, or newly accepted request parameters
* Database changes (any)
* Websocket additions or changes
* Anything noteworthy to an administrator running private sigstore instances (err on the side of over-communicating)
* New features and improvements, including behavioural changes, UI changes and CLI changes
* Bug fixes and fixes of previous known issues
* Deprecation warnings, breaking changes, or compatibility notes

If no release notes are required write NONE. Use past-tense.

-->

#### Documentation
<!--

Does this change require an update to documentation? How will users implement your new feature?

Please reference a PR within https://docs.sigstore.dev

-->


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) Report
> Merging [#1777](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) (6a86e66) into [main](https://app.codecov.io/gh/sigstore/rekor/commit/2143e33f8398d4694f27de71fee5597aa05c9b8f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) (2143e33) will **decrease** coverage by `0.10%`.
> The diff coverage is `37.50%`.

```diff
@@            Coverage Diff             @@
##             main    #1777      +/-   ##
==========================================
- Coverage   47.70%   47.60%   -0.10%     
==========================================
  Files          78       78              
  Lines        8182     8262      +80     
==========================================
+ Hits         3903     3933      +30     
- Misses       3902     3936      +34     
- Partials      377      393      +16     
```

| [Flag](https://app.codecov.io/gh/sigstore/rekor/pull/1777/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/sigstore/rekor/pull/1777/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) | `47.60% <37.50%> (-0.10%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) | Coverage Δ | |
|---|---|---|
| [pkg/types/entries.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2VudHJpZXMuZ28=) | `4.61% <ø> (ø)` | |
| [pkg/types/test\_util.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL3Rlc3RfdXRpbC5nbw==) | `0.00% <0.00%> (ø)` | |
| [pkg/types/alpine/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2FscGluZS92MC4wLjEvZW50cnkuZ28=) | `57.29% <40.00%> (-0.33%)` | :arrow_down: |
| [pkg/types/cose/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2Nvc2UvdjAuMC4xL2VudHJ5Lmdv) | `69.61% <40.00%> (-0.59%)` | :arrow_down: |
| [pkg/types/dsse/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2Rzc2UvdjAuMC4xL2VudHJ5Lmdv) | `57.04% <40.00%> (-0.30%)` | :arrow_down: |
| [pkg/types/hashedrekord/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2hhc2hlZHJla29yZC92MC4wLjEvZW50cnkuZ28=) | `59.29% <40.00%> (-0.50%)` | :arrow_down: |
| [pkg/types/helm/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2hlbG0vdjAuMC4xL2VudHJ5Lmdv) | `52.94% <40.00%> (-0.25%)` | :arrow_down: |
| [pkg/types/intoto/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2ludG90by92MC4wLjEvZW50cnkuZ28=) | `54.81% <40.00%> (-0.28%)` | :arrow_down: |
| [pkg/types/intoto/v0.0.2/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2ludG90by92MC4wLjIvZW50cnkuZ28=) | `52.92% <40.00%> (-0.19%)` | :arrow_down: |
| [pkg/types/jar/v0.0.1/entry.go](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore#diff-cGtnL3R5cGVzL2phci92MC4wLjEvZW50cnkuZ28=) | `43.51% <40.00%> (-0.08%)` | :arrow_down: |
| ... and [4 more](https://app.codecov.io/gh/sigstore/rekor/pull/1777?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=sigstore) | |



Patch:
diff --git a/pkg/types/alpine/v0.0.1/entry.go b/pkg/types/alpine/v0.0.1/entry.go
index cd3f20b..d4998bb 100644
--- a/pkg/types/alpine/v0.0.1/entry.go
+++ b/pkg/types/alpine/v0.0.1/entry.go
@@ -362,6 +362,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.AlpineModel.Package == nil || v.AlpineModel.Package.Hash == nil || v.AlpineModel.Package.Hash.Value == nil || v.AlpineModel.Package.Hash.Algorithm == nil {
+		return "", errors.New("alpine v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.AlpineModel.Package.Hash.Algorithm, *v.AlpineModel.Package.Hash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.AlpineModel.Package == nil {
 		return false, fmt.Errorf("missing package entry")
diff --git a/pkg/types/cose/v0.0.1/entry.go b/pkg/types/cose/v0.0.1/entry.go
index d5a74b4..930afb4 100644
--- a/pkg/types/cose/v0.0.1/entry.go
+++ b/pkg/types/cose/v0.0.1/entry.go
@@ -359,6 +359,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.CoseObj.Data == nil || v.CoseObj.Data.PayloadHash == nil || v.CoseObj.Data.PayloadHash.Value == nil || v.CoseObj.Data.PayloadHash.Algorithm == nil {
+		return "", errors.New("cose v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.CoseObj.Data.PayloadHash.Algorithm, *v.CoseObj.Data.PayloadHash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if len(v.CoseObj.Message) == 0 {
 		return false, errors.New("missing COSE Sign1 message")
diff --git a/pkg/types/dsse/v0.0.1/entry.go b/pkg/types/dsse/v0.0.1/entry.go
index ba41742..fb62a22 100644
--- a/pkg/types/dsse/v0.0.1/entry.go
+++ b/pkg/types/dsse/v0.0.1/entry.go
@@ -419,6 +419,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return keys, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.DSSEObj.PayloadHash == nil || v.DSSEObj.PayloadHash.Algorithm == nil || v.DSSEObj.PayloadHash.Value == nil {
+		return "", errors.New("dsse v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.DSSEObj.PayloadHash.Algorithm, *v.DSSEObj.PayloadHash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.DSSEObj.ProposedContent == nil {
 		return false, errors.New("missing proposed content")
diff --git a/pkg/types/entries.go b/pkg/types/entries.go
index 3bbf47e..c256596 100644
--- a/pkg/types/entries.go
+++ b/pkg/types/entries.go
@@ -37,8 +37,9 @@ type EntryImpl interface {
 	Canonicalize(ctx context.Context) ([]byte, error) // marshal the canonical entry to be put into the tlog
 	Unmarshal(e models.ProposedEntry) error           // unmarshal the abstract entry into the specific struct for this versioned type
 	CreateFromArtifactProperties(context.Context, ArtifactProperties) (models.ProposedEntry, error)
-	Verifiers() ([]pki.PublicKey, error)
-	Insertable() (bool, error) // denotes whether the entry that was unmarshalled has the writeOnly fields required to validate and insert into the log
+	Verifiers() ([]pki.PublicKey, error) // list of keys or certificates that can verify an entry's signature
+	ArtifactHash() (string, error)       // hex-encoded artifact hash prefixed with hash name, e.g. sha256:abcdef
+	Insertable() (bool, error)           // denotes whether the entry that was unmarshalled has the writeOnly fields required to validate and insert into the log
 }
 
 // EntryWithAttestationImpl specifies the behavior of a versioned type that also stores attestations
diff --git a/pkg/types/hashedrekord/v0.0.1/entry.go b/pkg/types/hashedrekord/v0.0.1/entry.go
index 5fdef02..5f0cb13 100644
--- a/pkg/types/hashedrekord/v0.0.1/entry.go
+++ b/pkg/types/hashedrekord/v0.0.1/entry.go
@@ -257,6 +257,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.HashedRekordObj.Data == nil || v.HashedRekordObj.Data.Hash == nil || v.HashedRekordObj.Data.Hash.Value == nil || v.HashedRekordObj.Data.Hash.Algorithm == nil {
+		return "", errors.New("hashedrekord v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.HashedRekordObj.Data.Hash.Algorithm, *v.HashedRekordObj.Data.Hash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.HashedRekordObj.Signature == nil {
 		return false, errors.New("missing signature property")
diff --git a/pkg/types/helm/v0.0.1/entry.go b/pkg/types/helm/v0.0.1/entry.go
index 9100a7d..80e4395 100644
--- a/pkg/types/helm/v0.0.1/entry.go
+++ b/pkg/types/helm/v0.0.1/entry.go
@@ -360,6 +360,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.HelmObj.Chart == nil || v.HelmObj.Chart.Hash == nil || v.HelmObj.Chart.Hash.Algorithm == nil || v.HelmObj.Chart.Hash.Value == nil {
+		return "", errors.New("helm v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.HelmObj.Chart.Hash.Algorithm, *v.HelmObj.Chart.Hash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.HelmObj.PublicKey == nil {
 		return false, errors.New("missing public key property")
diff --git a/pkg/types/intoto/v0.0.1/entry.go b/pkg/types/intoto/v0.0.1/entry.go
index ceb7371..e44c767 100644
--- a/pkg/types/intoto/v0.0.1/entry.go
+++ b/pkg/types/intoto/v0.0.1/entry.go
@@ -364,6 +364,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.IntotoObj.Content == nil || v.IntotoObj.Content.PayloadHash == nil || v.IntotoObj.Content.PayloadHash.Algorithm == nil || v.IntotoObj.Content.PayloadHash.Value == nil {
+		return "", errors.New("hashedrekord v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.IntotoObj.Content == nil {
 		return false, errors.New("missing content property")
diff --git a/pkg/types/intoto/v0.0.2/entry.go b/pkg/types/intoto/v0.0.2/entry.go
index 69a9b4c..0ae4a9d 100644
--- a/pkg/types/intoto/v0.0.2/entry.go
+++ b/pkg/types/intoto/v0.0.2/entry.go
@@ -478,6 +478,13 @@ func (v V002Entry) Verifiers() ([]pki.PublicKey, error) {
 	return keys, nil
 }
 
+func (v V002Entry) ArtifactHash() (string, error) {
+	if v.IntotoObj.Content == nil || v.IntotoObj.Content.PayloadHash == nil || v.IntotoObj.Content.PayloadHash.Algorithm == nil || v.IntotoObj.Content.PayloadHash.Value == nil {
+		return "", errors.New("intoto v0.0.2 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.IntotoObj.Content.PayloadHash.Algorithm, *v.IntotoObj.Content.PayloadHash.Value)), nil
+}
+
 func (v V002Entry) Insertable() (bool, error) {
 	if v.IntotoObj.Content == nil {
 		return false, errors.New("missing content property")
diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go
index 01fc6ac..ec25ad5 100644
--- a/pkg/types/jar/v0.0.1/entry.go
+++ b/pkg/types/jar/v0.0.1/entry.go
@@ -349,6 +349,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.JARModel.Archive == nil || v.JARModel.Archive.Hash == nil || v.JARModel.Archive.Hash.Value == nil || v.JARModel.Archive.Hash.Algorithm == nil {
+		return "", errors.New("jar v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.JARModel.Archive.Hash.Algorithm, *v.JARModel.Archive.Hash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.JARModel.Archive == nil {
 		return false, errors.New("missing archive property")
diff --git a/pkg/types/rekord/v0.0.1/entry.go b/pkg/types/rekord/v0.0.1/entry.go
index da0af23..619f19f 100644
--- a/pkg/types/rekord/v0.0.1/entry.go
+++ b/pkg/types/rekord/v0.0.1/entry.go
@@ -450,6 +450,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.RekordObj.Data == nil || v.RekordObj.Data.Hash == nil || v.RekordObj.Data.Hash.Value == nil || v.RekordObj.Data.Hash.Algorithm == nil {
+		return "", errors.New("rekord v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.RekordObj.Data.Hash.Algorithm, *v.RekordObj.Data.Hash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.RekordObj.Signature == nil {
 		return false, errors.New("missing signature property")
diff --git a/pkg/types/rfc3161/v0.0.1/entry.go b/pkg/types/rfc3161/v0.0.1/entry.go
index 9a6c049..dfac12b 100644
--- a/pkg/types/rfc3161/v0.0.1/entry.go
+++ b/pkg/types/rfc3161/v0.0.1/entry.go
@@ -26,6 +26,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/sigstore/rekor/pkg/pki"
 	"github.com/sigstore/rekor/pkg/types/rfc3161"
@@ -211,6 +212,18 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return nil, errors.New("Verifiers() does not support rfc3161 entry type")
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.Rfc3161Obj.Tsr == nil || v.Rfc3161Obj.Tsr.Content == nil {
+		return "", errors.New("rfc3161 v0.0.1 entry not initialized")
+	}
+	tsrDecoded, err := base64.StdEncoding.DecodeString(v.Rfc3161Obj.Tsr.Content.String())
+	if err != nil {
+		return "", err
+	}
+	h := sha256.Sum256(tsrDecoded)
+	return strings.ToLower(fmt.Sprintf("sha256:%s", hex.EncodeToString(h[:]))), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.Rfc3161Obj.Tsr == nil {
 		return false, errors.New("missing tsr property")
diff --git a/pkg/types/rpm/v0.0.1/entry.go b/pkg/types/rpm/v0.0.1/entry.go
index fd3b91c..d83527e 100644
--- a/pkg/types/rpm/v0.0.1/entry.go
+++ b/pkg/types/rpm/v0.0.1/entry.go
@@ -382,6 +382,13 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.RPMModel.Package == nil || v.RPMModel.Package.Hash == nil || v.RPMModel.Package.Hash.Value == nil || v.RPMModel.Package.Hash.Algorithm == nil {
+		return "", errors.New("rpm v0.0.1 entry not initialized")
+	}
+	return strings.ToLower(fmt.Sprintf("%s:%s", *v.RPMModel.Package.Hash.Algorithm, *v.RPMModel.Package.Hash.Value)), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.RPMModel.PublicKey == nil {
 		return false, errors.New("missing publicKey property")
diff --git a/pkg/types/tuf/v0.0.1/entry.go b/pkg/types/tuf/v0.0.1/entry.go
index be24fd6..608162e 100644
--- a/pkg/types/tuf/v0.0.1/entry.go
+++ b/pkg/types/tuf/v0.0.1/entry.go
@@ -385,6 +385,26 @@ func (v V001Entry) Verifiers() ([]pki.PublicKey, error) {
 	return []pki.PublicKey{key}, nil
 }
 
+func (v V001Entry) ArtifactHash() (string, error) {
+	if v.TufObj.Metadata == nil || v.TufObj.Metadata.Content == nil {
+		return "", errors.New("tuf v0.0.1 entry not initialized")
+	}
+	sigBytes, err := v.parseMetadataContent()
+	if err != nil {
+		return "", err
+	}
+	sig, err := ptuf.NewSignature(bytes.NewReader(sigBytes))
+	if err != nil {
+		return "", err
+	}
+	metadata, err := sig.CanonicalValue()
+	if err != nil {
+		return "", err
+	}
+	metadataHash := sha256.Sum256(metadata)
+	return strings.ToLower(fmt.Sprintf("sha256:%s", hex.EncodeToString(metadataHash[:]))), nil
+}
+
 func (v V001Entry) Insertable() (bool, error) {
 	if v.TufObj.Metadata == nil {
 		return false, errors.New("missing metadata property")


Test patch:
diff --git a/pkg/types/alpine/v0.0.1/entry_test.go b/pkg/types/alpine/v0.0.1/entry_test.go
index 03fd600..3df4689 100644
--- a/pkg/types/alpine/v0.0.1/entry_test.go
+++ b/pkg/types/alpine/v0.0.1/entry_test.go
@@ -18,6 +18,8 @@ package alpine
 import (
 	"bytes"
 	"context"
+	"crypto/sha256"
+	"encoding/hex"
 	"os"
 	"reflect"
 	"testing"
@@ -177,6 +179,13 @@ func TestCrossFieldValidation(t *testing.T) {
 			if ok, err := ei.Insertable(); ok || err == nil {
 				t.Errorf("unexpected success calling Insertable on entry created from canonicalized content")
 			}
+			hash, err := ei.ArtifactHash()
+			expectedHash := sha256.Sum256(dataBytes)
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:"+hex.EncodeToString(expectedHash[:]) {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 
 		verifiers, err := v.Verifiers()
diff --git a/pkg/types/cose/v0.0.1/entry_test.go b/pkg/types/cose/v0.0.1/entry_test.go
index f6b7a41..7f60b3c 100644
--- a/pkg/types/cose/v0.0.1/entry_test.go
+++ b/pkg/types/cose/v0.0.1/entry_test.go
@@ -303,6 +303,13 @@ func TestV001Entry_Unmarshal(t *testing.T) {
 						if ok, err := ei.Insertable(); ok || err == nil {
 							t.Errorf("entry created from canonicalized entry should not also be insertable")
 						}
+						hash, err := ei.ArtifactHash()
+						expectedHash := sha256.Sum256([]byte("hello"))
+						if err != nil {
+							t.Errorf("unexpected failure with ArtifactHash: %v", err)
+						} else if hash != "sha256:"+hex.EncodeToString(expectedHash[:]) {
+							t.Errorf("unexpected match with ArtifactHash: %s", hash)
+						}
 					}
 				}
 
diff --git a/pkg/types/dsse/v0.0.1/entry_test.go b/pkg/types/dsse/v0.0.1/entry_test.go
index e59e46f..77cfe66 100644
--- a/pkg/types/dsse/v0.0.1/entry_test.go
+++ b/pkg/types/dsse/v0.0.1/entry_test.go
@@ -306,6 +306,14 @@ func TestV001Entry_Unmarshal(t *testing.T) {
 					t.Errorf("index keys from hydrated object do not match those generated from canonicalized (and re-hydrated) object: %v %v", got, canonicalIndexKeys)
 				}
 
+				hash, err := canonicalV001.ArtifactHash()
+				expectedHash := sha256.Sum256([]byte(validPayload))
+				if err != nil {
+					t.Errorf("unexpected failure with ArtifactHash: %v", err)
+				} else if hash != "sha256:"+hex.EncodeToString(expectedHash[:]) {
+					t.Errorf("unexpected match with ArtifactHash: %s", hash)
+				}
+
 				return nil
 			}
 			if err := uv(); (err != nil) != tt.wantErr {
diff --git a/pkg/types/hashedrekord/hashedrekord_test.go b/pkg/types/hashedrekord/hashedrekord_test.go
index aeb2975..e75ab22 100644
--- a/pkg/types/hashedrekord/hashedrekord_test.go
+++ b/pkg/types/hashedrekord/hashedrekord_test.go
@@ -47,6 +47,10 @@ func (u UnmarshalFailsTester) Verifiers() ([]pki.PublicKey, error) {
 	return nil, nil
 }
 
+func (u UnmarshalFailsTester) ArtifactHash() (string, error) {
+	return "", nil
+}
+
 func TestRekordType(t *testing.T) {
 	// empty to start
 	if VersionMap.Count() != 0 {
diff --git a/pkg/types/hashedrekord/v0.0.1/entry_test.go b/pkg/types/hashedrekord/v0.0.1/entry_test.go
index 6c9fb2f..343712d 100644
--- a/pkg/types/hashedrekord/v0.0.1/entry_test.go
+++ b/pkg/types/hashedrekord/v0.0.1/entry_test.go
@@ -289,6 +289,13 @@ func TestCrossFieldValidation(t *testing.T) {
 			if !ok || err != nil {
 				t.Errorf("unexpected failure in testing insertable on valid entry: %v", err)
 			}
+
+			hash, err := v.ArtifactHash()
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:"+dataSHA {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 
 		b, err := v.Canonicalize(context.TODO())
@@ -313,6 +320,12 @@ func TestCrossFieldValidation(t *testing.T) {
 			if !ok || err != nil {
 				t.Errorf("unexpected failure in testing insertable on entry created from canonicalized content: %v", err)
 			}
+			hash, err := ei.ArtifactHash()
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:"+dataSHA {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 
 		verifiers, err := v.Verifiers()
diff --git a/pkg/types/helm/v0.0.1/entry_test.go b/pkg/types/helm/v0.0.1/entry_test.go
index d9f79eb..510e1bd 100644
--- a/pkg/types/helm/v0.0.1/entry_test.go
+++ b/pkg/types/helm/v0.0.1/entry_test.go
@@ -209,6 +209,12 @@ func TestCrossFieldValidation(t *testing.T) {
 				if ok, err := ei.Insertable(); ok || err == nil {
 					t.Errorf("unexpected success calling Insertable on entry created from canonicalized content")
 				}
+				hash, err := ei.ArtifactHash()
+				if err != nil {
+					t.Errorf("unexpected failure with ArtifactHash: %v", err)
+				} else if hash != "sha256:6dec7ea21e655d5796c1e214cfb75b73428b2abfa2e66c8f7bc64ff4a7b3b29f" {
+					t.Errorf("unexpected match with ArtifactHash: %s", hash)
+				}
 			}
 
 			verifiers, err := v.Verifiers()
diff --git a/pkg/types/intoto/v0.0.1/entry_test.go b/pkg/types/intoto/v0.0.1/entry_test.go
index 23eb549..3fb6134 100644
--- a/pkg/types/intoto/v0.0.1/entry_test.go
+++ b/pkg/types/intoto/v0.0.1/entry_test.go
@@ -336,6 +336,14 @@ func TestV001Entry_Unmarshal(t *testing.T) {
 					t.Errorf("index keys from hydrated object do not match those generated from canonicalized (and re-hydrated) object: %v %v", got, canonicalIndexKeys)
 				}
 
+				hash, err := canonicalV001.ArtifactHash()
+				expectedHash := sha256.Sum256([]byte(validPayload))
+				if err != nil {
+					t.Errorf("unexpected failure with ArtifactHash: %v", err)
+				} else if hash != "sha256:"+hex.EncodeToString(expectedHash[:]) {
+					t.Errorf("unexpected match with ArtifactHash: %s", hash)
+				}
+
 				verifiers, err := v.Verifiers()
 				if !tt.wantVerifierErr {
 					if err != nil {
diff --git a/pkg/types/intoto/v0.0.2/entry_test.go b/pkg/types/intoto/v0.0.2/entry_test.go
index cde6970..f26518c 100644
--- a/pkg/types/intoto/v0.0.2/entry_test.go
+++ b/pkg/types/intoto/v0.0.2/entry_test.go
@@ -377,6 +377,14 @@ func TestV002Entry_Unmarshal(t *testing.T) {
 					t.Errorf("index keys from hydrated object do not match those generated from canonicalized (and re-hydrated) object: %v %v", got, canonicalIndexKeys)
 				}
 
+				hash, err := canonicalV002.ArtifactHash()
+				expectedHash := sha256.Sum256([]byte(validPayload))
+				if err != nil {
+					t.Errorf("unexpected failure with ArtifactHash: %v", err)
+				} else if hash != "sha256:"+hex.EncodeToString(expectedHash[:]) {
+					t.Errorf("unexpected match with ArtifactHash: %s", hash)
+				}
+
 				verifiers, err := v.Verifiers()
 				if !tt.wantVerifierErr {
 					if err != nil {
diff --git a/pkg/types/jar/v0.0.1/entry_test.go b/pkg/types/jar/v0.0.1/entry_test.go
index 9a096b3..ad857ab 100644
--- a/pkg/types/jar/v0.0.1/entry_test.go
+++ b/pkg/types/jar/v0.0.1/entry_test.go
@@ -140,6 +140,12 @@ Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
 			if ok, err := ei.Insertable(); ok || err == nil {
 				t.Errorf("unexpected err from calling Insertable on entry created from canonicalized content")
 			}
+			hash, err := ei.ArtifactHash()
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:4a9d0ab4e10597497ed6c4617c983c35fa9e964e75cdd6f9fae3a0da1929acc6" {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 
 		verifiers, err := v.Verifiers()
diff --git a/pkg/types/rekord/v0.0.1/entry_test.go b/pkg/types/rekord/v0.0.1/entry_test.go
index e236a50..7c2dee2 100644
--- a/pkg/types/rekord/v0.0.1/entry_test.go
+++ b/pkg/types/rekord/v0.0.1/entry_test.go
@@ -18,6 +18,8 @@ package rekord
 import (
 	"bytes"
 	"context"
+	"crypto/sha256"
+	"encoding/hex"
 	"os"
 	"reflect"
 	"testing"
@@ -256,6 +258,13 @@ func TestCrossFieldValidation(t *testing.T) {
 			if ok, err := ei.Insertable(); ok || err == nil {
 				t.Errorf("unexpected success calling Insertable on entry created from canonicalized content")
 			}
+			hash, err := ei.ArtifactHash()
+			expectedHash := sha256.Sum256(dataBytes)
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:"+hex.EncodeToString(expectedHash[:]) {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 
 		verifiers, err := v.Verifiers()
diff --git a/pkg/types/rfc3161/v0.0.1/entry_test.go b/pkg/types/rfc3161/v0.0.1/entry_test.go
index 6c54574..b115acc 100644
--- a/pkg/types/rfc3161/v0.0.1/entry_test.go
+++ b/pkg/types/rfc3161/v0.0.1/entry_test.go
@@ -197,6 +197,12 @@ func TestCrossFieldValidation(t *testing.T) {
 			if ok, err := ei.Insertable(); !ok || err != nil {
 				t.Errorf("unexpected error calling insertable on entry created from canonicalized content")
 			}
+			hash, err := ei.ArtifactHash()
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:6dbda791c778454d0baa96e1dec5fa2c50867fd22d119ebe75c60c513b254c91" {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 	}
 }
diff --git a/pkg/types/rpm/v0.0.1/entry_test.go b/pkg/types/rpm/v0.0.1/entry_test.go
index 9b1ec7b..539c21d 100644
--- a/pkg/types/rpm/v0.0.1/entry_test.go
+++ b/pkg/types/rpm/v0.0.1/entry_test.go
@@ -194,6 +194,12 @@ func TestCrossFieldValidation(t *testing.T) {
 			if ok, err := ei.Insertable(); ok || err == nil {
 				t.Errorf("unexpected success calling Insertable on entry created from canonicalized content")
 			}
+			hash, err := ei.ArtifactHash()
+			if err != nil {
+				t.Errorf("unexpected failure with ArtifactHash: %v", err)
+			} else if hash != "sha256:c8b0bc59708d74f53aab0089ac587d5c348d6ead143dab9f6d9c4b48c973bfd8" {
+				t.Errorf("unexpected match with ArtifactHash: %s", hash)
+			}
 		}
 
 		verifiers, err := v.Verifiers()
diff --git a/pkg/types/test_util.go b/pkg/types/test_util.go
index 529d71b..0fada89 100644
--- a/pkg/types/test_util.go
+++ b/pkg/types/test_util.go
@@ -31,6 +31,10 @@ func (u BaseUnmarshalTester) NewEntry() EntryImpl {
 	return &BaseUnmarshalTester{}
 }
 
+func (u BaseUnmarshalTester) ArtifactHash() (string, error) {
+	return "", nil
+}
+
 func (u BaseUnmarshalTester) Verifiers() ([]pki.PublicKey, error) {
 	return nil, nil
 }
diff --git a/pkg/types/tuf/v0.0.1/entry_test.go b/pkg/types/tuf/v0.0.1/entry_test.go
index 174bb8c..92ca46a 100644
--- a/pkg/types/tuf/v0.0.1/entry_test.go
+++ b/pkg/types/tuf/v0.0.1/entry_test.go
@@ -239,10 +239,17 @@ func TestCrossFieldValidation(t *testing.T) {
 				if err != nil {
 					t.Errorf("unexpected err from Unmarshalling canonicalized entry for '%v': %v", tc.caseDesc, err)
 				}
-				if _, err := types.UnmarshalEntry(pe); err != nil {
+				ei, err := types.UnmarshalEntry(pe)
+				if err != nil {
 					t.Errorf("unexpected err from type-specific unmarshalling for '%v': %v", tc.caseDesc, err)
 				}
 				// Insertable on canonicalized content is variable so we skip testing it here
+				hash, err := ei.ArtifactHash()
+				if err != nil {
+					t.Errorf("unexpected failure with ArtifactHash: %v", err)
+				} else if hash != "sha256:c170ae288c93f56031639bac1ad085fc47918346f733b3d76b07a8124ebd24f9" {
+					t.Errorf("unexpected match with ArtifactHash: %s", hash)
+				}
 			}
 
 			verifiers, err := v.Verifiers()