# checker-email-keys DANE-Email posture checker for happyDomain. Runs a comprehensive testsuite on a domain's DNS-published OpenPGP key (`OPENPGPKEY`, [RFC 7929][rfc7929]) or S/MIME certificate (`SMIMEA`, [RFC 8162][rfc8162]) and renders an actionable HTML report whose top block nudges the user toward the fix for the most common failure scenarios. This checker binds to the happyDomain services: - `abstract.OpenPGP`: individual user's PGP key, owner-hashed below `._openpgpkey.`. - `abstract.SMimeCert`: user's S/MIME certificate, owner-hashed below `._smimecert.`. [rfc7929]: https://www.rfc-editor.org/rfc/rfc7929 [rfc8162]: https://www.rfc-editor.org/rfc/rfc8162 ## Tests run All findings are tagged by severity (`info` / `warn` / `crit`) so the rule engine can fold them into a single `CheckState`. ### DNS (both record types) | Code | Severity | What it catches | | --- | --- | --- | | `dns_query_failed` | crit | The resolver returned an error or did not answer. | | `dns_no_record` | crit | The authoritative answer has no record at the expected owner. | | `dnssec_not_validated` | crit / warn | The validating resolver did not set `AD`. RFC 7929/8162 mandate DNSSEC; the severity is configurable via `requireDNSSEC`. | | `dns_record_mismatch` | warn | The record returned by DNS differs from the one declared in the service (typically a stale zone on the authoritative servers). | | `owner_hash_mismatch` | crit | Record owner-name first label is not `sha256(localpart)[:28]`; mail clients will never find it. | ### OpenPGP-specific (RFC 7929) | Code | Severity | What it catches | | --- | --- | --- | | `pgp_parse_error` | crit | Malformed base64 or OpenPGP packet stream. | | `pgp_no_entity` | crit | Record decoded but carries no valid entity. | | `pgp_primary_revoked` | crit | Primary key has a revocation signature. | | `pgp_primary_expired` | crit | Self-signature expired; clients will refuse to encrypt. | | `pgp_primary_expiring_soon` | warn | Expires within the `certExpiryWarnDays` window (default 30). | | `pgp_weak_algorithm` | warn | Uses DSA / ElGamal (phase-out). | | `pgp_weak_key_size` | crit / warn | RSA below 2048 bits is critical, 2048-3071 is a warn. | | `pgp_no_encryption_subkey` | crit | No active key in the entity advertises encryption capability. | | `pgp_no_identity` | warn | No self-signed User ID. | | `pgp_uid_mismatch` | info | None of the UIDs reference ``. | | `pgp_multiple_entities` | warn | Record carries more than one entity (RFC 7929 recommends one). | | `pgp_record_too_large` | warn | Raw key > 4 KiB; forces UDP→TCP fallback on every lookup. | ### SMIMEA-specific (RFC 8162) | Code | Severity | What it catches | | --- | --- | --- | | `smimea_bad_usage` / `_selector` / `_match_type` | crit | Field outside the allowed range. | | `smimea_cert_parse_error` | crit | Hex-encoded blob is not a valid X.509 certificate / SPKI. | | `smimea_cert_expired` / `_not_yet_valid` | crit | `notBefore` / `notAfter` gate the current time out. | | `smimea_cert_expiring_soon` | warn | Within the `certExpiryWarnDays` window. | | `smimea_no_email_protection_eku` | crit / warn | Missing `emailProtection` EKU (RFC 8550/8551 agents will reject). | | `smimea_missing_key_usage` | warn | Neither `digitalSignature` nor `keyEncipherment` key-usage is set. | | `smimea_email_mismatch` | info | No email SAN starts with `@`. | | `smimea_weak_signature_algorithm` | crit | MD5 / SHA-1 based signature. | | `smimea_weak_key_size` | crit / warn | RSA < 2048 / 3072 bits. | | `smimea_self_signed` | info | Self-signed certificate paired with PKIX-EE usage. | | `smimea_hash_only` | info | Matching-type 1/2 only carries a digest; certificate can't be inspected. | ## Options | Id | Type | Default | Description | | --- | --- | --- | --- | | `resolver` | string | *(system)* | Validating resolver to query; comma-separated list accepted. | | `certExpiryWarnDays` | number | 30 | Raise an `expiring_soon` warning within this window. | | `requireDNSSEC` | bool | true | When false, missing AD is a warn instead of crit. | | `requireEmailProtection` | bool | true | When false, missing `emailProtection` EKU is a warn instead of crit. | Auto-filled by the host: `domain_name`, `subdomain`, `service`, `service_type`. ## Running ```bash # Plugin (loaded by happyDomain at startup) make plugin # Standalone HTTP server make && ./checker-email-keys -listen :8080 ```