Skip to content

Commit

Permalink
feat: add unit test for online tlog verification (#296)
Browse files Browse the repository at this point in the history
* feat: add unit test for online tlog verification

Signed-off-by: Vishal Choudhary <[email protected]>

* feat: add unfinished test

Signed-off-by: Vishal Choudhary <[email protected]>

* chore: remove online test with network calls

Signed-off-by: Vishal Choudhary <[email protected]>

* fix: inclusion and checkpoint verification in tlog

Signed-off-by: Vishal Choudhary <[email protected]>

* fix: rekor test

Signed-off-by: Vishal Choudhary <[email protected]>

* Update pkg/verify/tlog_test.go

Co-authored-by: Colleen Murphy <[email protected]>
Signed-off-by: Vishal Choudhary <[email protected]>

* fix: seperate attest func with inclusion proof

Signed-off-by: Vishal Choudhary <[email protected]>

* fix: add changes requested

Signed-off-by: Vishal Choudhary <[email protected]>

* feat: add tests

Signed-off-by: Vishal Choudhary <[email protected]>

* fix: remove unnecessary assignment

Signed-off-by: Vishal Choudhary <[email protected]>

* feat: add test TestNoInclusionProofOnline

Signed-off-by: Vishal Choudhary <[email protected]>

* fix: cleanup

Signed-off-by: Vishal Choudhary <[email protected]>

---------

Signed-off-by: Vishal Choudhary <[email protected]>
Signed-off-by: Vishal Choudhary <[email protected]>
Co-authored-by: Colleen Murphy <[email protected]>
  • Loading branch information
vishal-chdhry and cmurphy authored Oct 17, 2024
1 parent 8a157ab commit e92142f
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 15 deletions.
2 changes: 1 addition & 1 deletion pkg/sign/transparency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (m *mockRekor) CreateLogEntry(_ *entries.CreateLogEntryParams, _ ...entries
return nil, err
}

entry, err := virtualSigstore.GenerateTlogEntry(leafCert, envelope, signature, time.Now().Unix())
entry, err := virtualSigstore.GenerateTlogEntry(leafCert, envelope, signature, time.Now().Unix(), false)
if err != nil {
return nil, err
}
Expand Down
58 changes: 49 additions & 9 deletions pkg/testing/ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ import (
"github.com/cyberphone/json-canonicalization/go/src/webpki.org/jsoncanonicalizer"
"github.com/digitorus/timestamp"
"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
"github.com/secure-systems-lab/go-securesystemslib/dsse"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/rekor/pkg/pki"
"github.com/sigstore/rekor/pkg/types"
"github.com/sigstore/rekor/pkg/types/hashedrekord"
"github.com/sigstore/rekor/pkg/types/intoto"
"github.com/sigstore/rekor/pkg/types/rekord"
"github.com/sigstore/rekor/pkg/util"
"github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/tlog"
Expand Down Expand Up @@ -121,7 +123,11 @@ func getLogID(pub crypto.PublicKey) (string, error) {
return hex.EncodeToString(digest[:]), nil
}

func (ca *VirtualSigstore) rekorSignPayload(payload tlog.RekorPayload) ([]byte, error) {
func (ca *VirtualSigstore) RekorLogID() (string, error) {
return getLogID(ca.rekorKey.Public())
}

func (ca *VirtualSigstore) RekorSignPayload(payload tlog.RekorPayload) ([]byte, error) {
jsonPayload, err := json.Marshal(payload)
if err != nil {
return nil, err
Expand Down Expand Up @@ -156,10 +162,10 @@ func (ca *VirtualSigstore) GenerateLeafCert(identity, issuer string) (*x509.Cert
func (ca *VirtualSigstore) Attest(identity, issuer string, envelopeBody []byte) (*TestEntity, error) {
// The timing here is important. We need to attest at a time when the leaf
// certificate is valid, so we match what GenerateLeafCert() does, above
return ca.AttestAtTime(identity, issuer, envelopeBody, time.Now().Add(5*time.Minute))
return ca.AttestAtTime(identity, issuer, envelopeBody, time.Now().Add(5*time.Minute), false)
}

func (ca *VirtualSigstore) AttestAtTime(identity, issuer string, envelopeBody []byte, integratedTime time.Time) (*TestEntity, error) {
func (ca *VirtualSigstore) AttestAtTime(identity, issuer string, envelopeBody []byte, integratedTime time.Time, generateInclusionProof bool) (*TestEntity, error) {
leafCert, leafPrivKey, err := ca.GenerateLeafCert(identity, issuer)
if err != nil {
return nil, err
Expand Down Expand Up @@ -193,7 +199,7 @@ func (ca *VirtualSigstore) AttestAtTime(identity, issuer string, envelopeBody []
return nil, err
}

entry, err := ca.GenerateTlogEntry(leafCert, envelope, sig, integratedTime.Unix())
entry, err := ca.GenerateTlogEntry(leafCert, envelope, sig, integratedTime.Unix(), generateInclusionProof)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -245,7 +251,7 @@ func (ca *VirtualSigstore) SignAtTime(identity, issuer string, artifact []byte,
}, nil
}

func (ca *VirtualSigstore) GenerateTlogEntry(leafCert *x509.Certificate, envelope *dsse.Envelope, sig []byte, integratedTime int64) (*tlog.Entry, error) {
func (ca *VirtualSigstore) GenerateTlogEntry(leafCert *x509.Certificate, envelope *dsse.Envelope, sig []byte, integratedTime int64, generateInclusionProof bool) (*tlog.Entry, error) {
leafCertPem, err := cryptoutils.MarshalCertificateToPEM(leafCert)
if err != nil {
return nil, err
Expand All @@ -271,10 +277,10 @@ func (ca *VirtualSigstore) GenerateTlogEntry(leafCert *x509.Certificate, envelop
return nil, err
}

logIndex := int64(1000)
logIndex := int64(0)

b := createRekorBundle(rekorLogID, integratedTime, logIndex, rekorBody)
set, err := ca.rekorSignPayload(*b)
set, err := ca.RekorSignPayload(*b)
if err != nil {
return nil, err
}
Expand All @@ -284,7 +290,35 @@ func (ca *VirtualSigstore) GenerateTlogEntry(leafCert *x509.Certificate, envelop
return nil, err
}

return tlog.NewEntry(rekorBodyRaw, integratedTime, logIndex, rekorLogIDRaw, set, nil)
var inclusionProof *models.InclusionProof
if generateInclusionProof {
inclusionProof, err = ca.GetInclusionProof(rekorBodyRaw)
if err != nil {
return nil, err
}
}
return tlog.NewEntry(rekorBodyRaw, integratedTime, logIndex, rekorLogIDRaw, set, inclusionProof)
}

func (ca *VirtualSigstore) GetInclusionProof(rekorBodyRaw []byte) (*models.InclusionProof, error) {
signer, err := signature.LoadECDSASignerVerifier(ca.rekorKey, crypto.SHA256)
if err != nil {
return nil, err
}
rootHash := sha256.Sum256(append([]byte("\000"), rekorBodyRaw...))
encodedRootHash := hex.EncodeToString(rootHash[:])
scBytes, err := util.CreateAndSignCheckpoint(context.TODO(), "rekor.localhost", int64(123), uint64(42), rootHash[:], signer)
if err != nil {
return nil, err
}

return &models.InclusionProof{
TreeSize: swag.Int64(int64(1)),
RootHash: &encodedRootHash,
LogIndex: swag.Int64(0),
Hashes: nil,
Checkpoint: swag.String(string(scBytes)),
}, nil
}

func (ca *VirtualSigstore) generateTlogEntryHashedRekord(leafCert *x509.Certificate, artifact []byte, sig []byte, integratedTime int64) (*tlog.Entry, error) {
Expand All @@ -311,7 +345,7 @@ func (ca *VirtualSigstore) generateTlogEntryHashedRekord(leafCert *x509.Certific
logIndex := int64(1000)

b := createRekorBundle(rekorLogID, integratedTime, logIndex, rekorBody)
set, err := ca.rekorSignPayload(*b)
set, err := ca.RekorSignPayload(*b)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -451,6 +485,7 @@ func (ca *VirtualSigstore) RekorLogs() map[string]*root.TransparencyLog {
ValidityPeriodEnd: time.Now().Add(time.Hour),
HashFunc: crypto.SHA256,
PublicKey: ca.rekorKey.Public(),
SignatureHashFunc: crypto.SHA256,
}
return verifiers
}
Expand Down Expand Up @@ -489,6 +524,11 @@ func (e *TestEntity) HasInclusionPromise() bool {
}

func (e *TestEntity) HasInclusionProof() bool {
for _, tlog := range e.tlogEntries {
if tlog.HasInclusionProof() {
return true
}
}
return false
}

Expand Down
9 changes: 5 additions & 4 deletions pkg/verify/tlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import (

const maxAllowedTlogEntries = 32

var RekorClientGetter = getRekorClient

// VerifyArtifactTransparencyLog verifies that the given entity has been logged
// in the transparency log and that the log entry is valid.
//
Expand Down Expand Up @@ -118,10 +120,11 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
// DO NOT use timestamp with only an inclusion proof, because it is not signed metadata
}
} else {
client, err := getRekorClient(tlogVerifier.BaseURL)
client, err := RekorClientGetter(tlogVerifier.BaseURL)
if err != nil {
return nil, err
}

verifier, err := getVerifier(tlogVerifier.PublicKey, tlogVerifier.SignatureHashFunc)
if err != nil {
return nil, err
Expand All @@ -141,9 +144,7 @@ func VerifyArtifactTransparencyLog(entity SignedEntity, trustedMaterial root.Tru
return nil, fmt.Errorf("unable to locate log entry %d", logIndex)
}

logEntry := resp.Payload

for _, v := range logEntry {
for _, v := range resp.Payload {
v := v
err = rekorVerify.VerifyLogEntry(context.TODO(), &v, *verifier)
if err != nil {
Expand Down
173 changes: 172 additions & 1 deletion pkg/verify/tlog_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ package verify_test

import (
"encoding/base64"
"errors"
"strings"
"testing"
"time"

"github.com/go-openapi/runtime"
"github.com/go-openapi/swag"
rekorGeneratedClient "github.com/sigstore/rekor/pkg/generated/client"
"github.com/sigstore/rekor/pkg/generated/client/entries"
"github.com/sigstore/rekor/pkg/generated/models"
"github.com/sigstore/sigstore-go/pkg/testing/ca"
"github.com/sigstore/sigstore-go/pkg/tlog"
"github.com/sigstore/sigstore-go/pkg/verify"
Expand Down Expand Up @@ -56,7 +62,7 @@ func TestTlogVerifier(t *testing.T) {
//
// This time was chosen assuming the Fulcio signing certificate expires
// after 5 minutes, but while the TSA intermediate is still valid (2 hours).
entity, err = virtualSigstore.AttestAtTime("[email protected]", "issuer", statement, time.Now().Add(30*time.Minute))
entity, err = virtualSigstore.AttestAtTime("[email protected]", "issuer", statement, time.Now().Add(30*time.Minute), false)
assert.NoError(t, err)

_, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false)
Expand Down Expand Up @@ -221,3 +227,168 @@ func TestMaxAllowedTlogEntries(t *testing.T) {
_, err = verify.VerifyArtifactTransparencyLog(&tooManyTlogEntriesEntity{entity}, virtualSigstore, 1, true, false)
assert.ErrorContains(t, err, "too many tlog entries") // too many tlog entries should fail to verify
}

type mockEntriesClient struct {
Entries []*models.LogEntry
}

func newMockRekorEntriesClient(virtualSigstore ca.VirtualSigstore, statement []byte, integratedTime time.Time) (*mockEntriesClient, error) {
var err error
base64Statement := base64.StdEncoding.EncodeToString(statement)
verification := &models.LogEntryAnonVerification{}
verification.InclusionProof, err = virtualSigstore.GetInclusionProof(statement)
if err != nil {
return nil, err
}
logID, err := virtualSigstore.RekorLogID()
if err != nil {
return nil, err
}
bundle := &tlog.RekorPayload{
LogID: logID,
IntegratedTime: integratedTime.Unix(),
LogIndex: 0,
Body: base64Statement,
}
verification.SignedEntryTimestamp, err = virtualSigstore.RekorSignPayload(*bundle)
if err != nil {
return nil, err
}

var logEntry models.LogEntry = make(models.LogEntry)
logEntry["foo"] = models.LogEntryAnon{
Body: base64Statement,
IntegratedTime: swag.Int64(integratedTime.Unix()),
LogIndex: swag.Int64(0),
LogID: swag.String(logID),
Verification: verification,
}
mockRekor := &mockEntriesClient{
Entries: []*models.LogEntry{&logEntry},
}
return mockRekor, nil
}

func (m *mockEntriesClient) CreateLogEntry(_ *entries.CreateLogEntryParams, _ ...entries.ClientOption) (*entries.CreateLogEntryCreated, error) {
return nil, errors.New("not implemented")
}

func (m *mockEntriesClient) GetLogEntryByIndex(params *entries.GetLogEntryByIndexParams, _ ...entries.ClientOption) (*entries.GetLogEntryByIndexOK, error) {
resp := &entries.GetLogEntryByIndexOK{}
if len(m.Entries) != 0 {
for _, e := range m.Entries {
for _, i := range *e {
if *i.LogIndex == params.LogIndex {
resp.Payload = *e
}
}
}

if resp.Payload == nil {
resp.Payload = *m.Entries[0]
}
}
return resp, nil
}

func (m *mockEntriesClient) GetLogEntryByUUID(_ *entries.GetLogEntryByUUIDParams, _ ...entries.ClientOption) (*entries.GetLogEntryByUUIDOK, error) {
return nil, errors.New("not implemented")
}

func (m *mockEntriesClient) SearchLogQuery(_ *entries.SearchLogQueryParams, _ ...entries.ClientOption) (*entries.SearchLogQueryOK, error) {
return nil, errors.New("not implemented")
}

func (m *mockEntriesClient) SetTransport(_ runtime.ClientTransport) {}

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

statement := []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}`)
integratedTime := time.Now().Add(5 * time.Minute)
entity, err := virtualSigstore.AttestAtTime("[email protected]", "issuer", statement, integratedTime, true)
assert.NoError(t, err)

entriesClient, err := newMockRekorEntriesClient(*virtualSigstore, statement, integratedTime)
assert.NoError(t, err)
mockRekor := &rekorGeneratedClient.Rekor{
Entries: entriesClient,
}

oldRekorClientGetter := verify.RekorClientGetter
verify.RekorClientGetter = func(_ string) (*rekorGeneratedClient.Rekor, error) { return mockRekor, nil }
defer func() { verify.RekorClientGetter = oldRekorClientGetter }()

_, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, true)
assert.NoError(t, err)
}

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

statement := []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}`)
integratedTime := time.Now().Add(5 * time.Minute)
entity, err := virtualSigstore.AttestAtTime("[email protected]", "issuer", statement, integratedTime, false)
assert.NoError(t, err)

entriesClient, err := newMockRekorEntriesClient(*virtualSigstore, statement, integratedTime)
assert.NoError(t, err)
mockRekor := &rekorGeneratedClient.Rekor{
Entries: entriesClient,
}

oldRekorClientGetter := verify.RekorClientGetter
verify.RekorClientGetter = func(_ string) (*rekorGeneratedClient.Rekor, error) { return mockRekor, nil }
defer func() { verify.RekorClientGetter = oldRekorClientGetter }()

_, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, true)
assert.NoError(t, err)
}

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

statement := []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}`)
integratedTime := time.Now().Add(5 * time.Minute)
entity, err := virtualSigstore.AttestAtTime("[email protected]", "issuer", statement, integratedTime, true)
assert.NoError(t, err)

logID, err := virtualSigstore.RekorLogID()
assert.NoError(t, err)

var logEntry models.LogEntry = make(models.LogEntry)
logEntry["foo"] = models.LogEntryAnon{
Body: base64.StdEncoding.EncodeToString(statement),
IntegratedTime: swag.Int64(integratedTime.Unix()),
LogIndex: swag.Int64(0),
LogID: swag.String(logID),
}
mockRekor := &rekorGeneratedClient.Rekor{
Entries: &mockEntriesClient{
Entries: []*models.LogEntry{&logEntry},
},
}

oldRekorClientGetter := verify.RekorClientGetter
verify.RekorClientGetter = func(_ string) (*rekorGeneratedClient.Rekor, error) { return mockRekor, nil }
defer func() { verify.RekorClientGetter = oldRekorClientGetter }()

_, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, true)
assert.ErrorContains(t, err, "inclusion proof not provided")
}

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

statement := []byte(`{"_type":"https://in-toto.io/Statement/v0.1","predicateType":"customFoo","subject":[{"name":"subject","digest":{"sha256":"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"}}],"predicate":{}}`)
integratedTime := time.Now().Add(5 * time.Minute)
entity, err := virtualSigstore.AttestAtTime("[email protected]", "issuer", statement, integratedTime, true)
assert.NoError(t, err)

_, err = verify.VerifyArtifactTransparencyLog(entity, virtualSigstore, 1, true, false)
assert.NoError(t, err)
}

0 comments on commit e92142f

Please sign in to comment.