--- checkId: check.crypto.certchain plugin: stellaops.doctor.crypto severity: warn tags: [crypto, certificate, tls, security] --- # Certificate Chain Validation ## What It Checks Verifies certificate chain completeness, trust anchor validity, and expiration for the configured TLS certificate. The check reads the certificate path from `Crypto:TlsCertPath`, `Kestrel:Certificates:Default:Path`, or `Server:TlsCertificate` and validates: - **File existence**: whether the configured certificate file exists on disk. - **Chain completeness**: whether all intermediate certificates are present (no missing links). - **Trust anchor validity**: whether the root CA is trusted by the system trust store. - **Expiration**: days until the certificate expires, with tiered severity. | Condition | Result | |---|---| | No TLS certificate configured | Skip | | Certificate file not found | Fail | | Certificate chain incomplete (missing intermediates) | Fail | | Trust anchor not valid (unknown root CA) | Fail | | Certificate already expired | Fail | | Certificate expires within 7 days | Fail | | Certificate expires within 30 days | Warn | | Chain complete, trust anchor valid, not expiring soon | Pass | Evidence collected: `CertPath`, `ChainLength`, `MissingIntermediates`, `TrustAnchorValid`, `TrustAnchorIssuer`, `ExpirationDate`, `DaysRemaining`. This check always runs (no precondition), but skips if no TLS certificate path is configured. ## Why It Matters An incomplete certificate chain causes TLS handshake failures for clients that do not have intermediate certificates cached. An untrusted root CA triggers browser and API client warnings or outright connection refusal. An expired certificate causes immediate service outage for all HTTPS connections. Certificate issues affect every component that communicates over TLS, including the UI, API, inter-service communication, and external integrations. ## Common Causes - Certificate file was moved or deleted from the configured path - Incorrect certificate path in configuration - Missing intermediate certificates in the certificate bundle - Incomplete certificate bundle (only leaf certificate, no intermediates) - Root CA not added to the system trust store - Self-signed certificate not explicitly trusted - Certificate not renewed before expiration - Automated renewal process failed silently ## How to Fix ### Docker Compose ```bash # Check if certificate file exists at configured path docker compose exec gateway ls -la /certs/ # Verify certificate details docker compose exec gateway openssl x509 -in /certs/server.crt -noout -dates -subject -issuer # Verify certificate chain docker compose exec gateway openssl verify -untrusted /certs/chain.pem /certs/server.crt # Bundle certificates correctly (leaf + intermediates) cat server.crt intermediate.crt > fullchain.pem # Update configuration in .env or compose override # Crypto__TlsCertPath=/certs/fullchain.pem # Set up automated renewal notification # Notify__CertExpiry__ThresholdDays=14 ``` ### Bare Metal / systemd ```bash # Verify certificate file exists ls -la /etc/stellaops/certs/server.crt # Check certificate expiration openssl x509 -in /etc/stellaops/certs/server.crt -noout -enddate # Download missing intermediates stella crypto cert fetch-chain --cert /etc/stellaops/certs/server.crt --output /etc/stellaops/certs/fullchain.pem # Add CA to system trust store (Debian/Ubuntu) sudo cp root-ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates # Or configure explicit trust anchor stella crypto trust-anchors add --type ca --cert root-ca.crt # Renew certificate stella crypto cert renew --cert /etc/stellaops/certs/server.crt # Update appsettings.json # "Crypto": { "TlsCertPath": "/etc/stellaops/certs/fullchain.pem" } sudo systemctl restart stellaops-gateway ``` ### Kubernetes / Helm ```bash # Check certificate secret kubectl get secret stellaops-tls-cert -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -dates # Verify certificate chain kubectl get secret stellaops-tls-cert -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl verify # Update TLS certificate secret kubectl create secret tls stellaops-tls-cert \ --cert=fullchain.pem \ --key=server.key \ --dry-run=client -o yaml | kubectl apply -f - ``` ```yaml # values.yaml - use cert-manager for automated renewal certManager: enabled: true issuer: letsencrypt-prod renewBefore: 360h # 15 days before expiry ``` ## Verification ``` stella doctor run --check check.crypto.certchain ``` ## Related Checks - `check.crypto.fips` — FIPS compliance may impose certificate algorithm constraints - `check.crypto.eidas` — eIDAS compliance requires specific signature algorithms on certificates - `check.crypto.hsm` — HSM may store the private key associated with the certificate - `check.compliance.attestation-signing` — attestation signing uses related key material