diff --git a/experimental/auth-logic/wrappers/provenance_build_wrapper.go b/experimental/auth-logic/wrappers/provenance_build_wrapper.go index 5cd42c58..753bad17 100644 --- a/experimental/auth-logic/wrappers/provenance_build_wrapper.go +++ b/experimental/auth-logic/wrappers/provenance_build_wrapper.go @@ -40,11 +40,13 @@ type simplifiedProvenance struct { // by emitting the authorization logic statement. func (pbw ProvenanceBuildWrapper) EmitStatement() (UnattributedStatement, error) { // Unmarshal a provenance struct from the JSON file. - provenance, err := amber.ParseProvenanceFile(pbw.ProvenanceFilePath) + validatedProvenance, err := amber.ParseProvenanceFile(pbw.ProvenanceFilePath) if err != nil { return UnattributedStatement{}, fmt.Errorf("provenance build wrapper couldn't parse provenance file: %v", err) } + provenance := validatedProvenance.GetProvenance() + // TODO(#69): Set the verifier as a field in pbw, and use that here. verifier := verify.AmberProvenanceMetadataVerifier{} if err := verifier.Verify(pbw.ProvenanceFilePath); err != nil { diff --git a/experimental/auth-logic/wrappers/provenance_wrapper.go b/experimental/auth-logic/wrappers/provenance_wrapper.go index 10576174..d3fc1574 100644 --- a/experimental/auth-logic/wrappers/provenance_wrapper.go +++ b/experimental/auth-logic/wrappers/provenance_wrapper.go @@ -32,15 +32,12 @@ type ProvenanceWrapper struct{ FilePath string } // EmitStatement implements the wrapper interface for ProvenanceWrapper // by emitting the authorization logic statement. func (p ProvenanceWrapper) EmitStatement() (UnattributedStatement, error) { - provenance, err := amber.ParseProvenanceFile(p.FilePath) + validatedProvenance, err := amber.ParseProvenanceFile(p.FilePath) if err != nil { return UnattributedStatement{}, fmt.Errorf("provenance wrapper couldn't prase provenance file: %v", err) } - if len(provenance.Subject) != 1 { - return UnattributedStatement{}, fmt.Errorf("provenance file missing subject") - } - + provenance := validatedProvenance.GetProvenance() sanitizedAppName := SanitizeName(provenance.Subject[0].Name) expectedHash, hashOk := provenance.Subject[0].Digest["sha256"] @@ -64,14 +61,10 @@ func (p ProvenanceWrapper) EmitStatement() (UnattributedStatement, error) { // application it is about. This is useful for generating principal names, // for example. func GetAppNameFromProvenance(provenanceFilePath string) (string, error) { - provenance, provenanceErr := amber.ParseProvenanceFile(provenanceFilePath) - if provenanceErr != nil { - return "", provenanceErr - } - - if len(provenance.Subject) != 1 { - return "", fmt.Errorf("provenance file missing subject") + validatedProvenance, err := amber.ParseProvenanceFile(provenanceFilePath) + if err != nil { + return "", fmt.Errorf("provenance wrapper couldn't prase provenance file: %v", err) } - return provenance.Subject[0].Name, nil + return validatedProvenance.GetProvenance().Subject[0].Name, nil } diff --git a/internal/common/common.go b/internal/common/common.go index ad78e80e..6e3af083 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -90,11 +90,8 @@ func LoadBuildConfigFromFile(path string) (*BuildConfig, error) { } // LoadBuildConfigFromProvenance loads build configuration from a SLSA Provenance object. -func LoadBuildConfigFromProvenance(statement *intoto.Statement) (*BuildConfig, error) { - if len(statement.Subject) != 1 { - return nil, fmt.Errorf("the provenance statement must have exactly one Subject, got %d", len(statement.Subject)) - } - +func LoadBuildConfigFromProvenance(provenance *amber.ValidatedProvenance) (*BuildConfig, error) { + statement := provenance.GetProvenance() predicate := statement.Predicate.(slsa.ProvenancePredicate) if len(predicate.Materials) != 2 { return nil, fmt.Errorf("the provenance must have exactly two Materials, got %d", len(predicate.Materials)) @@ -328,7 +325,7 @@ func (b *BuildConfig) VerifyBinarySha256Hash(expectedBinarySha256Hash string) er } if binarySha256Hash != expectedBinarySha256Hash { - return fmt.Errorf("the hash of the generated binary does not match the expected SHA256 hash; got %s, want %v", + return fmt.Errorf("the hash of the generated binary does not match the expected SHA256 hash; got %s, want %s", binarySha256Hash, expectedBinarySha256Hash) } diff --git a/internal/verifier/verifier.go b/internal/verifier/verifier.go index 2f5f7333..0beb190d 100644 --- a/internal/verifier/verifier.go +++ b/internal/verifier/verifier.go @@ -68,7 +68,7 @@ func (verifier *ReproducibleProvenanceVerifier) Verify(provenanceFilePath string } // The provenance is valid, therefore `expectedBinaryHash` is guaranteed to be non-empty. - expectedBinaryHash := provenance.Subject[0].Digest["sha256"] + expectedBinaryHash := provenance.GetProvenance().Subject[0].Digest["sha256"] if err := buildConfig.VerifyBinarySha256Hash(expectedBinaryHash); err != nil { return fmt.Errorf("failed to verify the hash of the built binary: %v", err) @@ -88,13 +88,14 @@ type AmberProvenanceMetadataVerifier struct { // AmberProvenanceMetadataVerifier instance. Returns an error if any of the // values is not as expected. Otherwise returns nil, indicating success. // TODO(#69): Check metadata against the expected values. +// TODO(#126): Refactor and separate verification logic from the logic for reading the file. func (verifier *AmberProvenanceMetadataVerifier) Verify(provenanceFilePath string) error { provenance, err := amber.ParseProvenanceFile(provenanceFilePath) if err != nil { return fmt.Errorf("couldn't load the provenance file from %s: %v", provenanceFilePath, err) } - predicate := provenance.Predicate.(slsa.ProvenancePredicate) + predicate := provenance.GetProvenance().Predicate.(slsa.ProvenancePredicate) if predicate.BuildType != amber.AmberBuildTypeV1 { return fmt.Errorf("incorrect BuildType: got %s, want %v", predicate.BuildType, amber.AmberBuildTypeV1) diff --git a/internal/verifier/verifier_test.go b/internal/verifier/verifier_test.go index 586a123e..7bd666c3 100644 --- a/internal/verifier/verifier_test.go +++ b/internal/verifier/verifier_test.go @@ -42,6 +42,7 @@ func TestReproducibleProvenanceVerifier_validProvenance(t *testing.T) { } } +// TODO(#126): Update the test once Verify is refactored. func TestReproducibleProvenanceVerifier_invalidHash(t *testing.T) { // The path to provenance is specified relative to the root of the repo, so we need to go one level up. // Get the current directory before that to restore the path at the end of the test. @@ -60,6 +61,7 @@ func TestReproducibleProvenanceVerifier_invalidHash(t *testing.T) { } } +// TODO(#126): Update the test once Verify is refactored. func TestReproducibleProvenanceVerifier_badCommand(t *testing.T) { // The path to provenance is specified relative to the root of the repo, so we need to go one level up. // Get the current directory before that to restore the path at the end of the test. diff --git a/pkg/amber/provenance.go b/pkg/amber/provenance.go index c82a762b..4fb07ade 100644 --- a/pkg/amber/provenance.go +++ b/pkg/amber/provenance.go @@ -45,6 +45,33 @@ type BuildConfig struct { OutputPath string `json:"outputPath"` } +// ValidatedProvenance wraps an intoto.Statement representing a valid SLSA provenance statement. +// A provenance statement is valid if it contains a single subject, with a SHA256 hash. +type ValidatedProvenance struct { + // The field is private so that invalid instances cannot be created. + provenance intoto.Statement +} + +// GetProvenance returns a partial copy of the provenance statement wrapped in this instance. +// The partial copy guarantees that the validity condition will not be violated. +func (p *ValidatedProvenance) GetProvenance() intoto.Statement { + subject := intoto.Subject{ + Name: p.provenance.Subject[0].Name, + Digest: slsa.DigestSet{"sha256": p.provenance.Subject[0].Digest["sha256"]}, + } + + statementHeader := intoto.StatementHeader{ + Type: p.provenance.Type, + PredicateType: p.provenance.PredicateType, + Subject: []intoto.Subject{subject}, + } + + return intoto.Statement{ + StatementHeader: statementHeader, + Predicate: p.provenance.Predicate, + } +} + func validateSLSAProvenanceJSON(provenanceFile []byte) error { schemaFile, err := os.ReadFile(SchemaPath) if err != nil { @@ -74,7 +101,7 @@ func validateSLSAProvenanceJSON(provenanceFile []byte) error { // ParseProvenanceFile reads a JSON file from a given path, validates it against the Amber // buildType schema, and parses it into an instance of intoto.Statement. -func ParseProvenanceFile(path string) (*intoto.Statement, error) { +func ParseProvenanceFile(path string) (*ValidatedProvenance, error) { statementBytes, readErr := os.ReadFile(path) if readErr != nil { return nil, fmt.Errorf("could not read the provenance file: %v", readErr) @@ -89,8 +116,8 @@ func ParseProvenanceFile(path string) (*intoto.Statement, error) { return nil, fmt.Errorf("could not unmarshal the provenance file:\n%v", err) } - if len(statement.Subject) == 0 || statement.Subject[0].Digest["sha256"] == "" { - return nil, fmt.Errorf("at least one subject must be present, and it must have a sha256 digest") + if len(statement.Subject) != 1 || statement.Subject[0].Digest["sha256"] == "" { + return nil, fmt.Errorf("the provenance must have exactly one subject with a sha256 digest") } // statement.Predicate is now just a map, we have to parse it into an instance of slsa.ProvenancePredicate @@ -119,5 +146,5 @@ func ParseProvenanceFile(path string) (*intoto.Statement, error) { predicate.BuildConfig = buildConfig statement.Predicate = predicate - return &statement, nil + return &ValidatedProvenance{provenance: statement}, nil } diff --git a/pkg/amber/provenance_test.go b/pkg/amber/provenance_test.go index 4d4cb7ad..cf34cae0 100644 --- a/pkg/amber/provenance_test.go +++ b/pkg/amber/provenance_test.go @@ -40,10 +40,11 @@ func TestExampleProvenance(t *testing.T) { testutil.Chdir(t, "../../") // Parses the provenance and validates it against the schema. - provenance, err := ParseProvenanceFile(provenanceExamplePath) + validatedProvenance, err := ParseProvenanceFile(provenanceExamplePath) if err != nil { t.Fatalf("Failed to parse example provenance: %v", err) } + provenance := validatedProvenance.GetProvenance() predicate := provenance.Predicate.(slsa.ProvenancePredicate) buildConfig := predicate.BuildConfig.(BuildConfig)