checker-email-keys/README.md

94 lines
7.1 KiB
Markdown

# 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.<zone>`.
- `abstract.SMimeCert`: user's S/MIME certificate, owner-hashed below
`._smimecert.<zone>`.
[rfc7929]: https://www.rfc-editor.org/rfc/rfc7929
[rfc8162]: https://www.rfc-editor.org/rfc/rfc8162
## Security scope
This checker validates DNS publication and the structure/metadata of the
keys it finds. It does **not** cryptographically verify them:
- OpenPGP signatures (self-signatures, third-party certifications,
revocations beyond the presence of a revocation packet) are **not**
verified.
- S/MIME certificate chains are **not** built or validated against any
trust anchor; revocation (CRL/OCSP) is **not** checked.
- Authenticity of the records themselves is delegated to the
validating resolver via the DNSSEC `AD` flag (see
`dnssec_not_validated`). Run the checker against a resolver you
trust to perform DNSSEC validation.
Treat a green report as "the record is well-formed and DNSSEC-signed",
not as "the key is trustworthy".
## Rules
| Code | Description | Severity |
|-----------------------------------|---------------------------------------------------------------------------------------------------|---------------------|
| `dns_query_failed` | Verifies that the DNS lookup for the OPENPGPKEY/SMIMEA record succeeds. | CRITICAL |
| `dns_no_record` | Verifies that an OPENPGPKEY/SMIMEA record is published at the expected owner name. | CRITICAL |
| `dns_record_mismatch` | Verifies that the record returned by DNS matches the service-declared record. | WARNING |
| `dnssec_not_validated` | Verifies that the record is authenticated by DNSSEC (AD flag set). | CRITICAL |
| `owner_hash_mismatch` | Verifies that the owner-name first label equals hex(sha256(username))[:28]. | CRITICAL |
| `pgp_parse_error` | Verifies that the OPENPGPKEY record decodes as a valid OpenPGP key. | CRITICAL |
| `pgp_primary_revoked` | Verifies that the OpenPGP primary key carries no revocation signature. | CRITICAL |
| `pgp_primary_expired` | Verifies that the OpenPGP primary key has not passed its self-signature expiry. | CRITICAL |
| `pgp_primary_expiring_soon` | Warns when the OpenPGP primary key expires within the configured window. | WARNING |
| `pgp_weak_algorithm` | Verifies that OpenPGP keys do not use legacy algorithms (DSA/ElGamal). | WARNING |
| `pgp_weak_key_size` | Verifies that OpenPGP RSA keys meet the minimum 2048-bit size (3072+ preferred). | CRITICAL |
| `pgp_no_encryption_subkey` | Verifies that at least one active OpenPGP key advertises encryption capability. | CRITICAL |
| `pgp_no_identity` | Verifies that the OpenPGP key carries at least one self-signed User ID. | WARNING |
| `pgp_uid_mismatch` | Checks that at least one OpenPGP UID references <username@...>. | INFO |
| `pgp_multiple_entities` | Verifies that the record carries a single OpenPGP entity (RFC 7929). | WARNING |
| `pgp_record_too_large` | Verifies that the OPENPGPKEY record stays below 4 KiB to fit typical UDP answers. | WARNING |
| `smimea_bad_usage` | Verifies that the SMIMEA usage field is 0, 1, 2, or 3. | CRITICAL |
| `smimea_bad_selector` | Verifies that the SMIMEA selector field is 0 (Cert) or 1 (SPKI). | CRITICAL |
| `smimea_bad_match_type` | Verifies that the SMIMEA matching type is 0 (Full), 1 (SHA-256), or 2 (SHA-512). | CRITICAL |
| `smimea_cert_parse_error` | Verifies that the SMIMEA record decodes as a valid X.509 certificate or SPKI. | CRITICAL |
| `smimea_cert_not_yet_valid` | Verifies that the S/MIME certificate's NotBefore is in the past. | CRITICAL |
| `smimea_cert_expired` | Verifies that the S/MIME certificate's NotAfter is in the future. | CRITICAL |
| `smimea_cert_expiring_soon` | Warns when the S/MIME certificate expires within the configured window. | WARNING |
| `smimea_no_email_protection_eku` | Verifies that the S/MIME certificate advertises the emailProtection EKU. | CRITICAL |
| `smimea_missing_key_usage` | Verifies that the certificate carries digitalSignature and/or keyEncipherment key usage. | WARNING |
| `smimea_weak_signature_algorithm` | Verifies that the certificate is not signed with a deprecated algorithm (MD2/MD5/SHA-1). | CRITICAL |
| `smimea_weak_key_size` | Verifies that SMIMEA RSA keys meet the minimum 2048-bit size (3072+ preferred). | CRITICAL |
| `smimea_self_signed` | Flags self-signed certificates paired with PKIX-EE (usage 1). | INFO |
| `smimea_email_mismatch` | Checks that at least one email SAN on the certificate begins with <username>@. | INFO |
| `smimea_hash_only` | Notes that SMIMEA matching types 1/2 transport only a digest, preventing certificate inspection. | INFO |
## 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
```