Advertise a valid IPPS certificate for AirPrint
ci/woodpecker/push/build Pipeline was successful

This commit is contained in:
juergen committed 2026-10-18 22:35:48 +02:00
1 parent 7f26ca4f2f
commit db23feada5
8 files changed
+561 -15

No files matched your search

+98 -9
View File
@@ -42,7 +42,7 @@ becomes:
| --- | --- |
| `Containerfile` | Image definition (Debian base, CUPS + cups-filters + Avahi, BR-Script3 PPD) |
| `cupsd.conf` | Container CUPS configuration (listen on all interfaces, share printers) |
| `entrypoint.sh` | Starts D-Bus, Avahi and CUPS, then registers the printer queue |
| `entrypoint.sh` | Starts D-Bus, Avahi and CUPS, generates the `_ipps` certificate, then registers the printer queue |
| `assets/Brother HL-4150CDN/ppd/` | Official BR-Script3 PPD used for the queue |
| `assets/Brother HL-4150CDN/Windows/` | Vendor Windows driver the PPD was extracted from |
@@ -85,6 +85,8 @@ podman run -d --name printserver \
| `PRINTER_NAME` | `HL4150CDN` | CUPS queue name. Also used as the name advertised via AirPrint/DNS-SD. |
| `PRINTER_DESCRIPTION` | *(empty)* | CUPS printer description (`lpadmin -D`), shown in the CUPS web UI. |
| `PPD_FILE` | `/usr/share/cups/model/BR4150_2.PPD` | PPD used for the queue. |
| `CERT_EXTRA_NAMES` | *(empty)* | Comma-separated extra DNS names and/or IP addresses to put into the generated `_ipps` certificate. |
| `CERT_DAYS` | `3650` | Validity of the generated certificate, in days. |
No queue is created when neither `PRINTER_IP` nor `PRINTER_URI` is set.
@@ -121,6 +123,51 @@ set `PRINTER_DESCRIPTION` (this does not change the advertised name):
-e PRINTER_NAME=HL4150CDN -e PRINTER_DESCRIPTION="Brother HL-4150CDN (Office)"
```
### TLS certificate for `_ipps`
CUPS advertises the queue twice over mDNS: `_ipp._tcp` (plain) and `_ipps._tcp`
(TLS). macOS and iOS prefer the secure one, and *validate the certificate
against the name they connected to*. The name clients see is Avahi's
`<host>.local`, which is not necessarily the container's host name — so the
entrypoint installs its own self-signed certificate instead of leaving it to
CUPS. Its SANs cover the container host name, `<host name>.local`, Avahi's
FQDN, `localhost`, `127.0.0.1` and everything in `CERT_EXTRA_NAMES`:
```sh
-e CERT_EXTRA_NAMES=192.168.100.20,print.example.org
```
The certificate lives in `/etc/cups/ssl` (so that directory must be writable:
use a named volume, see the bind-mount caveat under *Notes and limitations*)
and is only regenerated when one of the names it must cover is missing, so a
restart normally logs `TLS certificate is up to date`. Inspect what the server
actually presents:
```sh
podman cp printserver:/etc/cups/ssl/server.crt .
openssl x509 -in server.crt -noout -subject -ext subjectAltName
echo | openssl s_client -connect localhost:631 -servername "$(hostname).local" \
| openssl x509 -noout -subject -ext subjectAltName
```
Because the certificate is self-signed, a fully validating client (macOS/iOS
AirPrint, `curl` without `-k`) rejects it until you trust it. CUPS' own tools
(`lpadmin`, `ipptool`) are lenient, so plain printing works without this step:
- **macOS** (needs your password):
```sh
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ./server.crt
```
- **iOS/iPadOS**: AirDrop or mail `server.crt` to the device, install the
profile, then enable full trust under *Settings → General → About →
Certificate Trust Settings*.
Alternatively, keep using plain IPP (port 631 without TLS) — printing and
duplex/colour options work there too.
## Printing options
The BR-Script3 PPD exposes the HL-4150CDN's hardware options. Common ones:
@@ -147,7 +194,7 @@ podman exec printserver lp -d HL4150CDN \
| Platform | Path | Notes |
| --- | --- | --- |
| macOS | AirPrint / IPP Everywhere | Discovered via mDNS; no driver needed |
| macOS | AirPrint / IPP Everywhere | Discovered via mDNS; no driver needed. If *Use:* shows *Generic PostScript Printer*, see [Troubleshooting](#macos-adds-the-printer-as-a-generic-postscript-printer) |
| iOS / iPadOS | AirPrint | Discovered via mDNS; no driver needed |
| Linux | IPP / IPP Everywhere | Native or with the shared PPD |
| Windows | IPP or raw 9100 | Install Brother's **BR-Script3** Windows driver, pointed at the printer or the shared queue |
@@ -251,15 +298,57 @@ the firewall.
If plain IPP works but the *secure* connection fails, this is the `_ipps`
service: macOS prefers `_ipps._tcp`, which CUPS advertises unconditionally
(also when no certificate exists). CUPS creates its TLS certificate lazily on
the first TLS connection, in `/etc/cups/ssl` — so that directory must be
writable. If it is not, CUPS logs
`Unable to encrypt connection: Unable to create server credentials` and the
secure connection fails while plain IPP keeps working. Use a **writable (named)
volume** for `/etc/cups`; see the bind-mount caveat under *Notes and
limitations*. After changing anything, remove the printer in macOS and add it
(also when no certificate exists). The certificate lives in `/etc/cups/ssl`, so
that directory must be writable — use a **writable (named) volume**; see the
bind-mount caveat under *Notes and limitations*. If it is not writable, CUPS
logs `Unable to encrypt connection: Unable to create server credentials` and the
secure connection fails while plain IPP keeps working.
The other cause is the certificate itself: clients only accept it when the name
they connected to is in its SANs *and* they trust it (see *TLS certificate for
`_ipps`*). Typical symptoms: `curl https://<host>.local:631/` ends with
`(60) SSL certificate problem`, or `ipptool -tv ipps://…` succeeds while
macOS/iOS refuse. Add the missing name via `CERT_EXTRA_NAMES` or trust the
certificate. After changing anything, remove the printer in macOS and add it
again — macOS caches the failed attempt.
### macOS adds the printer as a Generic PostScript printer
Symptom: the queue prints, but *Use:* shows **Generic PostScript Printer** and
the print dialog offers no duplex, no colour and no tray 2 — while iOS, which
queries the printer live, offers all of them. On the Mac,
`lpoptions -p <queue> -l` lists only `APOptionalDuplexer` plus
`Duplex`/`PageSize`/`ManualFeed`/`InputSlot`, and
`/etc/cups/ppd/<queue>.ppd` contains `*PCFileName: "Generic.ppd"`.
macOS only takes its driverless AirPrint path when it accepts the printer's
secure `_ipps` service; if that fails it silently falls back to its bundled
generic PostScript driver. Duplex then stays hidden, because the generic PPD
gates it behind `APOptionalDuplexer False`. The usual cause is the certificate
(see *TLS certificate for `_ipps`*), so check that first:
```sh
podman exec printserver avahi-browse -rt _ipps._tcp # what is advertised
podman cp printserver:/etc/cups/ssl/server.crt . &&
openssl x509 -in server.crt -noout -subject -ext subjectAltName
```
A verified way around it is to add the queue from the command line, driverless
over the same secure service (`ipptool -tv ipp://<host>:631/printers/<queue>`
prints the UUID; drop the `urn:uuid:` prefix):
```sh
sudo lpadmin -x <queue>
sudo lpadmin -p <queue> -E -m everywhere \
-v "dnssd://<queue>%20%40%20<host>._ipps._tcp.local./?uuid=<printer-uuid>"
lpoptions -p <queue> -l # Duplex, ColorModel and tray 2 show up now
```
In the GUI, remove the printer and add it again, then pick **AirPrint** (not
*Generic PostScript Printer*) under *Use*. If only the generic entry is offered,
the secure connection is still being rejected — fix the certificate, or add the
queue from the command line as above.
## CUPS 3 and the future of PPDs
Adding the queue with a PPD makes `lpadmin` print: