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

Commit

Permalink
Reworked provenance verification. (#246)
Browse files Browse the repository at this point in the history
Reworked provenance verification following an all-new verification options proto definition.
  • Loading branch information
thmsbinder committed Sep 22, 2023
1 parent 77ac93e commit dd29c62
Show file tree
Hide file tree
Showing 18 changed files with 1,944 additions and 1,282 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ jobs:
--binary_path=testdata/binary \
--binary_name=stage0_bin \
--provenance_uris=https://ent-server-62sa4xcfia-ew.a.run.app/raw/sha2-256:94f2b47418b42dde64f678a9d348dde887bfe4deafc8b43f611240fee6cc750a \
--verification_options=testdata/skip_verification.textproto
--verification_options="provenance_count_at_least { count: 1 }" \
--output_path=endorsement.json
# TODO(#113): Find a better, and easier-to-navigate way to report coverage.
# Generate coverage report for ./pkg/... and ./internal/... and upload the report as an html file.
Expand Down
51 changes: 26 additions & 25 deletions cmd/endorser/README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,41 @@
# Generating Endorsements

This package provides a command line tool for generating endorsement statements for binaries.
The *endorser* is a command line tool for verifying provenances, and, after successful verification, generating an endorsement statement for the binary in question.

The tool takes as input the name and digest of the binary, and optionally a list of provenance URIs.
In addition, a textproto file must be provided for specifying options for verifying the given
provenances prior to endorsement generation. The resulting endorsement statement is stored in a path
that can be customized via a dedicated input argument.
Inputs:
* `--provenance_uris`: Zero or more provenances, as a comma-separated list of URIs. The tool retrieves the URIs and evaluates them
* `--verification_options`: Custom verification to run on the provenances, as a prerequisite to the endorsement generation. Optional - if not specified then no verifications are carried out. See the underlying [protocol buffer definition](../../proto/verification_options.proto)
* `--skip_verification`: If there is no intention to verify anything, must confirm by setting this flag
* `--binary_name`: The name of the binary
* `--binary_path`: Path to the binary file. Needed only to compute digests

If no provenance URIs are provided, the tool generates a provenance-less endorsement statement if
the given verification options allows that. For more information about verification options, see the
[protobuf specification](../../proto/provenance_verification.proto).
Outputs:
* `--output_path`: Where the endorsement (a JSON file) goes. Common example: `--output_path=endorsement.json`

If a non-empty list of provenance URIs is provided, the tool downloads them, verifies them according
to the options in the provided verification options file, and if the verification is successful
generates an endorsement statement, with the given provenances listed in the endorsement statement
as evidence (in its evidence field).

Example execution without provenances:
Here is a simple example which neither involves provenances nor verification:

```bash
go run cmd/endorser/main.go \
--binary_path=testdata/binary \
--binary_name=stage0_bin \
--verification_options=testdata/skip_verification.textproto
--binary_path=testdata/binary \
--binary_name=stage0_bin \
--skip_verification \
--output_path=/tmp/endorsement.json
```

Example execution with a provenance URI from ent (for simplicity we pass in
`testdata/skip_verification.textproto` for verification):
A more involved example with a single provenance and some verification:

```bash
go run cmd/endorser/main.go \
--binary_path=testdata/binary \
--binary_name=stage0_bin \
--provenance_uris=https://ent-server-62sa4xcfia-ew.a.run.app/raw/sha2-256:94f2b47418b42dde64f678a9d348dde887bfe4deafc8b43f611240fee6cc750a \
--verification_options=testdata/skip_verification.textproto
--binary_path=testdata/binary \
--binary_name=stage0_bin \
--provenance_uris=https://ent-server-62sa4xcfia-ew.a.run.app/raw/sha2-256:94f2b47418b42dde64f678a9d348dde887bfe4deafc8b43f611240fee6cc750a \
--verification_options="provenance_count_at_least { count: 1 } all_with_build_command {} all_with_binary_digests { digests { hexdecimal { key: 18 value: '70a4fae8cd01e8e509f0d29efe9cf810192ad9b606fcf66fb6c4cbfee40fd951'}}}" \
--output_path=/tmp/endorsement.json
```

See [this comment](https://github.com/project-oak/oak/pull/4191#issuecomment-1643932356) as the
source of the binary and provenance info.
If the verification options should be kept in a file (for length reasons), then use
```bash
...
--verification_options="$(</tmp/ver_opts.textproto)"
...
```
89 changes: 46 additions & 43 deletions cmd/endorser/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package main provides a command-line tool for generating an endorsement statement for a binary.
package main

import (
Expand All @@ -27,78 +26,84 @@ import (
"time"

"github.com/project-oak/transparent-release/internal/endorser"
"github.com/project-oak/transparent-release/internal/verifier"
"github.com/project-oak/transparent-release/pkg/claims"
"github.com/project-oak/transparent-release/pkg/intoto"
)

// ISO 8601 layout for representing input dates.
const layout = "2006-01-02"
const dateLayout = "2006-01-02"

type provenanceURIsFlag []string

func (f *provenanceURIsFlag) String() string {
return "URI for downloading a provenance"
return "Provenance URI"
}

func (f *provenanceURIsFlag) Set(value string) error {
*f = append(*f, value)
return nil
}

type inputOptions struct {
binaryPath string
binaryName string
verificationOptions string
endorsementPath string
notBefore string
notAfter string
provenanceURIs provenanceURIsFlag
}
//nolint:gochecknoglobals
var provenanceURIs provenanceURIsFlag

func (i *inputOptions) init() {
flag.StringVar(&i.binaryPath, "binary_path", "",
"Location of the binary in the local file system. This is required for computing various digests.")
flag.StringVar(&i.binaryName, "binary_name", "",
"Name of the binary to endorse. Should match the name in provenances, if provenance URIs are provided.")
flag.StringVar(&i.verificationOptions, "verification_options", "",
"Path to a textproto file containing verification options.")
flag.StringVar(&i.endorsementPath, "endorsement_path", "endorsement.json",
"Output path to store the generated endorsement statement.")
flag.StringVar(&i.notBefore, "not_before", "",
//nolint:cyclop
func main() {
binaryName := flag.String("binary_name", "",
"Name of the binary to endorse. Must match the binary names in all provenances.")
binaryPath := flag.String("binary_path", "",
"Location of the binary in the local file system. Required only for computing digests.")
flag.Var(&provenanceURIs, "provenance_uris",
"Comma-separated URIs of zero or more provenances.")
verOptsTextproto := flag.String("verification_options", "",
"An instance of VerificationOptions as inline textproto.")
skipVerification := flag.Bool("skip_verification", false,
"Confirms that empty --verification_options is intended.")
notBefore := flag.String("not_before", "",
"The date from which the endorsement is effective, formatted as YYYY-MM-DD. Defaults to 1 day after the issuance date.")
flag.StringVar(&i.notAfter, "not_after", "",
notAfter := flag.String("not_after", "",
"The expiry date of the endorsement, formatted as YYYY-MM-DD. Defaults to 90 day after the issuance date.")
flag.Var(&i.provenanceURIs, "provenance_uris", "URIs of the provenances.")
outputPath := flag.String("output_path", "",
"Full path to store the generated endorsement statement as JSON.")
flag.Parse()
}

func main() {
opt := inputOptions{}
opt.init()

digests, err := computeBinaryDigests(opt.binaryPath)
// Make sure required flags are set.
if len(*binaryName) == 0 {
log.Fatalf("--binary_name not set")
}
if len(*binaryPath) == 0 {
log.Fatalf("--binary_path not set")
}
if len(*outputPath) == 0 {
log.Fatalf("--output_path not set")
}
if *verOptsTextproto == "" && !*skipVerification {
log.Fatalf("--verification_options empty, use --skip_verification to overrule")
}
verOpts, err := verifier.ParseVerificationOptions(*verOptsTextproto)
if err != nil {
log.Fatalf("Failed parsing binaryDigest: %v", err)
log.Fatalf("Couldn't map parse verification options: %v", err)
}

validity, err := getClaimValidity(opt.notBefore, opt.notAfter)
digests, err := computeBinaryDigests(*binaryPath)
if err != nil {
log.Fatalf("Failed creating claimValidity: %v", err)
log.Fatalf("Failed parsing binaryDigest: %v", err)
}

verOpts, err := endorser.LoadTextprotoVerificationOptions(opt.verificationOptions)
validity, err := getClaimValidity(*notBefore, *notAfter)
if err != nil {
log.Fatalf("Failed loading the verification options from %s: %v", opt.verificationOptions, err)
log.Fatalf("Failed creating claimValidity: %v", err)
}

provenances, err := endorser.LoadProvenances(opt.provenanceURIs)
provenances, err := endorser.LoadProvenances(provenanceURIs)
if err != nil {
log.Fatalf("Failed loading provenances: %v", err)
}

endorsement, err := endorser.GenerateEndorsement(opt.binaryName, *digests, verOpts, *validity, provenances)
endorsement, err := endorser.GenerateEndorsement(*binaryName, *digests, verOpts, *validity, provenances)
if err != nil {
log.Fatalf("Failed generating endorsement statement %v", err)
log.Fatalf("Failed to generate endorsement: %v", err)
}

bytes, err := json.MarshalIndent(endorsement, "", " ")
Expand All @@ -109,14 +114,12 @@ func main() {
// Add a newline at the end of the file.
newline := byte('\n')
bytes = append(bytes, newline)
if err := os.WriteFile(opt.endorsementPath, bytes, 0600); err != nil {
if err := os.WriteFile(*outputPath, bytes, 0600); err != nil {
log.Fatalf("Failed writing the endorsement statement to file: %v", err)
}

log.Printf("The endorsement statement is successfully stored in %s", opt.endorsementPath)
}

func getClaimValidity(notBefore, notAfter string) (*claims.ClaimValidity, error) {
func getClaimValidity(notBefore string, notAfter string) (*claims.ClaimValidity, error) {
// We only care about the date, but we want to store it as an
// RFC3339-encoded timestamp. So we need a Time object, but with only the
// date part.
Expand All @@ -142,7 +145,7 @@ func parseDateOrDefault(date string, value time.Time) (time.Time, error) {
if date == "" {
return value, nil
}
return time.Parse(layout, date)
return time.Parse(dateLayout, date)
}

func computeBinaryDigests(path string) (*intoto.DigestSet, error) {
Expand Down
14 changes: 11 additions & 3 deletions cmd/verifier/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ that it contains exactly one subject, containing a SHA256 digest and a binary na

To verify a SLSA v0.2 provenance, run:

```console
$ go run cmd/verifier/main.go -provenance_path testdata/slsa_v02_provenance.json
2023/04/21 14:33:47 Verification was successful.
```bash
go run cmd/verifier/main.go --provenance_path=testdata/slsa_v02_provenance.json
```

In case you want to add custom verifications on the provenances, just add verification
options as inline textproto.

```bash
go run cmd/verifier/main.go \
--provenance_path=testdata/slsa_v02_provenance.json \
--verification_options="all_with_binary_name { binary_name: 'oak_functions_freestanding_bin'}"
```
20 changes: 9 additions & 11 deletions cmd/verifier/main.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2022 The Project Oak Authors
// Copyright 2022-2023 The Project Oak Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Package main contains a command-line tool for verifying SLSA provenances.
package main

import (
Expand All @@ -22,12 +21,12 @@ import (

"github.com/project-oak/transparent-release/internal/model"
"github.com/project-oak/transparent-release/internal/verifier"
pb "github.com/project-oak/transparent-release/pkg/proto/verification"
)

func main() {
provenancePath := flag.String("provenance_path", "",
"Required - Path to a SLSA provenance file.")
provenancePath := flag.String("provenance_path", "", "Path to a single SLSA provenance file.")
verOptsTextproto := flag.String("verification_options", "",
"An instance of VerificationOptions as inline textproto.")
flag.Parse()

provenanceBytes, err := os.ReadFile(*provenancePath)
Expand All @@ -44,13 +43,12 @@ func main() {
if err != nil {
log.Fatalf("couldn't map from %s to internal representation: %v", validatedProvenance, err)
}

provenanceVerifier := verifier.ProvenanceIRVerifier{
Got: provenanceIR,
Want: &pb.ProvenanceReferenceValues{},
verOpts, err := verifier.ParseVerificationOptions(*verOptsTextproto)
if err != nil {
log.Fatalf("couldn't map parse verification options: %v", err)
}

if err := provenanceVerifier.Verify(); err != nil {
// We only process a single provenance, even though the verifier works on many.
if err := verifier.Verify([]model.ProvenanceIR{*provenanceIR}, verOpts); err != nil {
log.Fatalf("error when verifying the provenance: %v", err)
}

Expand Down
Loading

0 comments on commit dd29c62

Please sign in to comment.