dedupe cert list rows to the last non-revoked entry per CN
index.txt is append-only: a CN left unrevoked after expiring, then reissued, accumulates multiple V-status lines. load_expiring_certs() and load_all_certs() previously returned one CertInfo per line, so such a CN showed as duplicate rows in the TUI main menu (and in --list/--list-all). Extract the existing "last non-revoked entry wins" scan (previously only in get_email()) into a shared _load_current_certs(), and build load_expiring_certs()/load_all_certs()/get_email() on top of it. The date-window filter in load_expiring_certs() now applies to each CN's canonical (most recent) entry rather than to raw lines, so a stale duplicate that happens to fall in the "recently expired" window no longer masks a live reissued cert whose real expiry is outside it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,43 @@ def test_load_all_certs_sorted_ascending(tmp_path):
|
||||
assert dates == sorted(dates)
|
||||
|
||||
|
||||
def _make_duplicate_cn_pki(tmp_path, now):
|
||||
"""index.txt with an old (still V-status, never revoked) entry for
|
||||
"dave" plus a newer reissued V-status entry for the same CN."""
|
||||
pki = tmp_path / "pki"
|
||||
pki.mkdir()
|
||||
e_old = (now - timedelta(days=5)).strftime("%y%m%d%H%M%S") + "Z" # expired, left unrevoked
|
||||
e_new = (now + timedelta(days=365)).strftime("%y%m%d%H%M%S") + "Z" # reissued, far future
|
||||
content = (
|
||||
f"V\t{e_old}\t\t01\tunknown\t/CN=dave/emailAddress=old@example.com\n"
|
||||
f"V\t{e_new}\t\t02\tunknown\t/CN=dave/emailAddress=newest@example.com\n"
|
||||
)
|
||||
(pki / "index.txt").write_text(content)
|
||||
return str(pki), e_old, e_new
|
||||
|
||||
|
||||
def test_load_all_certs_dedups_to_last_valid_entry(tmp_path):
|
||||
# A CN left unrevoked after expiring, then reissued, must appear once
|
||||
# — as its most recent entry — not as two rows.
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
pki, _e_old, _e_new = _make_duplicate_cn_pki(tmp_path, now)
|
||||
certs = load_all_certs(pki)
|
||||
dave_rows = [c for c in certs if c.cn == "dave"]
|
||||
assert len(dave_rows) == 1
|
||||
assert dave_rows[0].email == "newest@example.com"
|
||||
assert dave_rows[0].days_left > 300
|
||||
|
||||
|
||||
def test_load_expiring_certs_filters_by_current_entry_not_stale_duplicate(tmp_path):
|
||||
# The stale (unrevoked) old entry for "dave" falls inside the
|
||||
# recently-expired window, but the *current* entry (reissued, far in
|
||||
# the future) doesn't — so "dave" must NOT show up as "expiring soon".
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
pki, _e_old, _e_new = _make_duplicate_cn_pki(tmp_path, now)
|
||||
certs = load_expiring_certs(pki, days_past=30, days_ahead=14)
|
||||
assert "dave" not in {c.cn for c in certs}
|
||||
|
||||
|
||||
def test_get_email_reads_from_index_txt(tmp_path):
|
||||
now = datetime.now(tz=timezone.utc)
|
||||
pki = tmp_path / "pki"
|
||||
|
||||
Reference in New Issue
Block a user