sip: TLS transport with a checked certificate, TLS + SRTP trunks
ci / check (push) Successful in 58s
ci / images (push) Successful in 24s

The certificate lives in data/tls (fullchain.pem + privkey.pem), mounted
read-only into asterisk and the api. Asterisk quietly drops transport-tls
when the files are missing, so the api won't turn TLS on unless they load
and match, shows the certificate on Settings -> Security, and warns on the
dashboard when it expires within 14 days. scripts/tls-cert.sh copies it
from the bundled Caddy (or given paths) and reloads res_pjsip: Asterisk 24
re-reads a changed certificate on reload, calls stay up.

transport-tls checks the provider's certificate (verify_server). Asterisk
24 checks a qualify's certificate against the IP instead of the name, which
fails on voip.ms, so TLS trunks don't qualify; REGISTER and INVITE verify
the name fine.

Task: M11-T2
This commit is contained in:
iamdoubz committed 2026-10-19 16:33:11 -05:00
1 parent 8c750c67ca
commit 803859a1d2
23 files changed
+772 -19

No files matched your search

+5 -1
View File
@@ -149,6 +149,8 @@ bind = 0.0.0.0:{sip.tlsPort}
cert_file = /var/lib/selfphoneng/tls/fullchain.pem
priv_key_file = /var/lib/selfphoneng/tls/privkey.pem
method = tlsv1_2
verify_server = yes ; as built (M11-T2): check the provider (08 §2.1)
ca_list_file = /etc/ssl/certs/ca-certificates.crt
{natLines}
{/if}
@@ -168,7 +170,7 @@ Notes:
- The WebSocket bind address is ignored. The browser connects through Asterisk's HTTP server (`http.conf`).
If browser registration fails with a "no transport" error, change
`protocol = ws` to `protocol = wss` and fix the spec.
- The asterisk container needs `./data/tls:/var/lib/selfphoneng/tls:ro`. Add it to the compose file when TLS is first used (M11).
- The asterisk container needs `./data/tls:/var/lib/selfphoneng/tls:ro`. Added in M11-T2 (the api mounts it read-only too, as `TLS_DIR`).
- No `anonymous` endpoint is ever generated, so unauthenticated calls from the internet are rejected.
### 5.2 One extension (example: 101, Dan Doe, webrtc on)
@@ -319,6 +321,8 @@ For `auth_mode = 'ip'`, leave out the `-auth` and `-reg` sections and the `outbo
(and `from_user` when there is no username).
Skip disabled trunks entirely.
For TCP/TLS trunks, `server_uri`, `client_uri` and the AOR `contact` get `;transport=tcp|tls`.
As built (M11-T2): TLS trunks get `qualify_frequency = 0` (Asterisk 24 checks a qualify's certificate
against the IP, see 08 §2.1); their health comes from the registration.
`identify` resolves `match` when the config loads, and an unresolvable name is an ERROR that drops the
trunk's identify (verified), so the API refuses a server name that doesn't resolve, and a transport
that isn't turned on in Settings → SIP.
+2 -1
View File
@@ -407,9 +407,10 @@ Reports, history, system:
| GET/PATCH | `/api/settings` | whole settings object; PATCH merges known keys only. `smtp` comes back as `{host, port, secure, username, from, hasPassword}` and is written with an optional `password` (empty keeps the stored one). `system.*`, `sms.webhookToken` and `setup.completed` are read-only. References (DIDs, extensions, destinations) are checked, and a transport a trunk uses can't be turned off |
| POST | `/api/settings/smtp/test` | `{to}` sends a test email |
| POST | `/api/settings/emergency/test` | runs 04 §5.3 steps 2–4 with `TEST` in front (email, ring-out, banner); records nothing and dials nothing (06 §6.5). Returns `{notified}` |
| GET | `/api/system/warnings` | `{items: [{code, message, link}]}` for the dashboard banner: `restart_required`, `e911_cid_missing` (a trunk exists, no default E911 caller ID), `e911_address_unconfirmed` (that DID has no `e911_address`), `smtp_missing`. "Trunk down" comes from live state in the browser |
| GET | `/api/system/warnings` | `{items: [{code, message, link}]}` for the dashboard banner: `restart_required`, `e911_cid_missing` (a trunk exists, no default E911 caller ID), `e911_address_unconfirmed` (that DID has no `e911_address`), `smtp_missing`, `tls_cert` (TLS on and the cert missing or expiring within 14 days, M11-T2). "Trunk down" comes from live state in the browser |
| POST | `/api/settings/network/detect-public-ip` | |
| GET | `/api/system/status` | `{ db: ok, ami: connected, asteriskVersion, uptime, trunks: [...], disk: {free, total}, restartRequired, lastGeneratorRun }` |
| GET | `/api/system/tls` | the SIP TLS certificate in `TLS_DIR`: `{ok: true, subject, names, notAfter}` or `{ok: false, error}` (M11-T2) |
| GET | `/api/system/generated-files` | names + contents of generated files, **passwords masked** (debug view) |
| POST | `/api/system/config/apply` | force regenerate |
| POST | `/api/system/config/rollback` | |
+1 -1
View File
@@ -16,7 +16,7 @@ trunk fallback (same fields, no voip.ms-specific help text or API features).
- Allowed codecs: **G.711u (ulaw)**. Others optional.
- DTMF mode: **RFC 2833**
- NAT: **Yes**
- Encrypted SIP traffic (TLS/SRTP): optional. If on, set our trunk transport to TLS, port 5061, SRTP on.
- Encrypted SIP traffic (TLS/SRTP): optional. If on, set our trunk transport to TLS, port 5061, SRTP on. Needs Settings → Security → Accept SIP over TLS (a certificate in `data/tls`, 08 §2.1) first. voip.ms's certificate names every POP, so the trunk checks it (`verify_server`).
- CallerID Number: your main DID.
- International calling: leave **disabled** unless needed (toll-fraud protection on their side too).
2. **Choose a POP server** close to the PBX (e.g. `chicago1.voip.ms`). The sub-account registers to this server.
+19
View File
@@ -20,6 +20,25 @@
- Expose only 443/tcp, 5060/udp+tcp (5061/tcp if TLS), 10000–20000/udp.
- Admin guide includes an nftables example that limits 5060/5061 to voip.ms
server IPs (from the voip.ms wiki list, **verify**) plus `sip.extraAllowedNets`, for setups with no remote phones.
- As built (M11-T2), SIP over TLS:
- Certificate: `data/tls/fullchain.pem` + `privkey.pem`, mounted read-only into asterisk and the
api (`TLS_DIR`). `scripts/tls-cert.sh` copies them from the bundled Caddy's storage
(`data/caddy/caddy/certificates/*/<PUBLIC_HOSTNAME>/`) or from two given paths (certbot), owner
app UID, key 0600, and runs `module reload res_pjsip.so`. Asterisk 24 re-reads the cert and key on
that reload (same file names, and only when a file's mtime changed; verified, calls stay up), so
no restart. It does nothing when the
files are unchanged: run it daily from cron.
- Asterisk quietly leaves `transport-tls` out when the files are missing, so `PATCH /api/settings`
refuses to turn `sip.tlsEnabled` on unless both load and the key matches (node:crypto
`X509Certificate.checkPrivateKey`), and the dashboard warns (`tls_cert`) when TLS is on and the
cert is missing or expires within 14 days.
- `transport-tls` has `verify_server = yes` + `ca_list_file = /etc/ssl/certs/ca-certificates.crt`:
the provider's certificate is checked (voip.ms: Let's Encrypt, every POP in its SAN list,
checked 2026-10-19). Asterisk 24.0.0-rc2 checks a **qualify** OPTIONS against the IP instead of
the host name, which fails on voip.ms ("server identity does not match") and marks the contact
Unavailable, so TLS trunks get `qualify_frequency = 0` and Test skips the qualify; REGISTER and
INVITE verify the name correctly. Re-check on Asterisk upgrades.
- Phones over TLS are not provisioned (templates stay UDP); that's for remote phones later.
### 2.2 Phone ACL (setting `sip.restrictPhonesToLocalNets`)
When true, the generator adds to every **desk** extension endpoint (not `-web`, not trunks):