# Security ## Private key on the server Design B generates the user private key in the portal: - Created in RAM on POST `/download` - Exported into an identity PKCS#12 (still RAM) - Embedded in the plist, which is then CMS-signed - Returned in the HTTP response - Not written to disk; no cache of keys or P12 passwords Anyone who can download a profile for a user obtains that user’s private key. Protect the app like an RA: - OIDC on every download - Optional `LDAP_REQUIRED_GROUP` - HTTPS only in production (`SESSION_HTTPS_ONLY`, reverse-proxy TLS) - Restrict who can reach the portal (VPN, SSO group, IP allow list) Do not use this design for keys that must be device-bound. ## Logging The portal logs UPN and SID at info when a profile is issued. It does **not** log: - Identity P12 bytes or passwords - Profile-signing P12 passwords - OIDC client secrets, LDAP bind passwords, certsrv passwords - Raw CSR PEM at info (avoid debug logging in production) Session cookie: `HttpOnly`, `SameSite=lax`, `Secure` when `SESSION_HTTPS_ONLY` is true. ## Who can enroll 1. Must complete OIDC (valid session). 2. Must match exactly one LDAP user. 3. If `LDAP_REQUIRED_GROUP` is set, must be a member (nested groups allowed). 4. The **service account**, not the user, must have Enroll on the template. A signed-in user cannot enroll a template the portal account cannot enroll. There is no Enrollment Agent. A caller cannot request a cert for a different SID than LDAP returned for their username. ## CA trust Prefer `CERTSRV_VERIFY_TLS=true` and `LDAP_VERIFY_TLS=true`. Disabling TLS verification allows credential theft on the path to certsrv (NTLM) and LDAP. ## OpenAPI Leave `DOCS_ENABLED=false` in production so `/docs` is not public. ## Threat notes | Issue | Mitigation | | --- | --- | | Stolen session cookie | Short IdP session, HTTPS, `Secure` cookies | | Stolen signing P12 | Filesystem permissions / secret store; rotate | | Template too powerful | Dedicated Wi-Fi user template, not a smartcard/admin template | | certsrv HTML change | Parser fails closed (unknown page = error), fixtures in tests | | SSRF-ish CA URL | `CERTSRV_URL` is config, not user input |