diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 6d2750a6e8cc..0fd86eaeb2b6 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -410,7 +410,29 @@ func tlogFindCertificate(ctx context.Context, rekorClient *client.Rekor, func tlogFindEntry(ctx context.Context, client *client.Rekor, blobBytes []byte, sig string, pem []byte) (*models.LogEntryAnon, error) { b64sig := base64.StdEncoding.EncodeToString([]byte(sig)) - return cosign.FindTlogEntry(ctx, client, b64sig, blobBytes, pem) + tlogEntries, err := cosign.FindTlogEntry(ctx, client, b64sig, blobBytes, pem) + if err != nil { + return nil, err + } + if len(tlogEntries) == 0 { + return nil, fmt.Errorf("no valid tlog entries found with proposed entry") + } + // Always return the earliest integrated entry. That + // always suffices for verification of signature time. + var earliestLogEntry *models.LogEntryAnon + var earliestLogEntryTime *time.Time + // We'll always return a tlog entry because there's at least one entry in the log. + for _, entry := range tlogEntries { + entryTime := time.Unix(*entry.IntegratedTime, 0) + if earliestLogEntryTime == nil || entryTime.Before(*earliestLogEntryTime) { + earliestLogEntryTime = &entryTime + earliestLogEntry = &entry + } + } + if earliestLogEntry == nil { + return nil, fmt.Errorf("no valid tlog entries found with proposed entry") + } + return earliestLogEntry, nil } // signatures returns the raw signature diff --git a/pkg/cosign/tlog.go b/pkg/cosign/tlog.go index 817be14a827b..94929d77d32b 100644 --- a/pkg/cosign/tlog.go +++ b/pkg/cosign/tlog.go @@ -389,7 +389,8 @@ func proposedEntry(b64Sig string, payload, pubKey []byte) ([]models.ProposedEntr return proposedEntry, nil } -func FindTlogEntry(ctx context.Context, rekorClient *client.Rekor, b64Sig string, payload, pubKey []byte) (entry *models.LogEntryAnon, err error) { +func FindTlogEntry(ctx context.Context, rekorClient *client.Rekor, + b64Sig string, payload, pubKey []byte) ([]models.LogEntryAnon, error) { searchParams := entries.NewSearchLogQueryParamsWithContext(ctx) searchLogQuery := models.SearchLogQuery{} proposedEntry, err := proposedEntry(b64Sig, payload, pubKey) @@ -406,23 +407,21 @@ func FindTlogEntry(ctx context.Context, rekorClient *client.Rekor, b64Sig string } if len(resp.Payload) == 0 { return nil, errors.New("signature not found in transparency log") - } else if len(resp.Payload) > 1 { - return nil, errors.New("multiple entries returned; this should not happen") - } - logEntry := resp.Payload[0] - if len(logEntry) != 1 { - return nil, errors.New("UUID value can not be extracted") } - var tlogEntry models.LogEntryAnon - for k, e := range logEntry { - // Check body hash matches uuid - if err := verifyUUID(k, e); err != nil { - return nil, err + // This may accumulate multiple entries on multiple tree IDs. + results := make([]models.LogEntryAnon, 0) + for _, logEntry := range resp.GetPayload() { + for k, e := range logEntry { + // Check body hash matches uuid + if err := verifyUUID(k, e); err != nil { + continue + } + results = append(results, e) } - tlogEntry = e } - return &tlogEntry, nil + + return results, nil } func FindTLogEntriesByPayload(ctx context.Context, rekorClient *client.Rekor, payload []byte) (uuids []string, err error) { diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 52b0a9882764..a83fe1de4ec9 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -410,11 +410,33 @@ func tlogValidateEntry(ctx context.Context, client *client.Rekor, sig oci.Signat if err != nil { return nil, err } - e, err := FindTlogEntry(ctx, client, b64sig, payload, pem) + tlogEntries, err := FindTlogEntry(ctx, client, b64sig, payload, pem) if err != nil { return nil, err } - return e, VerifyTLogEntry(ctx, client, e) + if len(tlogEntries) == 0 { + return nil, fmt.Errorf("no valid tlog entries found with proposed entry") + } + // Always return the earliest integrated entry. That + // always suffices for verification of signature time. + var earliestLogEntry *models.LogEntryAnon + earliestLogEntryTime := time.Now() + entryVerificationErrs := make([]string, 0) + for _, entry := range tlogEntries { + if err := VerifyTLogEntry(ctx, client, &entry); err != nil { + entryVerificationErrs = append(entryVerificationErrs, err.Error()) + continue + } + entryTime := time.Unix(*entry.IntegratedTime, 0) + if entryTime.Before(earliestLogEntryTime) { + earliestLogEntryTime = entryTime + earliestLogEntry = &entry + } + } + if earliestLogEntry == nil { + return nil, fmt.Errorf("no valid tlog entries found %s", strings.Join(entryVerificationErrs, ", ")) + } + return earliestLogEntry, nil } type fakeOCISignatures struct { diff --git a/pkg/cosign/verify_test.go b/pkg/cosign/verify_test.go index 58c25c69e5cc..a7433518a3a6 100644 --- a/pkg/cosign/verify_test.go +++ b/pkg/cosign/verify_test.go @@ -414,7 +414,7 @@ func TestVerifyImageSignatureWithSigVerifierAndRekor(t *testing.T) { // but we should look into improving this once there is an in-memory // Rekor client that is capable of performing inclusion proof validation // in unit tests. - t.Fatal("expected error while verifying signature") + t.Fatalf("expected error while verifying signature, got %s", err) } }