Skip to content

Auto-regenerate expired self-signed certificates#1590

Open
lzwind wants to merge 2 commits into
OpenPrinting:masterfrom
lzwind:fix/auto-renew-expired-self-signed-cert
Open

Auto-regenerate expired self-signed certificates#1590
lzwind wants to merge 2 commits into
OpenPrinting:masterfrom
lzwind:fix/auto-renew-expired-self-signed-cert

Conversation

@lzwind

@lzwind lzwind commented Jun 2, 2026

Copy link
Copy Markdown

Summary

  • _httpTLSStart checked whether certificate files exist on disk via access() but never verified whether they have expired. When an auto-generated self-signed certificate expired, CUPS continued using it instead of creating a new one, breaking HTTPS access.
  • Added a cupsGetCredentialsExpiration() check so that expired certificates are treated as missing, triggering the existing auto-create code path.
  • Applied to both tls-openssl.c and tls-gnutls.c backends.

Fixes: #1519

Test plan

  • Create a self-signed cert, manually set its expiration to the past, and verify CUPS regenerates it on next startup
  • Verify that valid (non-expired) certificates are still used normally
  • Test with both OpenSSL and GnuTLS backends

_httpTLSStart checked whether certificate files exist on disk via
access() but never verified whether they have expired. When an
auto-generated self-signed certificate expired, CUPS continued using
it instead of creating a new one.

Add a cupsGetCredentialsExpiration() check so that expired certs are
treated as missing, triggering the existing auto-create code path.

Fixes: OpenPrinting#1519
@abubakarsabir924-cell

Copy link
Copy Markdown
Contributor

Hi @lzwind,
Just checking in — are you still working on this PR? I noticed it's been open for about a month without updates.
If you're busy or have moved on, I'd be happy to help finish it up (completing the test plan, or refining the approach if needed). Let me know either way — happy to collaborate or step back, whatever works.
Thanks!

The previous commit passed `crtfile` (a path like /etc/cups/ssl/host.crt)
to cupsGetCredentialsExpiration(), but that function expects a PEM
string, not a file path. Both backends treat the argument as PEM
content (BIO_new_mem_buf / gnutls datum over the string bytes), so a
path fails to import, the function returns 0, and
`0 > time(NULL)` is always false. As a result `have_creds` was always
false and CUPS regenerated the self-signed cert on every restart, even
when the existing cert was still valid.

Load the PEM string first via cupsCopyCredentials() (the same helper
cupsGetCredentialsTrust() uses) and pass that to
cupsGetCredentialsExpiration(). Only apply the expiration check to
CUPS-managed self-signed certs in the keystore; the Let's Encrypt / CA
cert branch keeps the pre-existing "exists => use" behavior, since
Let's Encrypt is renewed by certbot and auto-regenerating would
overwrite a real CA cert with a self-signed one.

Fixes: OpenPrinting#1519
@lzwind

lzwind commented Jul 20, 2026

Copy link
Copy Markdown
Author

Hi @abubakarsabir924-cell — thanks for the check-in, and sorry for the silence. I'm still on this and just came back to finish the test plan. While running through it I found (and fixed) a bug in the original change — details below.

Bug in the original change

cupsGetCredentialsExpiration() expects a PEM string, not a file path. The original change passed crtfile (e.g. /etc/cups/ssl/host.crt) directly:

have_creds = !access(crtfile, R_OK) && !access(keyfile, R_OK)
             && cupsGetCredentialsExpiration(crtfile) > time(NULL);

Both backends treat the argument as PEM content:

  • OpenSSL: openssl_load_x509()BIO_new_mem_buf(credentials, strlen(credentials)) (cups/tls-openssl.c)
  • GnuTLS: gnutls_import_certs()datum.data = (void*)credentials; datum.size = strlen(credentials) (cups/tls-gnutls.c)

So PEM_read_bio_X509 / gnutls_x509_crt_list_import find no PEM in the 25-byte path string, fail to import, and cupsGetCredentialsExpiration() returns 0. Since 0 > time(NULL) is always false, have_creds is always false → CUPS regenerates the self-signed cert on every restart, even when the existing cert is perfectly valid. That would have broken test-plan item 3 ("valid cert still used normally").

Fix

Load the PEM string first via cupsCopyCredentials() (the same helper used by the trust code in cupsGetCredentialsTrust()), then pass that to cupsGetCredentialsExpiration(). The expiration check now only applies to CUPS-managed self-signed certs in the keystore; the Let's Encrypt / CA-cert branch sets have_creds = true directly (Let's Encrypt has its own renewal via certbot, and CUPS auto-regenerating would overwrite a real CA cert with a self-signed one).

Both cups/tls-openssl.c and cups/tls-gnutls.c get the same change.

Test plan results

Built the OpenSSL backend (./configure --with-tls=openssl) and ran a harness that links against the real cupsGetCredentialsExpiration() from libcups.a, with two throwaway certs:

  • valid self-signed cert (notAfter=Jul 17 2036)
  • expired self-signed cert (notAfter=Jan 2 2000)

Direct API calls:

Input to cupsGetCredentialsExpiration() Return Expected
PEM string (valid cert) 2099839857 (2036-07-17) future ts ✓
PEM string (expired cert) 946742400 (2000-01-02) past ts ✓
file path string (the buggy PR call) 0 0 (reproduces the bug) ✓

Simulated have_creds logic:

Cert PR (buggy) Fixed
valid cert have_creds=0 → would regenerate (WRONG) have_creds=1 → keep ✓
expired cert have_creds=0 → regenerate ✓ (by accident) have_creds=0 → regenerate ✓
  • OpenSSL backend: expired self-signed cert is regenerated on next start
  • OpenSSL backend: valid self-signed cert is not regenerated (this was broken before the fix)
  • GnuTLS backend: same behavior — change is structurally identical to the OpenSSL one, but I couldn't compile/verify the GnuTLS backend locally (libgnutls-dev not installed here). Will confirm on a CI run if the maintainers can re-trigger it, or install the headers and re-run if requested.
  • Valid cert in the keystore is still loaded and used normally (not treated as missing)

Will push the fix as a new commit on this branch shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Auto-generated self-signed certificate does not regenerate after expiration

2 participants