Skip to content

Commit

Permalink
Complete TODOs, add tests
Browse files Browse the repository at this point in the history
Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Jan 3, 2024
1 parent 1aeef18 commit 8684030
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 19 deletions.
13 changes: 12 additions & 1 deletion cmd/sigstore-go/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ var expectedOIDIssuer *string
var expectedSAN *string
var expectedSANRegex *string
var requireTSA *bool
var requireIntegratedTs *bool
var requireTimestamp *bool
var requireTlog *bool
var minBundleVersion *string
var onlineTlog *bool
Expand All @@ -57,6 +59,8 @@ func init() {
expectedSAN = flag.String("expectedSAN", "", "The expected identity in the signing certificate's SAN extension")
expectedSANRegex = flag.String("expectedSANRegex", "", "The expected identity in the signing certificate's SAN extension")
requireTSA = flag.Bool("requireTSA", false, "Require RFC 3161 signed timestamp")
requireIntegratedTs = flag.Bool("requireIntegratedTs", false, "Require log entry integrated timestamp")
requireTimestamp = flag.Bool("requireTimestamp", false, "Require either an RFC3161 signed timestamp or log entry integrated timestamp")
requireTlog = flag.Bool("requireTlog", true, "Require Artifact Transparency log entry (Rekor)")
minBundleVersion = flag.String("minBundleVersion", "", "Minimum acceptable bundle version (e.g. '0.1')")
onlineTlog = flag.Bool("onlineTlog", false, "Verify Artifact Transparency log entry online (Rekor)")
Expand Down Expand Up @@ -101,11 +105,18 @@ func run() error {

verifierConfig = append(verifierConfig, verify.WithSignedCertificateTimestamps(1))

// TODO: Add flag for requiring any timestamp
if *requireTSA {
verifierConfig = append(verifierConfig, verify.WithSignedTimestamps(1))
}

if *requireIntegratedTs {
verifierConfig = append(verifierConfig, verify.WithIntegratedTimestamps(1))
}

if *requireTimestamp {
verifierConfig = append(verifierConfig, verify.WithObserverTimestamps(1))
}

if *requireTlog {
verifierConfig = append(verifierConfig, verify.WithTransparencyLog(1))
}
Expand Down
2 changes: 1 addition & 1 deletion examples/oci-image-verification/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func run() error {

verifierConfig = append(verifierConfig, verify.WithSignedCertificateTimestamps(1))

// TODO: Add flag for requiring any timestamp
// TODO: Add flag for allowing observer timestamp once merged
if *requireTSA {
verifierConfig = append(verifierConfig, verify.WithSignedTimestamps(1))
}
Expand Down
10 changes: 6 additions & 4 deletions pkg/verify/signed_entity.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,12 +629,13 @@ func (v *SignedEntityVerifier) VerifyTransparencyLogInclusion(entity SignedEntit
return verifiedTimestamps, nil
}

// VerifyObserverTimestamps verifies TlogEntries and SignedTimestamps, if we
// expect them, and returns a slice of verified results, which embed the actual
// time.Time value. This value can then be used to verify certificates, if any.
// VerifyObserverTimestamps verifies RFC3161 signed timestamps, and verifies
// that timestamp thresholds are met with log entry integrated timestamps,
// signed timestamps, or a combination of both. The returned timestamps
// can be used to verify short-lived certificates.
// logTimestamps may be populated with verified log entry integrated timestamps
// In order to be verifiable, a SignedEntity must have at least one verified
// "observer timestamp".
// TODO: Update comment saying logTimestamps is populated when observertimestamps are used
func (v *SignedEntityVerifier) VerifyObserverTimestamps(entity SignedEntity, logTimestamps []TimestampVerificationResult) ([]TimestampVerificationResult, error) {
verifiedTimestamps := []TimestampVerificationResult{}

Expand All @@ -654,6 +655,7 @@ func (v *SignedEntityVerifier) VerifyObserverTimestamps(entity SignedEntity, log
if len(logTimestamps) < v.config.integratedTimeThreshold {
return nil, fmt.Errorf("threshold not met for verified log entry integrated timestamps: %d < %d", len(logTimestamps), v.config.integratedTimeThreshold)
}
verifiedTimestamps = append(verifiedTimestamps, logTimestamps...)
}

if v.config.requireObserverTimestamps {
Expand Down
103 changes: 95 additions & 8 deletions pkg/verify/signed_entity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ func TestSignedEntityVerifierInitialization(t *testing.T) {
assert.Error(t, err)
}

func TestSignedEntityVerifierInitRequiresTimestamp(t *testing.T) {
tr := data.PublicGoodTrustedMaterialRoot(t)

_, err := verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1))
assert.Error(t, err)
if !strings.Contains(err.Error(), "you must specify at least one of") {
t.Errorf("expected error missing timestamp verifier, got: %v", err)
}

_, err = verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1))
assert.NoError(t, err)
_, err = verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithSignedTimestamps(1))
assert.NoError(t, err)
_, err = verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1))
assert.NoError(t, err)
_, err = verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithoutAnyObserverTimestampsInsecure())
assert.NoError(t, err)
}

// Testing a bundle:
// - signed by public good
// - one tlog entry
Expand All @@ -62,10 +81,10 @@ func TestEntitySignedByPublicGoodWithTlogVerifiesSuccessfully(t *testing.T) {
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1))
assert.Nil(t, err)
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.Nil(t, err)
assert.NoError(t, err)
assert.NotNil(t, res)

assert.NotNil(t, res.Statement)
Expand All @@ -74,17 +93,24 @@ func TestEntitySignedByPublicGoodWithTlogVerifiesSuccessfully(t *testing.T) {
assert.NotNil(t, res.Signature.Certificate)
assert.Equal(t, "https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main", res.Signature.Certificate.SubjectAlternativeName.Value)
assert.NotEmpty(t, res.VerifiedTimestamps)

// verifies with integrated timestamp threshold too
v, err = verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(1))
assert.NoError(t, err)
res, err = v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.NoError(t, err)
assert.NotNil(t, res)
}

func TestEntitySignedByPublicGoodWithoutTimestampsVerifiesSuccessfully(t *testing.T) {
tr := data.PublicGoodTrustedMaterialRoot(t)
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithoutAnyObserverTimestampsInsecure())
assert.Nil(t, err)
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.Nil(t, err)
assert.NoError(t, err)
assert.NotNil(t, res)
}

Expand All @@ -93,23 +119,84 @@ func TestEntitySignedByPublicGoodWithHighTlogThresholdFails(t *testing.T) {
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(2), verify.WithObserverTimestamps(1))
assert.Nil(t, err)
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.NotNil(t, err)
assert.Error(t, err)
assert.Nil(t, res)
if !strings.Contains(err.Error(), "not enough verified log entries from transparency log") {
t.Errorf("expected error not meeting log entry threshold, got: %v", err)
}
}

func TestEntitySignedByPublicGoodWithoutVerifyingLogEntryFails(t *testing.T) {
tr := data.PublicGoodTrustedMaterialRoot(t)
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithObserverTimestamps(1))
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.Error(t, err)
assert.Nil(t, res)
if !strings.Contains(err.Error(), "threshold not met for verified signed & log entry integrated timestamps") {
t.Errorf("expected error not meeting timestamp threshold without entry verification, got: %v", err)
}

// also fails trying to use integrated timestamps without verifying the log
v, err = verify.NewSignedEntityVerifier(tr, verify.WithIntegratedTimestamps(1))
assert.NoError(t, err)
res, err = v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.Error(t, err)
assert.Nil(t, res)
if !strings.Contains(err.Error(), "threshold not met for verified log entry integrated timestamps") {
t.Errorf("expected error not meeting integrated timestamp threshold without entry verification, got: %v", err)
}
}

func TestEntitySignedByPublicGoodWithHighLogTimestampThresholdFails(t *testing.T) {
tr := data.PublicGoodTrustedMaterialRoot(t)
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithIntegratedTimestamps(2))
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.Error(t, err)
assert.Nil(t, res)
if !strings.Contains(err.Error(), "threshold not met for verified log entry integrated timestamps") {
t.Errorf("expected error not meeting log entry integrated timestamp threshold, got: %v", err)
}
}

func TestEntitySignedByPublicGoodExpectingTSAFails(t *testing.T) {
tr := data.PublicGoodTrustedMaterialRoot(t)
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithSignedTimestamps(1))
assert.Nil(t, err)
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.NotNil(t, err)
assert.Error(t, err)
assert.Nil(t, res)
if !strings.Contains(err.Error(), "threshold not met for verified signed timestamps") {
t.Errorf("expected error not meeting signed timestamp threshold, got: %v", err)
}
}

func TestEntitySignedByPublicGoodWithHighObserverTimestampThresholdFails(t *testing.T) {
tr := data.PublicGoodTrustedMaterialRoot(t)
entity := data.SigstoreJS200ProvenanceBundle(t)

v, err := verify.NewSignedEntityVerifier(tr, verify.WithTransparencyLog(1), verify.WithObserverTimestamps(2))
assert.NoError(t, err)

res, err := v.Verify(entity, SkipArtifactAndIdentitiesPolicy)
assert.Error(t, err)
assert.Nil(t, res)
if !strings.Contains(err.Error(), "threshold not met for verified signed & log entry integrated timestamps") {
t.Errorf("expected error not meeting observer timestamp threshold, got: %v", err)
}
}

// Now we test policy:
Expand Down
5 changes: 1 addition & 4 deletions pkg/verify/tlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru

logIndex := entry.LogIndex()

// TODO: Change from search by index to search by hash?
// TODO(issue#52): Change to GetLogEntryByIndex
searchParams := rekorEntries.NewSearchLogQueryParams()
searchLogQuery := rekorModels.SearchLogQuery{}
searchLogQuery.LogIndexes = []*int64{&logIndex}
Expand Down Expand Up @@ -185,9 +185,6 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
if logEntriesVerified < logThreshold {
return nil, fmt.Errorf("not enough verified log entries from transparency log: %d < %d", logEntriesVerified, logThreshold)
}
// if len(verifiedTimestamps) < tsThreshold {
// return nil, fmt.Errorf("not enough verified timestamps from transparency log entries: %d < %d", len(verifiedTimestamps), tsThreshold)
// }

return verifiedTimestamps, nil
}
Expand Down
11 changes: 10 additions & 1 deletion pkg/verify/tlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/stretchr/testify/assert"
)

// TODO(issue#53): Add unit tests for online log verification and inclusion proofs
func TestTlogVerifier(t *testing.T) {
virtualSigstore, err := ca.NewVirtualSigstore()
assert.NoError(t, err)
Expand All @@ -34,8 +35,16 @@ func TestTlogVerifier(t *testing.T) {
entity, err := virtualSigstore.Attest("foo@fighters.com", "issuer", statement)
assert.NoError(t, err)

_, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false)
var ts []time.Time
ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false)
assert.NoError(t, err)
// 1 verified timestamp
assert.Len(t, ts, 1)

ts, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, false, false)
assert.NoError(t, err)
// 0 verified timestamps, since integrated timestamps are ignored
assert.Len(t, ts, 0)

virtualSigstore2, err := ca.NewVirtualSigstore()
assert.NoError(t, err)
Expand Down
23 changes: 23 additions & 0 deletions pkg/verify/tsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,29 @@ func TestTimestampAuthorityVerifier(t *testing.T) {
assert.Error(t, err) // only 1 trusted should not meet threshold of 2
}

func TestTimestampAuthorityVerifierWithoutThreshold(t *testing.T) {
virtualSigstore, err := ca.NewVirtualSigstore()
assert.NoError(t, err)

entity, err := virtualSigstore.Attest("foo@fighters.com", "issuer", []byte("statement"))
assert.NoError(t, err)

virtualSigstore2, err := ca.NewVirtualSigstore()
assert.NoError(t, err)

var ts []time.Time

// expect one verified timestamp
ts, err = verify.VerifyTimestampAuthority(entity, virtualSigstore)
assert.NoError(t, err)
assert.Len(t, ts, 1)

// no failure, but also no verified timestamps
ts, err = verify.VerifyTimestampAuthority(entity, virtualSigstore2)
assert.NoError(t, err)
assert.Empty(t, ts)
}

type oneTrustedOneUntrustedTimestampEntity struct {
*ca.TestEntity
UntrustedTestEntity *ca.TestEntity
Expand Down

0 comments on commit 8684030

Please sign in to comment.