Skip to content

Commit

Permalink
Don't fail on Rekor entry verification for untrusted entries
Browse files Browse the repository at this point in the history
Like sigstore#45, as long as the threshold of expected timestamps is met, then
verification should succeed. Otherwise, entries without trust root
material should be skipped.

One benefit of having the log key ID be used to look up the correct
trust root material is that we can still error out if the signature is invalid.

Ref: sigstore#43

Signed-off-by: Hayden Blauzvern <hblauzvern@google.com>
  • Loading branch information
haydentherapper committed Dec 15, 2023
1 parent 0946840 commit 82b05ba
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 20 deletions.
4 changes: 3 additions & 1 deletion pkg/verify/sct.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
// leaf certificate, will extract SCTs from the leaf certificate and verify the
// timestamps using the TrustedMaterial's FulcioCertificateAuthorities() and
// CTlogAuthorities()
// TODO(issue#46): Add unit tests
func VerifySignedCertificateTimestamp(leafCert *x509.Certificate, threshold int, trustedMaterial root.TrustedMaterial) error { // nolint: revive
ctlogs := trustedMaterial.CTlogAuthorities()
fulcioCerts := trustedMaterial.FulcioCertificateAuthorities()
Expand All @@ -48,7 +49,8 @@ func VerifySignedCertificateTimestamp(leafCert *x509.Certificate, threshold int,
encodedKeyID := hex.EncodeToString(sct.LogID.KeyID[:])
key, ok := ctlogs[encodedKeyID]
if !ok {
return fmt.Errorf("unable to find ctlogs key for %s", encodedKeyID)
// skip entries the trust root cannot verify
continue
}

for _, fulcioCa := range fulcioCerts {
Expand Down
44 changes: 25 additions & 19 deletions pkg/verify/tlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
}
}
}
if len(entries) < threshold {
return nil, fmt.Errorf("not enough transparency log entries: %d < %d", len(entries), threshold)
}

sigContent, err := entity.SignatureContent()
if err != nil {
Expand All @@ -78,14 +75,14 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
if err != nil {
return nil, err
}
inclusionVerified := false

if !online {
var inclusionVerified bool
// TODO: do we validate that an entry has EITHER a promise OR a proof?
if entry.HasInclusionPromise() {
err = tlog.VerifySET(entry, trustedMaterial.TlogAuthorities())
if err != nil {
return nil, err
continue
}
inclusionVerified = true
}
Expand All @@ -94,7 +91,8 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
hex64Key := hex.EncodeToString([]byte(keyID))
tlogVerifier, ok := trustedMaterial.TlogAuthorities()[hex64Key]
if !ok {
return nil, fmt.Errorf("unable to find tlog information for key %s", hex64Key)
// skip entries the trust root cannot verify
continue
}

verifier, err := getVerifier(tlogVerifier.PublicKey, tlogVerifier.SignatureHashFunc)
Expand All @@ -118,7 +116,8 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
hex64Key := hex.EncodeToString([]byte(keyID))
tlogVerifier, ok := trustedMaterial.TlogAuthorities()[hex64Key]
if !ok {
return nil, fmt.Errorf("unable to find tlog information for key %s", hex64Key)
// skip entries the trust root cannot verify
continue
}

client, err := getRekorClient(tlogVerifier.BaseURL)
Expand Down Expand Up @@ -157,27 +156,34 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
return nil, err
}
}
inclusionVerified = true
verifiedTimestamps = append(verifiedTimestamps, entry.IntegratedTime())
}

// Ensure entry signature matches signature from bundle
if !bytes.Equal(entry.Signature(), entitySignature) {
return nil, errors.New("transparency log signature does not match")
}
if inclusionVerified {
// Ensure entry signature matches signature from bundle
if !bytes.Equal(entry.Signature(), entitySignature) {
return nil, errors.New("transparency log signature does not match")
}

// Ensure entry certificate matches bundle certificate
if !verificationContent.CompareKey(entry.PublicKey(), trustedMaterial) {
return nil, errors.New("transparency log certificate does not match")
}
// Ensure entry certificate matches bundle certificate
if !verificationContent.CompareKey(entry.PublicKey(), trustedMaterial) {
return nil, errors.New("transparency log certificate does not match")
}

// TODO: if you have access to artifact, check that it matches body subject
// TODO: if you have access to artifact, check that it matches body subject

// Check tlog entry time against bundle certificates
if !verificationContent.ValidAtTime(entry.IntegratedTime(), trustedMaterial) {
return nil, errors.New("integrated time outside certificate validity")
// Check tlog entry time against bundle certificates
if !verificationContent.ValidAtTime(entry.IntegratedTime(), trustedMaterial) {
return nil, errors.New("integrated time outside certificate validity")
}
}
}

if len(verifiedTimestamps) < threshold {
return nil, fmt.Errorf("not enough verified timestamps from transparency log entries: %d < %d", len(verifiedTimestamps), threshold)
}

return verifiedTimestamps, nil
}

Expand Down
41 changes: 41 additions & 0 deletions pkg/verify/tlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,47 @@ func TestTlogVerifier(t *testing.T) {
assert.Error(t, err)
}

type goodAndUntrustedLogEntry struct {
*ca.TestEntity
OtherTestEntity *ca.TestEntity
}

func (e *goodAndUntrustedLogEntry) TlogEntries() ([]*tlog.Entry, error) {
entries, err := e.TestEntity.TlogEntries()
if err != nil {
return nil, err
}

otherEntries, err := e.OtherTestEntity.TlogEntries()
if err != nil {
return nil, err
}

return append(entries, otherEntries...), nil
}

func TestIgnoredTLogEntries(t *testing.T) {
statement := []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}`)

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

otherSigstore, err := ca.NewVirtualSigstore()
assert.NoError(t, err)
otherEntity, err := otherSigstore.Attest("foo@fighters.com", "issuer", statement)
assert.NoError(t, err)

// success: entry that cannot be verified is ignored
_, err = verify.VerifyArtifactTransparencyLog(&goodAndUntrustedLogEntry{entity, otherEntity}, virtualSigstore, 1, false)
assert.NoError(t, err)

// failure: threshold of 2 is not met since 1 untrusted entry is ignored
_, err = verify.VerifyArtifactTransparencyLog(&goodAndUntrustedLogEntry{entity, otherEntity}, virtualSigstore, 2, false)
assert.Error(t, err)
}

type dupTlogEntity struct {
*ca.TestEntity
}
Expand Down

0 comments on commit 82b05ba

Please sign in to comment.