Initial commit
This commit is contained in:
commit
b97f30faf4
18 changed files with 2653 additions and 0 deletions
128
README.md
Normal file
128
README.md
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
# checker-openpgpkey
|
||||
|
||||
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
|
||||
|
||||
## 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 `<username@…>`. |
|
||||
| `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 `<username>@`. |
|
||||
| `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. |
|
||||
|
||||
## Why a bespoke checker instead of a third-party testsuite?
|
||||
|
||||
There is no canonical "OPENPGPKEY / SMIMEA testsuite" in Go or as a
|
||||
self-hostable online service:
|
||||
|
||||
- `ldns-dane` (NLnet Labs) validates DANE-TLSA and handles SMIMEA only
|
||||
shallowly (it parses the record without deep certificate checks).
|
||||
- `hokey` (Paul Wouters) queries OPENPGPKEY but does not validate the
|
||||
key material.
|
||||
- Online DANE validators (e.g. `dane.sys4.de`, `has-tls-rpt.com`) focus
|
||||
on SMTP DANE-TLSA, not email-identity records.
|
||||
|
||||
The heavy lifting here is standard Go parsing:
|
||||
|
||||
- `github.com/ProtonMail/go-crypto/openpgp` (maintained fork of the
|
||||
deprecated `golang.org/x/crypto/openpgp`) for OpenPGP packet parsing,
|
||||
UIDs, subkeys, revocations, key-lifetime self-signatures.
|
||||
- `crypto/x509` for SMIMEA certificate parsing, validity window, EKU,
|
||||
key-usage, signature-algorithm and key-size checks.
|
||||
- `github.com/miekg/dns` for the DNS+EDNS0+DO query and the `AD` flag
|
||||
read-back used as the DNSSEC-validation signal.
|
||||
|
||||
## 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-openpgpkey -listen :8080
|
||||
```
|
||||
|
||||
## HTML report
|
||||
|
||||
The report renders as a self-contained HTML document intended for
|
||||
embedding in an `<iframe>` (the same contract as the other happyDomain
|
||||
checkers). It is organised as:
|
||||
|
||||
1. **Header**: status badge, queried owner name, resolver used, DNSSEC
|
||||
flag.
|
||||
2. **Most common issues (fix these first)**: remediation cards shown
|
||||
*only* when a matching finding was emitted. Each card carries the
|
||||
concrete shell commands / zone-file snippets the user needs.
|
||||
3. **OpenPGP key / SMIMEA record**: structured details for the
|
||||
parsed material (fingerprint, UIDs, subkeys, cert subject/issuer,
|
||||
EKU/KU flags, …).
|
||||
4. **Findings**: the full table of per-finding code / severity /
|
||||
message / fix hints, sorted by severity.
|
||||
Loading…
Add table
Add a link
Reference in a new issue