src/pqc_lint/rules.py
| 1 | """Rule catalog for pqc-lint.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | from dataclasses import dataclass |
| 6 | |
| 7 | from pqc_lint.findings import Severity |
| 8 | from pqc_lint.suggestions import suggest_replacement |
| 9 | |
| 10 | |
| 11 | @dataclass(frozen=True) |
| 12 | class Rule: |
| 13 | id: str |
| 14 | severity: Severity |
| 15 | classical_primitive: str # RSA-PSS, ECDSA, DH, etc. |
| 16 | title: str # short title |
| 17 | message: str # detailed message |
| 18 | languages: tuple[str, ...] # languages this rule applies to |
| 19 | cwe: str | None = None |
| 20 | |
| 21 | @property |
| 22 | def suggestion(self) -> str: |
| 23 | return suggest_replacement(self.classical_primitive) |
| 24 | |
| 25 | |
| 26 | # ------------------------------------------------------------------------- |
| 27 | # Rules |
| 28 | # ------------------------------------------------------------------------- |
| 29 | # ID scheme: |
| 30 | # PQC001-099 - signature schemes (RSA, ECDSA, Ed25519, DSA) |
| 31 | # PQC100-199 - key exchange (DH, ECDH, X25519) |
| 32 | # PQC200-299 - encryption (RSA-OAEP, RSA-PKCS1) |
| 33 | # PQC300-399 - weak hashes (MD5, SHA-1) |
| 34 | |
| 35 | RULES: tuple[Rule, ...] = ( |
| 36 | # -- Signatures (broken by Shor's) -- |
| 37 | Rule( |
| 38 | id="PQC001", severity=Severity.CRITICAL, |
| 39 | classical_primitive="RSA-signing", |
| 40 | title="RSA signature usage", |
| 41 | message="RSA signatures are broken by Shor's algorithm on a sufficiently large quantum computer.", |
| 42 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 43 | cwe="CWE-327", |
| 44 | ), |
| 45 | Rule( |
| 46 | id="PQC002", severity=Severity.CRITICAL, |
| 47 | classical_primitive="ECDSA", |
| 48 | title="ECDSA signature usage", |
| 49 | message="ECDSA signatures are broken by Shor's algorithm. All elliptic-curve signatures are quantum-vulnerable.", |
| 50 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 51 | cwe="CWE-327", |
| 52 | ), |
| 53 | Rule( |
| 54 | id="PQC003", severity=Severity.HIGH, |
| 55 | classical_primitive="Ed25519", |
| 56 | title="Ed25519 signature usage", |
| 57 | message="Ed25519 is a classical EC signature - broken by Shor's algorithm. Consider PQC alternative.", |
| 58 | languages=("python", "javascript", "go", "rust", "java"), |
| 59 | cwe="CWE-327", |
| 60 | ), |
| 61 | Rule( |
| 62 | id="PQC004", severity=Severity.HIGH, |
| 63 | classical_primitive="DSA", |
| 64 | title="DSA signature usage", |
| 65 | message="DSA is broken by Shor's algorithm and deprecated even in classical settings.", |
| 66 | languages=("python", "java"), |
| 67 | cwe="CWE-327", |
| 68 | ), |
| 69 | |
| 70 | # -- Key exchange (broken by Shor's) -- |
| 71 | Rule( |
| 72 | id="PQC101", severity=Severity.CRITICAL, |
| 73 | classical_primitive="ECDH", |
| 74 | title="ECDH key exchange", |
| 75 | message="Elliptic Curve Diffie-Hellman key exchange is broken by Shor's algorithm.", |
| 76 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 77 | cwe="CWE-327", |
| 78 | ), |
| 79 | Rule( |
| 80 | id="PQC102", severity=Severity.CRITICAL, |
| 81 | classical_primitive="DH", |
| 82 | title="Finite-field Diffie-Hellman", |
| 83 | message="Classical Diffie-Hellman is broken by Shor's algorithm.", |
| 84 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 85 | cwe="CWE-327", |
| 86 | ), |
| 87 | Rule( |
| 88 | id="PQC103", severity=Severity.HIGH, |
| 89 | classical_primitive="X25519", |
| 90 | title="X25519 key agreement", |
| 91 | message="X25519 is a classical EC key agreement - broken by Shor's algorithm.", |
| 92 | languages=("python", "javascript", "go", "rust", "java"), |
| 93 | cwe="CWE-327", |
| 94 | ), |
| 95 | |
| 96 | # -- Encryption -- |
| 97 | Rule( |
| 98 | id="PQC201", severity=Severity.CRITICAL, |
| 99 | classical_primitive="RSA-OAEP", |
| 100 | title="RSA-OAEP encryption", |
| 101 | message="RSA-OAEP encryption is broken by Shor's algorithm. All data encrypted today may be decrypted once CRQC exists (HNDL).", |
| 102 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 103 | cwe="CWE-327", |
| 104 | ), |
| 105 | Rule( |
| 106 | id="PQC202", severity=Severity.CRITICAL, |
| 107 | classical_primitive="RSA-PKCS1v15", |
| 108 | title="RSA PKCS#1v1.5 encryption", |
| 109 | message="RSA PKCS#1v1.5 is broken by Shor's algorithm AND has padding oracle vulnerabilities in classical settings.", |
| 110 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 111 | cwe="CWE-327", |
| 112 | ), |
| 113 | |
| 114 | # -- Weak hashes -- |
| 115 | Rule( |
| 116 | id="PQC301", severity=Severity.MEDIUM, |
| 117 | classical_primitive="MD5", |
| 118 | title="MD5 hashing", |
| 119 | message="MD5 is cryptographically broken. Use SHA3 family.", |
| 120 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 121 | cwe="CWE-328", |
| 122 | ), |
| 123 | Rule( |
| 124 | id="PQC302", severity=Severity.MEDIUM, |
| 125 | classical_primitive="SHA1", |
| 126 | title="SHA-1 hashing", |
| 127 | message="SHA-1 is broken (SHAttered). Use SHA3 for quantum-safe hashing.", |
| 128 | languages=("python", "javascript", "go", "rust", "java", "c"), |
| 129 | cwe="CWE-328", |
| 130 | ), |
| 131 | ) |
| 132 | |
| 133 | |
| 134 | RULE_BY_ID: dict[str, Rule] = {r.id: r for r in RULES} |
| 135 | |
| 136 | |
| 137 | def get_rule(rule_id: str) -> Rule: |
| 138 | return RULE_BY_ID[rule_id] |
| 139 | |
| 140 | |
| 141 | def get_rules_for_language(language: str) -> list[Rule]: |
| 142 | return [r for r in RULES if language in r.languages] |
| 143 | |