# drift DKIM-Signer for CommuniGate Pro
### Setup & Description

*Stalker Software GmbH, Berlin — www.cgatepro.de*
Version 0.1 · Windows & Linux · Document date: 2026-08-05

---

## 1. What it is

**drift DKIM-Signer** is a small helper for CommuniGate Pro (CGP) that adds a
**DKIM signature to outgoing e-mail, choosing the signing key by the sender's
From-domain.**

This solves a limitation of CGP's built-in DKIM: CGP signs per *domain*, but an
**alias domain** (a second name pointing at the same domain, e.g.
`barinformatik.com` used as an alias of `barinformatik.ch`) does **not** get its
own DKIM key. Mail sent `From: user@barinformatik.com` therefore goes out either
unsigned or signed with the wrong domain, and large providers such as **GMX,
web.de and Google** then fail DKIM/DMARC and treat the mail as suspicious.

With the DKIM-Signer, **every domain and every alias gets its own aligned
signature** (`d=` matches the From-domain), so those providers report
`dkim=pass` and `dmarc=pass`.

### Key facts
- Signs outgoing mail as a CGP **external content filter** (EXTFILTER protocol).
- **Per-From-domain** key selection — the feature CGP native DKIM lacks for aliases.
- Standard **RSA-SHA256**, **relaxed/relaxed** canonicalization, 2048-bit keys.
- Never blocks mail: if it cannot sign, mail simply passes through unsigned.
- Ships for **Windows and Linux**; identical behaviour on both.
- Includes a **Settings & Test GUI** and command-line tools.

---

## 2. How it works (overview)

```
   Outgoing mail (CGP queue)
            │
            ▼
   CGP external content filter  ──►  dkim-signer
            │                          reads From-domain
            │                          picks the matching key
            │                          adds "DKIM-Signature:" header
            ▼
   Mail leaves CGP, now signed  ──►  GMX / web.de / Google → dkim=pass, dmarc=pass
```

The helper reads its key map from `dkim-signer.cfg`, looks at each message's
`From:` header, and — if a key is configured for that domain — inserts a
`DKIM-Signature` header. Domains without a configured key pass through unchanged.

---

## 3. Requirements

- CommuniGate Pro (6.x) with permission to add an external content filter/rule.
- Control over your **DNS** (to publish the DKIM and DMARC TXT records).
- The `dkim-signer` binary for your platform (provided, pre-stamped by the vendor).
- *Optional, for advanced testing only:* Python 3 with `dkimpy`, `aiosmtpd`,
  `pyspf`, `authres`, `dnspython`, `cryptography`.

---

## 4. Files in the package

| File | Purpose |
|---|---|
| `dkim-signer.exe` (Windows) / `dkim-signer` (Linux) | the helper (pre-stamped) |
| `dkim-signer.cfg` | configuration |
| `dkim-signer-config.pyw` | **Settings & Test GUI** (with built-in guide) |
| `SETUP-AND-DESCRIPTION.md` | this document |
| `DEPLOYMENT-GUIDE.md` / `README.md` | condensed guide / reference |
| `testtools/harness.py` | independent sign+verify test (dkimpy) |
| `testtools/dkim_receiver.py` | local “GMX-style” inbound checker (DKIM+SPF+DMARC) |
| `testtools/stamp.py` | vendor build-integrity stamper (not needed by customers) |

---

## 5. Setup — step by step

You can do everything from the **Settings GUI** (`dkim-signer-config.pyw`) or the
command line. Both are shown below.

### Step 1 — Generate a key for each domain AND each alias
Do this once per domain *and* per alias name.

- **GUI:** tab **Domains & Keys** → enter Domain/Alias + Selector (default `mail`)
  → **Generate key + DNS record**. A window shows the DNS records to publish.
- **CLI:**
  ```
  dkim-signer genkey --domain barinformatik.ch  --selector mail --out keys
  dkim-signer genkey --domain barinformatik.com --selector mail --out keys
  ```

### Step 2 — Publish the DNS records
For **each** generated key, publish the shown records at your DNS provider:

```
Name : mail._domainkey.<domain>       Type: TXT
Value: v=DKIM1; k=rsa; p=<public key from step 1>

Name : _dmarc.<domain>                Type: TXT
Value: v=DMARC1; p=none; adkim=r; aspf=r; rua=mailto:postmaster@<domain>
```

Start DMARC with `p=none`; tighten to `quarantine`/`reject` only after you have
confirmed `dkim=pass` (Step 8). DNS changes may take up to a few hours to
propagate.

### Step 3 — Configure signing
`dkim-signer.cfg` (GUI: tabs **Domains & Keys** + **Signing Options**):

```ini
# One entry per domain AND per alias: domain:selector:keypath  (comma-separated)
domain_keys = barinformatik.ch:mail:C:\CommuniGate Files\Helpers\dkim-signer\keys\mail.barinformatik.ch.pem, \
              barinformatik.com:mail:C:\CommuniGate Files\Helpers\dkim-signer\keys\mail.barinformatik.com.pem

# Headers to sign (From is always signed). Defaults are recommended.
sign_headers = from,to,cc,subject,date,message-id,mime-version,content-type,content-transfer-encoding

# true = insert signatures (production). false = dry run (log only).
modify_files = true

log_level = 3
```

### Step 4 — Deploy
Place the binary + config + `keys/` folder in the CGP Helpers directory:

- **Windows:** `C:\CommuniGate Files\Helpers\dkim-signer\`
- **Linux:** `/var/CommuniGate/Helpers/dkim-signer/`

(GUI: tab **Deploy** → *Deploy binary + config*.) Keep private key files readable
only by the CGP service account.

### Step 5 — Register as a CGP external filter
Three server-side registrations are required (verified on CGP 6.4). All can be
done in WebAdmin; the underlying settings files are shown for reference.

1. **Allow the binary to run.** Add its full path to
   `Settings/ChildProcs.data` (one line), otherwise CGP refuses to launch it
   ("not allowed child process name"):
   ```
   C:\CommuniGate Files\Helpers\dkim-signer\dkim-signer.exe
   ```
2. **Register the External Filter.** WebAdmin → *Settings → General → Helpers →
   Content Filtering / External Filters* → add `dkim-signer`, program = the
   binary path, **Enabled**. (In `Main.settings` this is the `ExternalFilters`
   list.)
3. **Add a Rule** that runs it on outgoing mail. WebAdmin → *Settings → Rules*
   (server-wide): condition **`Source is authenticated`**, action **`Execute
   External Filter → dkim-signer`**. (In `Rules.settings`:
   `((Source, is, authenticated)) → ((ExternalFilter, dkim-signer))`.)

**Restart CGP** after step 1–2 (`ChildProcs.data` and `ExternalFilters` are read
at startup; Rules can be changed live). The helper adds the signature via the
CGP External-Filter `ADDHEADER` response — it never rewrites the message, so the
body and existing headers are delivered untouched.

### Step 6 — Disable CGP native DKIM for the handled domains  *(important)*
Otherwise mail would be signed twice. In WebAdmin, turn off CGP's built-in DKIM
for every domain this helper signs.

### Step 7 — Order relative to the whitelabel-filter
If you also run the drift whitelabel-filter (header rewriting), the DKIM-Signer
must run **after** it — otherwise a rewritten header would invalidate the
signature. (Alternatively, remove any rewritten header from `sign_headers`.)

### Step 8 — Test
In this order:
1. **GUI → Test → Self-Test** — confirms the binary can sign & verify (no setup).
2. **GUI → Test → Sign Sample** — signs a sample with your real config.
3. *(advanced)* verify like a receiver would:
   ```
   python testtools/dkim_receiver.py --file <signed.eml> --dns-map <map>
   # or run a live SMTP sink:
   python testtools/dkim_receiver.py --smtp 127.0.0.1:2525
   ```
4. **Real world:** send from each domain and alias to a GMX and a web.de
   mailbox, and to https://www.mail-tester.com. Confirm the
   `Authentication-Results:` header shows `dkim=pass` and `dmarc=pass`.

---

## 6. Multidomain mode

Multidomain is the **core operating mode** — the helper is built around choosing
the signing key per From-domain, so signing many domains *and* aliases is what it
does by design. A real CGP domain and an alias name are treated identically:
each is simply one more entry in `domain_keys`. There is **no practical limit** on
the number of domains (they are held in an in-memory map).

Add domains as a comma-separated list — **one entry per domain or alias**:

```ini
domain_keys = barinformatik.ch:mail:...\mail.barinformatik.ch.pem, \
              barinformatik.com:mail:...\mail.barinformatik.com.pem, \
              example.de:mail:...\mail.example.de.pem
```

### Prerequisites — for EACH domain / alias you want signed
All five are required per entry:
1. **A DKIM keypair** — `dkim-signer genkey --domain <d> --selector mail` (or the
   GUI **Generate** button). One key per domain/alias.
2. **A `domain_keys` entry** — `domain:selector:keypath` (comma-separated).
3. **A published DNS TXT record** — `<selector>._domainkey.<domain>` = the public key.
4. **A DMARC record** (recommended) — `_dmarc.<domain>`.
5. **CGP native DKIM disabled** for that domain (otherwise mail is signed twice).

Global prerequisites (once, not per domain): the helper registered as a CGP
external filter on outgoing mail (Step 5), and — if the whitelabel-filter is also
used — DKIM-Signer ordered **after** it (Step 7).

### Important behaviour & limitations
- **Exact From-domain match, case-insensitive.** There are **no wildcards** and
  **no subdomain fallback** — `mail.example.de` needs its own entry, separate
  from `example.de`.
- **Unmatched domains pass through unsigned** (never blocked). A missing entry
  therefore means *unsigned mail*, not an error — always add an entry for every
  From-domain you actually send from.
- **Licensing does not cap the number of signing domains.** In eval/beta mode all
  configured domains are signed until the 2026-12-31 cut-off (Section 7). A full
  `license.key` gates on the `dkim` helper and the server's main domain; it does
  not limit how many domains/aliases you configure.

---

## 7. License & evaluation period

- **Without a license** the helper is **fully functional until 31 December 2026.**
  After that date it keeps mail flowing but **passes it through unsigned** until a
  license is installed.
- A signed **`license.key`** (RSA-2048, the standard drift-helper format) placed
  next to the binary removes the time limit. The license must permit the `dkim`
  helper and cover your main domain.
- If signing is ever withheld the reason is written to `dkim-signer.log`
  (`beta-expired`, `tampered`, or `clock-rollback`).

---

## 8. Security / integrity notes (for operators)

The binary is **hardened and tamper-evident**. For correct operation:
- **Deploy the binary exactly as shipped** — do not modify or repackage it. The
  helper verifies its own integrity and will stop signing if the file was
  changed.
- **Keep the system clock correct.** Moving the clock backwards is detected and
  disables signing (anti-rollback protection).
- **Protect the private keys** (`keys/*.pem`) — restrict file permissions to the
  CGP service account. Publish only the *public* key in DNS.

These controls only ever *disable signing* (mail continues to flow, unsigned);
they never reject or delay mail.

---

## 9. Troubleshooting

| Symptom | Check |
|---|---|
| Mail goes out unsigned | Is there a `domain_keys` entry for that exact From-domain? See `dkim-signer.log`. |
| `dkim=fail` at receiver | Does the published DNS TXT (`<selector>._domainkey.<domain>`) match the generated key? Did DNS propagate? |
| Signed twice / conflicting | Disable CGP native DKIM for that domain (Step 6). |
| `dkim=pass` locally but fails after another filter | Ensure DKIM-Signer runs **after** header-rewriting filters (Step 7). |
| Log says `signing disabled [tampered]` | The binary was modified — redeploy the original shipped file. |
| Log says `signing disabled [clock-rollback]` | System clock was set backwards — correct the clock. |
| Log says `signing disabled [beta-expired]` | Install a valid `license.key`. |

Logs: `dkim-signer.log` in the helper folder (set `log_level` 0–4 in the config).

---

## 10. Current status (honesty note)

The signing engine is **independently verified locally**: valid RSA-SHA256
signatures, correct per-From (alias) key selection, tamper detection — confirmed
with the third-party **dkimpy** verifier and a local inbound receiver. The final
confirmation is always a **real send** from your server, with your DNS records
published, to GMX / web.de / Google (Step 8) — that verifies the full external
path (IP reputation, TLS) which no local test can substitute.

---

*Questions / support: www.cgatepro.de*
