Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Allow provenance-less endorsement generation #237

Merged
merged 13 commits into from
Jul 17, 2023

Conversation

rbehjati
Copy link
Contributor

@rbehjati rbehjati commented Jul 7, 2023

Fixes #236 and #234

  • Replaces ReferenceValues with VerificationOptions, which may or may not contain a ProvenanceReferenceValue object. The new VerificationOptions type allows invoking endorsement generation in the absence of any provenances.
  • To get a good balance between expressiveness and simplicity, VerificationOptions is a protobuf.
  • Consequently, toml files for reference value specification are replaced with textproto files.
  • Removes binary digest from reference values, and explicitly passes it, alongside binary name, to GenerateEndorsement. This is required to enable provenance-less endorsement generation.

It's a good idea to open an issue first for discussion.

  • Tests pass
  • Appropriate changes to README are included in PR

@rbehjati rbehjati changed the title Replace ReferenceValues with proto and allow no provenance verification Allow provenance-less endorsement generation Jul 12, 2023
Copy link
Contributor

@mariaschett mariaschett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intermediate Comments :)

verifiedProvenances, err := verifyAndSummarizeProvenances(referenceValues, provenances)
// GenerateEndorsement generates an endorsement statement for the given binary
// and the given validity duration, using the given provenances as evidence and
// reference values to verify them. If more than one provenance statements are
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are reference values here? Are they inside the VerificationOptions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Updated the comment. I meant VerificationOptions.

// about a set of verified provenances, or an error. An error is returned if
// any of the following conditions is met:
// (1) The list of provenances is empty, but verification options contains a
// nonempty provenance reference.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: what is a "provenance reference" and is it the same as a "reference provenance" in line 53?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I meant "reference provenance".

}

message ProvenanceReferenceValues {
bool must_have_build_command = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, but I think generally all fields to be optional is preferred. Happy to discuss offline!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. The guidelines say:

Make all fields optional or repeated. You never know how long a message type is going to last and whether someone will be forced to fill in your required field with an empty string or zero in four years when it’s no longer logically required but the proto still says it is.

But I think for primitive fields it should be fine. Even though I have not made them explicitly optional, I can leave them unset.

Making them optional causes additional an unnecessary indirection, which does not look right.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In proto3, everything is optional by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed optional from other fields too. Here is the change in the generated Go code. Some oneof annotation and a oneof wrapper are removed. I am not sure if it has any serious impact on serialization.

Copy link
Contributor Author

@rbehjati rbehjati left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review.

}

message ProvenanceReferenceValues {
bool must_have_build_command = 1;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. The guidelines say:

Make all fields optional or repeated. You never know how long a message type is going to last and whether someone will be forced to fill in your required field with an empty string or zero in four years when it’s no longer logically required but the proto still says it is.

But I think for primitive fields it should be fine. Even though I have not made them explicitly optional, I can leave them unset.

Making them optional causes additional an unnecessary indirection, which does not look right.

verifiedProvenances, err := verifyAndSummarizeProvenances(referenceValues, provenances)
// GenerateEndorsement generates an endorsement statement for the given binary
// and the given validity duration, using the given provenances as evidence and
// reference values to verify them. If more than one provenance statements are
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Updated the comment. I meant VerificationOptions.

// about a set of verified provenances, or an error. An error is returned if
// any of the following conditions is met:
// (1) The list of provenances is empty, but verification options contains a
// nonempty provenance reference.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I meant "reference provenance".

Copy link
Contributor

@mariaschett mariaschett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something on how SkipProvenanceVerification and the list of provenances interact. Could you have a look and let me know?

// VerificationOptions to verify them. If more than one provenance statements
// are provided the endorsement statement is generated only if the provenance
// statements are consistent and valid according to the input
// VerificationOptions. If no provenances are provided, a provenance-less
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A design question: might it happen, that I accidentially endorse something if I forget to add a provenance? Do we want to make this more explict? E.g., make GenerateEndorsement private but expose two methods GenerateEndorsementWithProvenance and GenerateEndorsementWithoutProvenance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is being handled by the VerificationOptions. It requires explicitly setting skip provenance verification. If that is not set, at least one provenance is required.

So I'd say those accidents are unlikely.

// endorsement is generated, if the input VerificationOptions does not contain
// a reference provenance.
func GenerateEndorsement(binaryName, binaryDigest string, verOpt *prover.VerificationOptions, validityDuration claims.ClaimValidity, provenances []ParsedProvenance) (*intoto.Statement, error) {
if (verOpt.GetSkipProvenanceVerification() == nil) && (verOpt.GetReferenceProvenance() == nil) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking: Don't you get from oneof in the proto definition that exactly one of verOpt is not nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I think this is a shortcoming of Go. I was able to create an instance where both options are null, so I had to add this check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks! I'd have expected...

@@ -73,22 +83,35 @@ func verifyAndSummarizeProvenances(referenceValues *verification.ReferenceValues
provenancesData = append(provenancesData, p.SourceMetadata)
}

errs := multierr.Append(verifyConsistency(provenanceIRs), verifyProvenances(referenceValues, provenanceIRs))
var errs error
if len(provenanceIRs) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am trying to understand what happens if we have multiple provenances but SkipProvenanceVerification.

IIUC the control flow lands here, but then GetReferenceProvenance would be nil? Is this okay with verifyProvenances?

On a higher level: What do we expect to happen then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We expect the provenances to be consistent, but they are not verified against a set of reference values. Added tests to cover these scenarios.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, sounds good!

Just to double check: In the GetReferenceProvenance case, verifyProvenances will get nil for GetReferenceProvenance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Yes. verifyProvenances is called with a nil. Added a check to immediately return if the input ProvenanceReferenceValues is nil.

@@ -69,7 +71,67 @@ func TestGenerateEndorsement_SingleValidEndorsement(t *testing.T) {
testutil.AssertEq(t, "notAfter date", predicate.Validity.NotAfter, &nextWeek)
}

func TestLoadAndVerifyProvenances_MultipleValidEndorsement(t *testing.T) {
func TestGenerateEndorsement_NoProvenance(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if you give provenances and also set SkipProvenanceVerification?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few tests and updated the documentation on GenerateEndorsement. It generates an endorsement with the given provenances as evidence, if the provenances are consistent and match the given subject.

Let me know if you expect some other behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... I am not sure I would expect "unverified provenances" as evidence, but actually: why not?

However, is it clear afterwards if the provenances were verified? Or does that not really matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are not totally unverified. But they are not verified against a set of reference values. Actually this is similar to providing verification options with an empty ReferenceProvenance. Except that the latter enforces having some provenances. Clarified this, and renamed SkipVerification to EndorseProvenanceLess to be clear that we are not skipping the entire verification logic.

However, is it clear afterwards if the provenances were verified? Or does that not really matter?

Currently we don't include the verification options in the endorsement, but I think it is a good idea to include it. As an extension. Created #238.

@@ -23,3 +23,27 @@ You need to have:
- Format files: `./scripts/formatting.sh`
- Check linting: `./scripts/linting.sh`
- Additional checks: `go vet ./...`

## Using protocol buffers

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this is mostly solved by bazel and / or nix. Especially since the instructions here refer to latest, which will stop working at some point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. We used to use bazel in this repo, and it caused a lot of problems. I think this is fine for now.

@@ -32,6 +32,7 @@ import (
"github.com/project-oak/transparent-release/internal/verification"
"github.com/project-oak/transparent-release/pkg/claims"
"github.com/project-oak/transparent-release/pkg/intoto"
prover "github.com/project-oak/transparent-release/pkg/proto/verification"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having two different packages with the same name but in different locations in the same repo seems problematic. Maybe one of them should be renamed at some point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I cannot think of a better name. I don't like long names with underscore in them.
Do you have any suggestions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the other verification into verifier. The only files in it now are verifier.go and verifier_test.go so I think that makes sense.

binaryHash = "d059c38cea82047ad316a1c6c6fbd13ecf7a0abdcc375463920bd25bf5c142cc"
binaryName = "oak_functions_freestanding_bin"
errorBinaryDigest = "do not contain the actual binary SHA256 digest"
binaryHash = "d059c38cea82047ad316a1c6c6fbd13ecf7a0abdcc375463920bd25bf5c142cc"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
binaryHash = "d059c38cea82047ad316a1c6c6fbd13ecf7a0abdcc375463920bd25bf5c142cc"
binaryDigestSha256 = "d059c38cea82047ad316a1c6c6fbd13ecf7a0abdcc375463920bd25bf5c142cc"

for consistency

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -65,7 +65,7 @@ linters:
- noctx
- nolintlint
- nonamedreturns
- nosnakecase
# - nosnakecase

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template kept these as commented out. I think this is for convenience to be able to enable or disable them easily.

@@ -68,6 +68,25 @@ func TestNotAfterBeforeNotBeforeEndorsement(t *testing.T) {
}
}

func TestGenerateProvenanceLessEndorsement(t *testing.T) {
newNotBefore := time.Now().AddDate(0, 0, 1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally the tests (and the functions under tests) would not rely on the current time, but would accept a reference time from the outside. This will make them easier to test (without having to depend on relative times), and also avoid actual bugs (TOCTOU and related issues, if multiple functions get their own time.Now() independently at slightly different times).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the endorsement to be valid the notBefore time should be after the issued time, which uses the current time. For the test to pass, we have to use the current time here.

// An allow list of digests, represented as a mapping from cryptographic hash
// function names, to their allow listed values.
message Digests {
// Keys are cryptographic hash algorithms (e.g., sha256).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we were using the numeric multihash id somewhere else (from https://github.com/multiformats/multicodec/blob/master/table.csv ).

in particular sha256 is ambiguous; it should be sha2-256 or something like that, to be unambiguos -- but the numeric id solves that problem (without having to actual implement full multihash -- I don't think we should do that for now).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this repo. This repo follows the in-toto and SLSA style of using digests. We can change that in the future, but that is out of the scope of this PR.

Copy link
Contributor Author

@rbehjati rbehjati left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the reviews.

// VerificationOptions to verify them. If more than one provenance statements
// are provided the endorsement statement is generated only if the provenance
// statements are consistent and valid according to the input
// VerificationOptions. If no provenances are provided, a provenance-less
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is being handled by the VerificationOptions. It requires explicitly setting skip provenance verification. If that is not set, at least one provenance is required.

So I'd say those accidents are unlikely.

// endorsement is generated, if the input VerificationOptions does not contain
// a reference provenance.
func GenerateEndorsement(binaryName, binaryDigest string, verOpt *prover.VerificationOptions, validityDuration claims.ClaimValidity, provenances []ParsedProvenance) (*intoto.Statement, error) {
if (verOpt.GetSkipProvenanceVerification() == nil) && (verOpt.GetReferenceProvenance() == nil) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. I think this is a shortcoming of Go. I was able to create an instance where both options are null, so I had to add this check.

@@ -73,22 +83,35 @@ func verifyAndSummarizeProvenances(referenceValues *verification.ReferenceValues
provenancesData = append(provenancesData, p.SourceMetadata)
}

errs := multierr.Append(verifyConsistency(provenanceIRs), verifyProvenances(referenceValues, provenanceIRs))
var errs error
if len(provenanceIRs) > 0 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We expect the provenances to be consistent, but they are not verified against a set of reference values. Added tests to cover these scenarios.

@@ -69,7 +71,67 @@ func TestGenerateEndorsement_SingleValidEndorsement(t *testing.T) {
testutil.AssertEq(t, "notAfter date", predicate.Validity.NotAfter, &nextWeek)
}

func TestLoadAndVerifyProvenances_MultipleValidEndorsement(t *testing.T) {
func TestGenerateEndorsement_NoProvenance(t *testing.T) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few tests and updated the documentation on GenerateEndorsement. It generates an endorsement with the given provenances as evidence, if the provenances are consistent and match the given subject.

Let me know if you expect some other behavior.

@@ -23,3 +23,27 @@ You need to have:
- Format files: `./scripts/formatting.sh`
- Check linting: `./scripts/linting.sh`
- Additional checks: `go vet ./...`

## Using protocol buffers
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. We used to use bazel in this repo, and it caused a lot of problems. I think this is fine for now.

@@ -65,7 +65,7 @@ linters:
- noctx
- nolintlint
- nonamedreturns
- nosnakecase
# - nosnakecase
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template kept these as commented out. I think this is for convenience to be able to enable or disable them easily.

binaryHash = "d059c38cea82047ad316a1c6c6fbd13ecf7a0abdcc375463920bd25bf5c142cc"
binaryName = "oak_functions_freestanding_bin"
errorBinaryDigest = "do not contain the actual binary SHA256 digest"
binaryHash = "d059c38cea82047ad316a1c6c6fbd13ecf7a0abdcc375463920bd25bf5c142cc"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -68,6 +68,25 @@ func TestNotAfterBeforeNotBeforeEndorsement(t *testing.T) {
}
}

func TestGenerateProvenanceLessEndorsement(t *testing.T) {
newNotBefore := time.Now().AddDate(0, 0, 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the endorsement to be valid the notBefore time should be after the issued time, which uses the current time. For the test to pass, we have to use the current time here.

// An allow list of digests, represented as a mapping from cryptographic hash
// function names, to their allow listed values.
message Digests {
// Keys are cryptographic hash algorithms (e.g., sha256).
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in this repo. This repo follows the in-toto and SLSA style of using digests. We can change that in the future, but that is out of the scope of this PR.

@@ -32,6 +32,7 @@ import (
"github.com/project-oak/transparent-release/internal/verification"
"github.com/project-oak/transparent-release/pkg/claims"
"github.com/project-oak/transparent-release/pkg/intoto"
prover "github.com/project-oak/transparent-release/pkg/proto/verification"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but I cannot think of a better name. I don't like long names with underscore in them.
Do you have any suggestions?

Copy link
Contributor

@mariaschett mariaschett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, added a couple more comments, where I wasn't 100% sure.

// endorsement is generated, if the input VerificationOptions does not contain
// a reference provenance.
func GenerateEndorsement(binaryName, binaryDigest string, verOpt *prover.VerificationOptions, validityDuration claims.ClaimValidity, provenances []ParsedProvenance) (*intoto.Statement, error) {
if (verOpt.GetSkipProvenanceVerification() == nil) && (verOpt.GetReferenceProvenance() == nil) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks! I'd have expected...

@@ -69,7 +71,67 @@ func TestGenerateEndorsement_SingleValidEndorsement(t *testing.T) {
testutil.AssertEq(t, "notAfter date", predicate.Validity.NotAfter, &nextWeek)
}

func TestLoadAndVerifyProvenances_MultipleValidEndorsement(t *testing.T) {
func TestGenerateEndorsement_NoProvenance(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... I am not sure I would expect "unverified provenances" as evidence, but actually: why not?

However, is it clear afterwards if the provenances were verified? Or does that not really matter?

@@ -73,22 +83,35 @@ func verifyAndSummarizeProvenances(referenceValues *verification.ReferenceValues
provenancesData = append(provenancesData, p.SourceMetadata)
}

errs := multierr.Append(verifyConsistency(provenanceIRs), verifyProvenances(referenceValues, provenanceIRs))
var errs error
if len(provenanceIRs) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, sounds good!

Just to double check: In the GetReferenceProvenance case, verifyProvenances will get nil for GetReferenceProvenance.

Provenances: provenancesData,
}

return &verifiedProvenances, nil
}

// verifyProvenances verifies the given list of provenances. An error is returned if verification fails for one of them.
func verifyProvenances(referenceValues *verification.ReferenceValues, provenances []model.ProvenanceIR) error {
func verifyProvenances(referenceValues *prover.ProvenanceReferenceValues, provenances []model.ProvenanceIR) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment what happens if prover.ProvenanceReferenceValues == nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@rbehjati rbehjati merged commit efb5eb8 into project-oak:main Jul 17, 2023
@rbehjati rbehjati deleted the pro-ver branch July 17, 2023 18:46
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow provenance-less endorsement generation
3 participants