github NeySlim/ultimate-ca-manager v2.228

3 hours ago

What's Changed

Security

  • A certificate request signed through ACME, EST, WSTEP or the Sign CSR action could carry extensions only the issuer may set: its own CRL Distribution Points, Authority Information Access and Certificate Policies replaced the CA's, and a Microsoft SID security extension chosen by the requester was copied onto the certificate (the strong certificate mapping bypass KB5014754 closes). The shared signing path now takes only Subject Alternative Name, Key Usage, Extended Key Usage, Basic Constraints, TLS Feature and OCSP No Check from a leaf request and sets everything else itself, as the SCEP path already did with a stricter list; sub-CA requests signed by an operator are unchanged. A request that already carries the Microsoft template extension or a Must-Staple flag no longer fails with a duplicate extension; Key Usage on a leaf is always critical and Subject Alternative Name is critical when the subject is empty (RFC 5280)
  • Any authenticated user, the read-only viewer included, could download another record's private key through the mTLS PKCS#12 export: the export resolved the certificate by serial number alone, and enrolling a PEM never checked whose certificate it was. Every route that joins an enrolment to a certificate (the mTLS account export, and the user certificate detail, export, revoke and delete routes) now resolves the certificate that was enrolled (byte-identical certificate, else its fingerprint, SHA-1 or SHA-256, else serial and issuer together, the serial read from the certificate itself). Enrolling a certificate that no CA of this server issued is refused, so a serial number cannot be squatted, and the certificate login refuses a certificate whose fingerprint differs from the enrolled one. Importing a user certificate whose key UCM holds is refused unless the caller created that record or is an administrator; assigning an existing certificate, and the list of certificates available for assignment, are limited to one's own records for non-administrators as documented; the mTLS routes validate the validity period strictly, fall back to the configured mTLS CA as intended, and look the CA up by id or reference without a type error on PostgreSQL
  • The approval workflow could be skipped: an error while evaluating the issuance policies (a malformed san value in the request was enough) was logged as non-blocking and the certificate was issued without approval. Policy evaluation now fails closed, and a san value that is not a list of names is refused
  • Every issuance path now refuses to sign outside the issuing CA certificate's own validity window and never issues a certificate expiring after the CA's. Signing a request through EST, WSTEP, ACME or the Sign CSR action did not refuse an expired CA certificate and could issue a certificate outliving it; the user mTLS certificate path ignored a CA taken offline and turned an expired CA into the issuer of a one-day certificate; and no path refused a CA certificate that is not yet valid. The same rule now applies to the issue form (checked before a request could be queued for approval), the approval workflow, SCEP, renewal, sub-CA creation, the timestamp signer and the OCSP responder renewal, with the reason reported instead of a server error
  • The Authority Key Identifier of certificates issued by the issue form, the approval workflow, SCEP, in-place renewal, the timestamp signer and the OCSP responder renewal is now the issuing CA's own Subject Key Identifier, as on the other paths (RFC 5280). It was recomputed from the CA key, so for a CA whose identifier is not the standard hash of its key (an imported CA, an HSM or AD CS generated identifier) the certificate did not chain to its CA for strict validators; a renewal no longer drops the identifier silently on error
  • Protocol renewals now authenticate with the certificate being renewed under one rule shared by EST, SCEP and WSTEP: it must be a certificate this CA issued and still holds, byte for byte, neither revoked (including a certificate revoked and then deleted) nor expired. EST re-enrolment accepted any certificate the TLS layer presented, so a revoked certificate, or one issued by another CA with the same names, was re-enrolled and even kept its extended key usages; SCEP renewal looked the signer up by decimal serial only, so a revoked certificate stored in hexadecimal, or revoked then deleted, could renew. A SCEP renewal must also keep the certificate's Subject Alternative Names, as EST and WSTEP already required. The renewed certificate's record is archived when the new one is issued, so it is no longer picked up again by automatic renewal, a renewal webhook is sent instead of an issuance one, and an EST commit failure is reported instead of being ignored
  • In-place renewal (manual, in bulk and scheduled) now produces what issuance would. It copied every extension of the previous certificate: a Key Usage the key type cannot honour (keyEncipherment on an EC key, corrected at issuance since v2.222) survived renewal, CRL Distribution Points, Authority Information Access and the CPS policy stayed frozen after the CA's URLs changed or its CRL was disabled, and embedded Certificate Transparency timestamps signed for the old certificate were carried over. These are now rebuilt from the CA's configuration, the Key Usage is constrained to the key type, the key must meet the same floor as issuance (a re-signed RSA-1024 was accepted), the Certificate Transparency policy applies, and the certificate starts with the same clock-skew allowance as issuance
  • A certificate whose private key the server does not hold (SCEP, EST, WSTEP and ACME enrolments, imports without a key) is no longer renewed in place: the renewed certificate could never reach the device, and superseding the serial the device still presents got that device refused by OCSP and the CRL. Manual and bulk renewal refuse with the reason, and the scheduler no longer selects such certificates
  • The issuing CA of a certificate whose record does not name it is resolved by verifying the signature, no longer by name: a homonymous CA after a rotation, or a local CA merely sharing the Common Name of an external issuer, could re-sign the certificate. The record is then linked to that CA and the superseded serial is recorded under it, so the previous certificate appears on the right CRL
  • Two concurrent renewals of the same certificate no longer leave a signed certificate that is neither stored nor revocable: the second is refused and asked to reload
  • SCEP issuance applies the same defaults and template rules as the other enrollment paths: a request carrying neither Key Usage nor Extended Key Usage received a certificate with neither, usable for anything; it now gets the TLS defaults. A template bound to a SCEP profile can no longer grant an EKU that hands the enrollee authority over the PKI or a logon identity of its choosing (OCSP signing, timestamping, any purpose, Smartcard Logon), the same rule ACME profiles already enforced: the binding is refused, and a binding saved before this rule never issues such an EKU. The Subject Alternative Name is marked critical when the request has an empty subject (RFC 5280)
  • Issuance paths made consistent with each other: restoring a CA key from an exported file refuses a key that is not the CA certificate's own, as the key import does; a request already holding a certificate is not re-signed in place through the AD CS connector, as the local Sign CSR action refuses it; the CA of an issue or Sign CSR request may be named by id or reference without a type error on PostgreSQL; the CA-only key usages of a template (keyCertSign, cRLSign) are dropped from templates, which describe end-entity certificates, and never land on a certificate issued from an older one; the Certificate Transparency policy (embedded SCTs, ct_required) now applies to the issue form, the approval workflow and SCEP as it did to the other paths
  • ACME: the certificate.issued webhook is sent once per issuance instead of twice; a signing failure the request is not responsible for (CA offline or outside its validity window, signing key unavailable) leaves the order retryable and is reported as serverInternal instead of invalidating it as badCSR; the fallback used when no issuing CA is configured is a CA able to sign, chosen deterministically; an order naming a certificate that a previous order already replaced is refused with alreadyReplaced (RFC 9773)
  • Two requests racing on the same object no longer both issue: signing the same CSR twice, finalizing the same ACME order twice or approving the same request twice from two workers now leaves exactly one certificate, the other request being told to reload
  • Every spelling of an extended key usage in use (the templates page writes msSmartcardLogin, the seeded template and the XCEP policy smartcardLogon) resolves to the same purpose everywhere: a Smartcard Logon template created from the templates page issued no Smartcard Logon over WSTEP, and one written for WSTEP was refused by the issue form; the WSTEP template match and the XCEP policy use the same resolver as issuance, and an unknown name in a template is ignored instead of discarding the others
  • Name constraints now cover every Common Name of a subject (only the first was checked), URI names (by host), directory names (the subject itself, as RFC 5280 prescribes) and UPN names (by domain); a constraint of one of these types used to be ignored when excluding and to refuse everything when permitting
  • Issuance policy rules (allowed key types, maximum number of DNS names, maximum validity) now bind the Sign CSR action, the bulk signing and renewal as they bind the issue form and the approval workflow
  • A certificate issued through the approval workflow now carries everything the request asked for: the subject e-mail address, URI and UPN Subject Alternative Names and OCSP Must-Staple were dropped, and it starts with the same clock-skew allowance as the issue form
  • The certificate presented over mTLS to EST /simpleenroll and /serverkeygen, when the EST CA signed it, must be one the CA still holds, as for re-enrolment: a revoked, superseded or deleted certificate could enrol a new one for any subject (a certificate from another authority the TLS layer trusts is still accepted, RFC 7030). The mTLS login refuses a revoked certificate, whatever the proxy checked, even after the certificate record was deleted. Approving a queued SCEP request from the API now issues its certificate, which the client was polling for indefinitely (with the CA defaults: the profile's template is not applied to a manually approved request), and notifies webhooks of it. A SCEP renewal announces itself as a renewal to webhooks, and a certificate presented before its validity start is refused as such
  • Enrolling a certificate for mTLS login is limited, for non-administrators, to certificates of records they created, whether or not the server holds the key: enrolling another holder's certificate mapped that holder's logins onto the enrolling account and blocked the real holder. A revoked certificate is refused, the same certificate is one enrolment whatever form its serial was stored in, and a certificate forwarded by the proxy is kept with the enrolment
  • SCEP renewal no longer carries over the purposes no enrollee may hold (OCSP signing, timestamping, any purpose, Smartcard Logon) from the previous certificate; a template bound to a SCEP profile validated by Intune may still issue Smartcard Logon, Intune vouching for the requester's identity; WSTEP templates are capped the same way, Smartcard Logon excepted
  • Auto-renewal covers the certificates the server can renew: the default sources now include certificates issued from the form and signed requests, and the settings page offers that source. The CA that really signed a renewed certificate gets the CRL carrying the superseded serial, and among several CA records for one key the one the certificate names is chosen
  • Smaller consistency fixes from the review: the renewed OCSP responder record carries the identifier of its new certificate; an expired CA is skipped by the responder renewal job instead of being counted as a failure at every run; the timestamp signer refuses a CA outside its validity window before loading its key; a TLS Feature copied from a request is never marked critical (RFC 7633); the sub-CA creation names the parent in its refusal
  • An issuance policy that requires approval now binds signing a stored request (alone or in bulk) and renewing a certificate: they are queued for approval like the issue form, and approving them performs the signing or the renewal. Administrators bypass as before; a renewal no renewal could honour is refused at once rather than queued; the bulk operations report the requests they queued; the Approvals page shows what a request is about (type, target, validity, extra key usages). A queued request whose target is signed or renewed directly meanwhile is closed as approved by that action and linked to the certificate; one whose target is deleted or revoked is closed as rejected; approving a request already satisfied closes it on the existing certificate, and approving one of several requests for the same target closes the others as approved by that approver. A request completed by a certificate issued elsewhere, or signed through the Microsoft CA connector, is closed the same way. An intermediate CA signed from a stored request records its signer as creator instead of "system". A hold keeps the renewal request waiting for the unhold; a request past its expiry is closed as expired, never as approved, and stale requests are expired hourly and never listed as pending; the renewal scheduler leaves a certificate whose renewal awaits approval to that decision, as long as it can come before the certificate expires; an expired request no longer counts as pending anywhere (badge, user deletion), approving a request whose target is gone closes it, and a request closed that way or by the deletion or revocation of its target notifies webhooks as for a rejection, a request restored without an expiry gets the standard seven-day lifetime, and a renewal reads the certificate again before deciding, so a revocation committed meanwhile is honoured and a certificate renewed meanwhile is not renewed a second time; a certificate deleted during the renewal batch is skipped instead of ending the batch
  • A SCEP request records the profile it came through: approving it by hand now issues with that profile's template (validity, key usages), as auto-approval does, instead of the CA defaults; the approval is refused when the profile or its template no longer exists, a profile cannot be deleted while requests that came through it are pending, nor a template while a SCEP profile binds it, and a renewal request approved by hand archives the certificate it renews, as auto-approval does. Certificates superseded by a renewal or re-enrolment that kept the old record (SCEP, EST, WSTEP, ACME, OCSP responder) show an "Archived" badge and can be listed with the status filter
  • Development dependency vitest updated to 4.1.11: its mocker could read arbitrary files through a redirect mock (GHSA-82fw-gwwq-j7x9, test tooling only, not shipped in the application)

Changed

  • Upgrade note: auto-renewal no longer renews certificates whose private key the server does not hold (SCEP, EST, WSTEP and ACME enrolments); those devices renew through their protocol. Review the auto-renewal sources after upgrading: the new default adds certificates issued by the server, which is what the scheduler can renew
  • Upgrade note: operators signing stored requests or renewing certificates under a CA covered by an approval policy now see their action queued for approval instead of performed at once
  • Upgrade note: a template bound to a SCEP profile without Intune validation no longer issues OCSP signing, timestamping, any-purpose or Smartcard Logon key usages; such bindings are refused when saved and ignored at issuance

Fixed

  • An approval whose certificate issuance failed (CA offline, HSM unreachable, policy rule violated) was still marked approved, with no certificate and no way to approve it again. The request now stays pending with the reason reported to the approver, who can approve again once the cause is dealt with; a failure to record the issued certificate is reported as such instead of corrupting the response

📜 Recent release history (last 2 versions)

[2.227] - 2026-09-10

Added

  • A CA that holds only its certificate, as one created by signing an external CA request (a Windows sub-CA signed by a UCM root), is flagged as such in the list and its details, and its private key can be imported afterwards (POST /api/v2/cas/<id>/key, key checked against the certificate) so it can sign; taking it offline is refused until then (#348, reported by @j2r2k2)

Fixed

  • CA revocation, reviewed after v2.226: a CA under a revoked ancestor is shown as revoked upstream, with signing actions withheld, and the pickers that name a signing CA (issue form, CSR signing, sub-CA parent, ACME, SCEP, EST, XCEP/WSTEP, TSA, policies) offer only CAs that can sign, the corresponding settings refusing any other; certificateHold is refused for a CA, since a CA revocation is permanent; a revoked CA is not taken offline; a CA whose row lost its revoked flag (older backup, import before its parent) still reads as revoked from the parent's record, a second revocation keeps the original date, and the parent's CRL lists a revoked child CA even without that record; a CA under a revoked ancestor can still install a certificate from a healthy issuer or renew its request; the SCEP dashboard tile follows the chain; the status badge is translated and a Revoked counter appears on the CA page; the certificate list stays on an existing page after the last row of the last page goes away and ignores a reload that a later filter change overtook; revocation warnings are translated (follow-up to #343)
  • Delegated OCSP responders now sign the responses. No issuance path emitted id-pkix-ocsp-nocheck, so the configured responder was refused at answer time and the CA signed with its own identity; every path now emits it, renewals included (#347, reported by @j2r2k2)
  • One rule for a delegated responder, shared by assignment, the eligibility list and the responder: issued and signed by the CA, valid, not revoked, OCSPSigning EKU, Key Usage with digitalSignature, nocheck, and a readable private key that is the certificate's own. A refusal names the reason, also in the responder settings (#347)
  • Responder changes take effect at once: assigning, removing, revoking, renewing or deleting a responder drops the CA's cached answers, and an answer never claims to be current beyond the responder's own expiry (#347)
  • Responders with Ed25519, Ed448 or DSA keys sign on both response paths; Ed25519 and Ed448 keys can be imported or uploaded (stored as PKCS#8); an Ed25519 or Ed448 certificate keeps its key type when renewed (#347)
  • Imports refuse a private key that is not the certificate's. Re-importing an existing certificate or CA without a key drops a stored key that is not the new certificate's and says so; an unreadable stored key, or an HSM key that cannot be reached, refuses the update and leaves the record untouched (#347)
  • Re-import targets the right record among homonyms: the one holding the certificate's key, told apart by the issuer's key identifier or signature when a CA is cross-signed; a certificate that matches none of several homonyms is refused. A CA waiting for its certificate, or holding a renewal request, is completed through the certificate upload path (#347)
  • A renewed certificate imported onto a revoked record is no longer published as revoked: the revoked serial stays on the list and the record follows its new certificate. Both import routes now keep a CA's serial, subject key identifier, issuing CA and path length in step with the certificate, and apply a revocation the parent still holds (#347)
  • A CA certificate answering an intermediate request pending in the certificate list is created with that request's key; a foreign CA that merely shares the subject of a pending CA becomes a new CA. CAs with Ed25519 or Ed448 keys are refused at import with the reason, since they could sign neither certificates nor CRLs (#347)
  • Signatures made with HSM-resident RSA-3072, RSA-4096, P-384 and P-521 keys verify again: the providers hashed with a digest chosen from the key size while the signature named SHA-256. The signature now names the digest the provider signs with, on every signing path (certificates, CRLs, OCSP, timestamps) (#347)
  • An HSM-backed CA without a delegated responder answered every single-certificate OCSP request with an internal error; a sub-CA whose record no longer points at its real issuer is still found by signature, so its OCSP status is good rather than unknown; a serial lookup is verified against the stored certificate, the serial column mixing decimal and hexadecimal forms (#347)
  • A request issued through the approval workflow yields the same certificate as the same request issued directly, extra extended key usages included, and the approval is linked to the certificate it produced (#347)
  • Backups: restoring a backup written before the CA revocation fields existed no longer un-revokes a CA; a CA's offline state, validity dates, origin and CRL, OCSP, AIA and CPS settings are restored; a certificate's invalidity date is exported and restored; a revocation record for an unknown CA is skipped instead of aborting the restore on PostgreSQL; a wrong restore password is reported as such (#347)
  • A backup no longer loses revocations: CA revocation state and the persistent revocation list are exported and restored, and a restore reports how many records it brought back (follow-up to #343)
  • Restoring a backup that contains a CA still awaiting its externally signed certificate no longer aborts the whole restore; its pending request travels with the backup (follow-up to #343)
  • Revoking a CA publishes the certificate it currently holds, under the CA that really signed it, after a renewal or a cross-signature as well; DSA-signed CAs, self-issued sub-CAs and looping or over-deep chains no longer bypass the signing block on a revoked chain (follow-up to #343)
  • The certificate list, its counters, the dashboard and the Prometheus metrics count the same records: a pending request is not a certificate, a record with an empty certificate or request is read the same way everywhere, a certificate without validity dates counts as valid, and the Orphan filter is computed by the server over every certificate (follow-up to #345)
  • The certificate list filters form the same buckets as the counters, valid, expiring, expired and revoked never overlapping, and the list shows what the server selected (follow-up to #345)
  • An on-demand backup password is refused with the exact reason, the dialog applies the same rule (at least 12 characters, 8 distinct, 6 distinct from 16) and counts characters the way the server does; the same rule applies to the settings backup route and the scheduled backup password, and a failed scheduled backup is recorded in the audit trail (#346, reported by @kiar1404-de)

Security

  • Private keys created by the certificate issue form and by the Smart Import are now encrypted at rest like every other path; the HSM public key lookup no longer commits the caller's transaction (#347)

[2.226] - 2026-09-09

Added

  • Intermediate CA revocation: Revoke CA on an intermediate (POST /api/v2/cas/<id>/revoke, write:cas) with the RFC 5280 reasons and optional invalidity date. The serial goes on the parent's persistent revocation list, the parent's CRL is regenerated and its OCSP responder answers revoked. The revoked CA and everything below it can no longer sign (issue form, CSRs, renewals, sub-CAs, ACME, EST, SCEP, WSTEP, TSA, OCSP responders); its own CRL keeps being served. The record belongs to the parent and outlives the CA row, so a deleted and re-imported CA comes back revoked. Roots and CAs with an external issuer are not revocable here. When the parent cannot regenerate its CRL, the response says so. New Revoked status and filter, audit action ca_revoked (#343, reported by @j2r2k2)

Fixed

  • An active filter survives an action performed from a certificate's detail window, on the certificates and SSH certificates pages; the Orphan filter works on the first load (#345, reported by @kiar1404-de)
  • The OCSP responder answered unknown for the certificate of an intermediate CA: it now recognises the certificates of the CAs signed by the requested issuer and answers good or revoked (#344, reported by @j2r2k2)

Full history: CHANGELOG.md


Installation

Docker (Recommended)

# From Docker Hub
docker pull neyslim/ultimate-ca-manager:2.228

# Or from GitHub Container Registry
docker pull ghcr.io/neyslim/ultimate-ca-manager:2.228

# Run
docker run -d -p 8443:8443 \
  -e SECRET_KEY=$(openssl rand -hex 32) \
  --name ucm neyslim/ultimate-ca-manager:2.228

Debian/Ubuntu

wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.228/ucm_2.228_all.deb
sudo dpkg -i ucm_2.228_all.deb
sudo apt-get install -f

Fedora/RHEL

wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.228/ucm-2.228-1.fc43.noarch.rpm
sudo dnf install ./ucm-2.228-1.fc43.noarch.rpm

Silent/Automated Install

# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.228_all.deb

Default Credentials

  • Username: admin
  • Password: changeme123

Change the password immediately after first login!

Documentation

Don't miss a new ultimate-ca-manager release

NewReleases is sending notifications on new releases.