alitheg opened a new issue, #1417:
URL: https://github.com/apache/tooling-trusted-releases/issues/1417
<div class="markdown-heading" dir="auto"><h4 tabindex="-1"
class="heading-element" dir="auto">Expired signing keys accepted during
signature verification; OpenPGP revocation handling not explicit or
tested</h4></div>
<markdown-accessiblity-table data-catalyst="">
Attribute | Value
-- | --
Severity | 🔴 Critical
ASVS Level(s) | L2, L3
CWE | —
ASVS Sections | 11.6.1, 4.1.5
Files | atr/tasks/checks/signature.py, atr/storage/writers/keys.py,
atr/get/keys.py
Source Reports | 11.6.1.md, 4.1.5.md
Related | —
</markdown-accessiblity-table>
<p dir="auto"><strong>Description:</strong></p>
<p dir="auto">ATR computes and persists key expiration as a first-class
security attribute (<code>_key_expires_at</code> via
<code>_effective_key_expiration_self_signature</code> in
<code>atr/storage/writers/keys.py</code>, stored in
<code>PublicSigningKey.expires</code>; <code>atr/get/keys.py::details</code>
renders an 'Expired' danger badge from this field). However, the
signature-verification path
(<code>atr/tasks/checks/signature.py::_check_core_logic</code>) loads full
<code>PublicSigningKey</code> rows — including <code>expires</code> — and never
consults it. The <code>where</code> clause filters only on committee membership
and <code>deleted IS NULL</code>; there is no <code>expires</code> filter, and
<code>_check_core_logic_verify_signature</code> calls
<code>signature.verify_file(candidate_key, artifact_path)</code>
with no expiry check on the key or on the signature creation time
relative to key validity. An expired key — precisely the class of keys
most likely to have lapsed operational protection or been deliberately
expired after suspected compromise — verifies identically to a valid
key, yielding 'Signature verified successfully'. Data flow:
attacker-held expired key (committee-linked, not soft-deleted) →
detached <code>.asc</code> uploaded with tampered artifact →
<code>check()</code> → <code>_check_core_logic</code> (no expiry filter) →
<code>verify_file</code>
succeeds. Impact: forged signatures on release artifacts pass the
committee signature check, so the primary supply-chain assurance ('this
artifact was signed by a currently valid committee key') is false.
Additionally, it is unverified whether the <code>openpgp</code> library
evaluates OpenPGP revocation self-signatures during <code>verify_file</code>;
the embedded test key in <code>tests/unit/test_keys_writer.py</code>
is titled 'Demonstrating Revoker Trouble (DO NOT USE)' yet no test
asserts rejection of a revoked or expired key at verification time.</p>
<p dir="auto"><strong>Remediation:</strong></p>
<p dir="auto">In <code>_check_core_logic</code>, after loading keys, filter
out expired keys: <code>db_public_keys = [key for key in db_public_keys if
(key.expires is None) or (key.expires >
datetime.datetime.now(datetime.UTC))]</code>. Better: also check the signature
creation time against key validity — reject if <code>key.expires is not None
and signature_info.creation_time > key.expires</code>. Additionally, verify
(and document with a test) whether the <code>openpgp</code> library evaluates
OpenPGP revocation self-signatures during <code>verify_file</code>;
if it does not, revocation certificates present in imported key blocks
must be evaluated explicitly. Add regression tests using the
already-embedded expiring test key.</p>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]