github NeySlim/ultimate-ca-manager v2.254
v2.254 The Last Stand

one hour ago

2.254 is the last stand of UCM 2.x: security fixes will keep coming, new features will not. The next chapter is already being written.

What's Changed

Fixed

  • CA import: an imported CA is linked at once to the stored CA whose key signed it (CA import, certificate import, OPNsense and smart import, whatever the order of a batch), so SCEP GetCACert in chain mode serves the chain without waiting for the hourly repair. Smart import prefers that CA over a same-name one, and still links by name when no stored CA verifies the signature.
  • Kerberos: a Kerberos Microsoft CA connection pointed KRB5_KTNAME at its own keytab for the whole process, so XCEP and WSTEP could then accept with the wrong keys; it now sets its client keytab only.
  • ACME: revoke-cert with reason 8 (removeFromCRL, a delta CRL entry only) is refused with badRevocationReason, on the server and the proxy, which also answered malformed for any invalid reason.
  • Backup: a revocation record archived under a hexadecimal spelling of its serial (0x, colons) is restored in a spelling the CRL reads, and two spellings of one serial in an archive both stay, the gravest reason answering.
  • WSTEP, EST and SCEP: a renewed certificate keeps the SID security extension (KB5014754 strong mapping) of the certificate it renews when the CA signed that certificate, never one from the CSR, so it still maps to its account on a domain controller enforcing strong mapping; with the AD Connector enabled, a SID that names no account is refused.
  • Packages: the DEB installation no longer fails on a host without sudo; the unused sudoers file is no longer written and is removed from both the DEB and the RPM.

Security

  • Backup: a merge restore no longer turns a certificate or a CA revoked for good here back into a hold or a valid one, nor its revocation record into a hold.
  • Certificates: an imported certificate is now linked to a CA only when that CA's signature on it verifies, and renewal, re-enrolment and status checks only treat as issued by a CA a stored certificate it signed.
  • OCSP: a serial the CA revoked answers revoked, as on its CRL, even when a non-revoked record of that serial is stored again.
  • Certificates: a certificate imported again after its revocation and deletion comes back revoked, and a certificate its CA's revocation record names is no longer renewed in place.
  • Revocation: a certificate its CA recorded as revoked is now shown and treated as revoked everywhere, and an hourly pass marks such certificates revoked, with an audit entry.
  • Revocation: when a final revocation is recorded, on the certificate or on its CA's record, lifting a hold is refused with one code (409) and one message; otherwise it removes the hold's record in each unambiguous spelling of its serial and under every record of the CA key.
  • Revocation: revoking or suspending a certificate its CA already recorded as revoked for good answers that it is already revoked; a recorded hold can still be made final.
  • Revocation: the upgrade deletes once the suspension records that lifting a hold used to leave behind.
  • Revocation: a hold its CA recorded but the certificate does not show yet can be lifted, and the certificate details and list show the revocation the CA recorded.
  • Backup: restored certificates take the revocation their CA recorded at once, with an audit entry, and an archive from a release before 2.254 no longer brings back as holds the records a lifted hold left behind (the restore result counts them).
  • mTLS: an administrator importing a certificate for a user can no longer enrol a revoked certificate.
  • Renewal: a certificate whose signature no longer verifies with its CA's current key (CA re-keyed or replaced under the same name) is refused with a message asking for a new certificate.

📜 Recent release history (last 2 versions)

[2.253] - 2026-11-11

Fixed

  • Certificate Transparency: POST /api/v2/certificates/<id>/submit-ct always failed with a server error, as it read an attribute the certificate model does not have, and the chain it built stopped at the direct issuer; the submission now sends the leaf with its full issuer chain up to the root, or the chain stored inline with the certificate, one element per certificate (RFC 6962 §4.1). Reported by Chris by e-mail.
  • Users: deleting a user, alone or in bulk, dropped its mTLS enrolments without revoking their certificates, which stayed valid for anything else trusting the CA; each is now revoked (cessationOfOperation) and deleted as when removing the enrolment, and a certificate that cannot be revoked no longer blocks the deletion but is named in the answer and the audit log.
  • mTLS: the self-service routes that list, assign and enrol a user's own certificates relied on the username alone; a certificate recorded before the current account was created is no longer treated as its own, and an administrator can still assign it.
  • Sessions: a session is now bound to the account it was opened for, not only to its id, so it no longer outlives that account on SQLite; sessions opened before the update stay open, but a login waiting on its second factor at the update has to enter the password again.
  • Import: re-importing a certificate under a new serial updated the stored one but left its deploy bindings idle; it now counts as a renewal and queues them, while re-importing the same serial stays silent (discussion #440).

[2.252] - 2026-11-08

Added

  • LDAP: a StartTLS option switches a plain LDAP connection to TLS before the service and user binds, with the existing certificate verification; it was documented but never sent, so passwords crossed in clear (RFC 4513 §3, migration 137).
  • SAML: the Single Logout endpoint announced in the SP metadata ignored LogoutRequests. A LogoutRequest signed by the IdP, naming its Destination, issued within the clock window and not seen before, now closes the session of its NameID in that browser and gets a LogoutResponse signed with the SP RSA key, unsigned with a warning when the SP key is not RSA (SAML Profiles §4.4).
  • Deployment: a deploy target can be an HTTP API instead of an SSH host, with a URL, a method, encrypted request headers and a body template rendered from the certificate ({{cert_pem|json}}, {{fullchain_pem}}, {{cn}}, {{serial}}...), through the same delivery queue; the private key is sent only with an explicit opt-in over verified HTTPS, with an optional pasted trust anchor for a self-signed appliance (discussion #428, requested by @gjarassier, migration 131).
  • EST: the encrypted serverkeygen key can now go to an EC client certificate by key agreement (ephemeral-static ECDH, RFC 5753), and the key transport or key agreement and the content cipher follow the CSR's SMIMECapabilities in the client's order of preference, with AES-CBC when no listed content cipher is available. A list naming only key transport or agreement algorithms UCM lacks is refused with the supported ones named, as is an RSA client key under 2048 bits (RFC 7030 section 4.4.2).
  • EST: with the allow_empty_username setting of PATCH /api/v2/est/config, Basic authentication accepts an empty username and the password alone (RFC 7030 §3.2.3).
  • SCEP: a "Serve on plain HTTP" setting, off by default, also answers SCEP on the HTTP protocol port (8080 by default) for clients such as sscep that cannot use HTTPS; RFC 8894 §7.10 needs no TLS, as the CMS envelope protects the messages.

Changed

  • AD Connector: saving a base DN without DC components, which names no domain, is refused with 400; saving or testing a Global Catalog port (3268, 3269) or a server written with a port or an ldap:// scheme is refused too, and a Kerberos realm must match the base DN's in capitals exactly (RFC 4120 §6.1).
  • SSO providers: an unknown default_role is refused instead of being stored as viewer, and an empty one, "None" in the form, gives no implicit role; an existing provider whose default role is empty or unknown keeps viewer (migration 137).
  • LDAP: a new provider without SSL/TLS uses StartTLS by default, so passwords do not cross in clear (RFC 4513 §6.3.3); existing providers keep their setting.
  • ACME server: revokeCert refused an account other than the one that ordered the certificate even when it held valid authorizations for all its identifiers. Such an account may now revoke a certificate this ACME server issued, as RFC 8555 §7.6 requires; an authorization granted by auto-approval does not count.
  • ACME server: the notBefore and notAfter of a newOrder were echoed, then ignored, so the certificate got the profile's 90 days. They are now issued exactly: a window longer than the profile allows, outside the issuing CA's, already past or unreadable is refused as malformed, one a policy forbids as badCSR at finalize (RFC 8555 §7.4).
  • WSTEP: a certificate issued under a template carries that template's EKUs instead of the ones the request asks for, as on renewal and in AD CS.
  • WSTEP: a request without Key Usage gets the one its template configures, else the one of its template's kind, at issuance and renewal: digitalSignature, keyEncipherment and nonRepudiation for an emailProtection or EFS template, digitalSignature and keyEncipherment for Smartcard Logon, digitalSignature alone for clientAuth, instead of the server pair for all.
  • XCEP: the Username/Password policy endpoint announces a client-supplied subject for every template, machine templates included, since that binding carries no directory identity to build one from.
  • PostgreSQL: UCM connects with psycopg 3, which sends values apart from the statement, so a database error that quotes the statement no longer carries them. psycopg2 is no longer shipped, and a postgresql+psycopg2:// URL now uses psycopg 3.
  • Dependencies: SQLAlchemy 2.1 is accepted, now that every PostgreSQL URL reaches psycopg 3.
  • EST, SCEP and TSA share one CMS SignedData builder; no wire change.
  • EST: /csrattrs lists keyUsage, subjectAltName and extKeyUsage as bare OIDs instead of inside an extensionRequest, a form RFC 9908 §5.2 declares incorrect; the csrattrs_legacy_encoding setting of PATCH /api/v2/est/config restores the former shape.
  • EST: enrollment refusals are sent as text/plain with the reason, such as a body that is not base64 or not a PKCS#10 CSR, a CA name constraint or the rate limit, instead of an HTML page saying "Enrollment failed" or JSON (RFC 8951 §5).
  • EST: simplereenroll without a client certificate answers 403 instead of a 401 offering Basic authentication, which that route refuses (RFC 7030 §3.2.3), and /fullcmc answers 501 instead of 405 (RFC 7030 §3.2.2).
  • EST: a CSR whose challengePassword is not the tls-unique value of its TLS 1.2 connection to UCM, or that arrives over TLS 1.3 or through a proxy where none can be checked, is refused with 400 on simpleenroll, simplereenroll and serverkeygen instead of being certified unchecked (RFC 7030 §3.5).
  • SCEP: a RenewalReq signed by a certificate issued through the same endpoint is authenticated by that certificate, so its challenge password is no longer compared and a rotated secret no longer blocks renewal; a certificate from another profile or protocol still needs the endpoint's challenge.
  • SCEP: every FAILURE reply now carries a failInfoText attribute giving the reason (RFC 8894 §3.2.1.4).
  • ACME proxy: the directory no longer advertises keyChange, which the proxy refuses, and a challenge none of the requesting account's orders lists is now answered 404 instead of 403.
  • ACME proxy: a client's profile, notBefore and notAfter are now passed to the upstream CA, so a CA that does not accept them refuses the order with its own error instead of silently applying its defaults.
  • SSH: certificates are valid from 5 minutes before issuance, so a server whose clock lags accepts them at once, and the KRL version now grows with each generation instead of staying 0 (PROTOCOL.krl).
  • mTLS: the trusted CA is now the only trust anchor of the HTTPS port. A client certificate from its parent or from a sibling intermediate passed the TLS handshake, and with it EST enrollment; it is now refused (RFC 5280 §6.1).
  • Dashboard: the Recent Activity feed shows the whole audit trail only with read:audit, as the Audit Logs page does; without it, a user sees their own events.
  • TOTP: the account actions that ask for a code (confirm, disable, regenerate recovery codes) accept one time step of clock drift either way, as the login does.
  • Access Control: the API no longer lets a custom role inherit from a built-in role, which the page never offered; a role that already does keeps its parent.
  • TSA: a request carrying any extension, critical or not, is now refused with unacceptedExtension, and a malformed one, trailing bytes included, with badDataFormat instead of systemFailure (RFC 3161 §2.4.1).
  • TSA: tokens carry genTime in whole seconds with a declared accuracy of one second, and /tsa matches application/timestamp-query or application/octet-stream exactly, ignoring case, instead of any type containing timestamp-query.
  • CRL: with delta CRLs enabled, each complete CRL is now followed by a delta on it, so the shared CRL number advances by two per regeneration.
  • Approvals: a revocation request approved while its certificate is on hold now revokes it for good with the requested reason, instead of waiting for the hold to be lifted.
  • Sign CSR, EST, SCEP, WSTEP and ACME: a subject beyond the RFC 5280 bounds (CN over 64 characters, a country that is not two letters in a PrintableString) was signed as received; it is now refused with the reason, as on the issue form. An EST, SCEP, WSTEP or in-place renewal that keeps its certificate's subject is not affected; an ACME renewal is.
  • Sign CSR (single, bulk and approval): a request without Key Usage signed as client, code signing or S/MIME got none, so the key was unrestricted; it now gets its type's Key Usage, as on the issue form.
  • Certificates: on the issue form, Sign CSR, EST and WSTEP, a valid emailAddress in the subject is now also an rfc822Name in the SAN for every certificate type, not only email and combined (RFC 5280 §4.1.2.6); SCEP keeps the request's SAN as sent.
  • Certificates and ACME: a DNS name holding an underscore (my_host.example, _sip._tcp.example.com, also as the CN of a server or device certificate without SAN whose template allows DNS names) or in IP-address form is now refused, as RFC 5280 §4.2.1.6 requires. An EST, SCEP, WSTEP or in-place renewal keeps the names its certificate already carries; an ACME order does not.
  • EST, WSTEP and SCEP: every certificate was recorded as a server certificate, so an S/MIME or client one showed as "Server" in its details and in the API of a renewal approval request. Each now records the type it is signed as (EST: device; SCEP and WSTEP: from its EKUs, by one rule); certificates issued earlier keep their recorded type.

Fixed

  • Account: a wrong current password on the password change form answered 401, which logged the user out; it now answers 403 and the form shows the error.

  • AD Connector: an LDAP search that the domain controller answered with an error (busy, unavailable, time limit, a base DN it does not hold, missing read rights) read as "account not found", so WSTEP refused the requester with 400. It now counts as a directory failure, answered 500 like an unreachable DC.

  • mTLS: a static file request sent without the session cookie, as browsers fetch the favicon, opened a new certificate session and wrote a login_success audit event, several per page refresh. Static files no longer open a session, so a page load logs in once.

  • mTLS: once a session expired, opened by password or by certificate, each of the parallel calls still carrying its cookie logged in again with the client certificate, opening one session and one login_success event per call. They now share a single new session, and the web interface redirects to the login page once for a burst of 401 answers.

  • ACME proxy: every upstream CA error reached the client as a 500 serverInternal, so a rate limit, a refused name or a refused CSR looked like a proxy failure. The upstream type, status, subproblems and Retry-After now reach the client, revocation included (RFC 8555 §6.6, §6.7).

  • ACME proxy: when the DNS provider refused the TXT record, the challenge was still submitted and failed on the shared upstream account. The order now turns invalid without any upstream request, its other challenges are not submitted either, a retried order on the same authorizations is validated, and the TXT records of invalid or deactivated orders are removed.

  • ACME proxy: an upstream error on a certificate download reached the client as a 200 carrying the error body. It is now relayed as an ACME problem with its status.

  • ACME proxy: when the upstream CA refused or did not take the challenge response, the order stayed pending with its TXT records published and no later order could validate the name. The order now turns invalid for good with the upstream error, its records are removed, a retried order is validated, and a refused nonce is retried up to three times by the proxy and the ACME client (RFC 8555 §6.5, §7.1.6).

  • ACME proxy: a CSR that did not name exactly the order's identifiers reached the client as a 500 serverInternal when the upstream CA refused it as unauthorized. It is now refused with badCSR before reaching the upstream CA (RFC 8555 §7.4).

  • ACME server and proxy: proxy responses other than newNonce and revocation carried no Link: rel="index", and a successful revocation answered with an empty text/html body. Every proxy response but the directory now links it, and a revocation answers 200 with no body and no media type (RFC 8555 §7.1, §7.6).

  • ACME proxy: the directory listed the upstream profiles but profile and notAfter were never passed upstream, so a client got the default profile and validity. Both are now passed, a retried request reuses a pending order only if it carries the requested dates and the same replaces, and a profile the upstream CA does not offer is refused with invalidProfile.

  • ACME proxy: the challenge was read upstream with a POST-as-GET before the TXT record was published, which starts the validation on some CAs. It is now read through its authorization (RFC 8555 §7.5.1, erratum 5729).

  • ACME proxy: the upstream Retry-After of an order, authorization or finalization was dropped, and upstream requests carried the HTTP library's User-Agent. Both are now relayed and set (RFC 8555 §6.1, §7.1.3).

  • RFC 2136 DNS provider: a server given by host name always failed with an empty message, and a filled-in zone was overridden by the zone the system resolver saw. The name is now resolved and each of its addresses tried, the configured zone is used for the names inside it, and otherwise the zone is read from the configured server's SOA.

  • ACME client: with ARI, a certificate renewed at the very start of the suggested window, an expired certificate was still checked and any Retry-After was taken as is. It now renews at a random moment drawn once inside the window, skips expired certificates and keeps the checking interval between one minute and one day (RFC 9773 §4.2, §4.3).

  • ACME client: an HTML error page from the CA was reported as a JSON parse error, an RSA account key with an exponent other than 65537 got a padded JWK exponent, and an unquoted rel=alternate link was ignored. The status and Retry-After are now reported, the exponent is minimal (RFC 7518 §6.3.1.2) and every RFC 8288 form is read.

  • DNS providers: Cloudflare, OVH, Bunny, DNS Made Easy, IONOS, NS1, Rackspace, Scaleway, Tencent Cloud, Dynu, Porkbun, Netcup, ClouDNS and INWX followed redirects with their credentials, and a Cloudflare delete the API refused was reported as a success. Redirects are no longer followed and the refusal is reported.

  • Approval e-mails: without a Base URL or server FQDN, the link to the request followed the Host header the requester sent; the e-mail now goes without a link.

  • Password reset: the e-mailed link pointed at https://localhost:8443. It now uses the Base URL, else the server FQDN, never the request's Host; when neither is set, Forgot password is hidden and the reset is refused with a warning in the log.

  • OIDC: an ID token whose azp named another client was accepted when it had a single audience; it is now refused (OpenID Connect Core §3.1.3.7).

  • LDAP: a second factor the user had enrolled, even one Enforce 2FA required, was never asked at sign-in. LDAP sign-ins now ask for it, as local ones do.

  • Certificates: a DNS name with a space, a trailing dot, a wildcard past the first label, a lone *, an all-digit last label or an xn-- label that is no A-label was issued by the form, the API, Sign CSR, EST and SCEP. Such names are now refused with the reason, a 400 or a SCEP failure naming them (RFC 5280 §4.2.1.6), and an internationalized name is issued as its A-label instead of answering 500 (RFC 9549).

  • Sign CSR, EST and WSTEP: a request without SAN got its CN as a DNS name whatever it held (DNS:John Doe, and on a sub-CA too), and a non-ASCII CN answered 500. Only a CN in DNS name syntax now gives one, a single label only on a server or device certificate and never on S/MIME or a sub-CA, with a notice when a host-like server CN gives none.

  • WSTEP: a template without serverAuth signed its certificates as server ones, so a user CN (jdoe) became DNS:jdoe. A template with emailProtection or EFS now issues S/MIME certificates, one with clientAuth or Smartcard Logon (VPN Client included) client ones and one with code signing alone code-signing ones, which take no DNS name from a one-label CN.

  • Sign CSR: a refusal for a parent CA with pathLen 0, a name outside the CA name constraints or an unacceptable key answered 500 "Failed to sign CSR". It now answers 400 with the reason; an empty subject takes as CN the first SAN name that fits in 64 characters.

  • CAs: the error for a DNS name constraint with a leading dot suggested .example.com, the form it refuses.

  • Rate limiting: a 429 from a login route's own limit carried the general limit in X-RateLimit-Limit (30 instead of 5) and no Retry-After. It now names the limit that refused the request and when to retry.

  • ACME server: a body that was not JSON, a payload that was not an object or an unreadable date answered 500 serverInternal. They now answer 400 malformed (RFC 8555 §6.2, §7).

  • ACME server: a missing nonce answered malformed and a nonce outside base64url badNonce, the reverse of RFC 8555 §6.5; HEAD on newNonce answered 204 without a link to the directory. Both errors now carry the right type, and newNonce, on the server and the ACME proxy, answers 200 to HEAD and 204 to GET with Link: rel="index" (§7.2).

  • ACME server: every response stored a nonce that was never deleted, so the nonce table only grew. The 6-hourly ACME purge now also deletes expired and consumed nonces.

  • ACME server: a CSR whose common name is one of the order's IP addresses, as lego v4 and the UCM ACME client send for an IP order, was refused as badCSR. Such a CN is now matched against the IP identifiers (RFC 8738).

  • ACME server: the directory advertised the public host as caaIdentities even when CAA identifiers were configured, while finalize checked CAA against the configured ones. Both now use the same list (RFC 8555 §7.1.1).

  • ACME server: an order past its expiry, or one whose valid authorization had expired, could still be finalized. Finalize now answers 403 orderNotReady and the order becomes invalid, and an expired valid authorization reads expired (RFC 8555 §7.1.6, §7.4).

  • ACME server: an account created with External Account Binding was returned without its externalAccountBinding field. The new account object, on the server and the ACME proxy, now carries it with the value of the request (RFC 8555 §7.3.4).

  • ACME server: a POST-as-GET on a challenge started its validation, so a client reading the challenge before preparing its response, the ACME proxy included, lost the order. An empty payload now only reads the challenge, and only a response object such as {} starts the validation (RFC 8555 §6.3, §7.5.1).

  • ACME server: an order accepted with replaces did not show it in the order object, and the finalize response lacked the profile. Every order object (newOrder, read, finalize) now carries replaces, profile and the requested dates (RFC 9773 §5).

  • ACME server: tls-alpn-01 accepted a validation certificate whatever its subjectAltName. The SAN must now hold exactly the identifier being validated, one dNSName or one iPAddress (RFC 8737 §3, RFC 8738 §6).

  • WSTEP: a request with no wst:RequestType or another one than Issue was served as an issuance, a message whose wsu:Timestamp had expired was accepted, and the fault for an unreadable request did not relate to its message. Such requests now get a SOAP fault (wsse:MessageExpired past a 5 minute skew), which carries RelatesTo.

  • WSTEP: a PKCS#10 request sent under the #PKCS7 ValueType, as in the MS-WSTEP example, was refused as a malformed CMS envelope. The request format is now read from its content, whatever the ValueType says.

  • WSTEP: a plain PKCS#10 request got no CMC Full PKI Response in its answer, which MS-WSTEP requires for every request, and the one a CMC request got carried its status under a wrong OID. Every request now gets one, with CMCStatusInfoV2 under id-cmc-statusInfoV2 (RFC 5272 §6.1.1).

  • WSTEP: the template a request named (szOID_CERTIFICATE_TEMPLATE by OID, szOID_ENROLL_CERTTYPE by name) was ignored and the template chosen by EKU, so of two templates with the same EKUs the oldest always won, and a template the CA does not offer was issued anyway. The named template is now issued, an unknown, inactive or conflicting one is refused, and a request naming none is still matched by its EKUs.

  • XCEP: the policy announced no certificate template extension, so Windows CertEnroll named no template in its request and of two templates with the same EKUs the oldest was issued. Each template now announces its szOID_CERTIFICATE_TEMPLATE extension, which Windows copies into the request, and its revision is the one the issued certificate carries.

  • WSTEP and XCEP: a fault caused by the request (unknown template, EKUs the template does not issue, failed authentication, malformed message) carried the s:Receiver code, and a failure on the CA side (signing error, unreachable directory, no template configured to issue under) went out as HTTP 400. Faults now follow SOAP 1.2: s:Sender with HTTP 400 for the request's (a Kerberos requester missing from Active Directory included), s:Receiver with HTTP 500 for the server's.

  • WSTEP: a request without Extended Key Usage, such as openssl req -new with a CN alone, was issued under the built-in OCSP Signing template, and one asking for OCSP Signing got serverAuth instead. A template whose EKUs are all never issued to an enrollee is now left out of the XCEP policy, and a request or renewal under it is refused.

  • WSTEP: an AD Connector enabled without a server or base DN answered a Kerberos request with HTTP 400 "The requester was not found in the directory". It now answers s:Receiver with HTTP 500, and an account whose objectSid is missing or malformed gets its own reason.

  • WSTEP: with XCEP and WSTEP set to two different CAs, the certificate carried a template OID derived from the WSTEP CA rather than the one XCEP announced, so Windows autoenrollment did not recognize it and enrolled again at every pulse. The certificate now carries the OID XCEP announced and still renews under its template once the XCEP CA setting changes; an OID derived by another UCM instance names no template.

  • Access Control: the built-in roles showed the permissions seeded at installation (12 for Operator, which is granted 41) and Administrator's * as ignored. They now show what UCM applies to them.

  • ACME DNS-01 on DuckDNS, Hurricane Electric and FreeDNS: these providers hold one TXT value per name, so the cleanup of one order cleared the value another order was still validating. The name is now left alone while another order in progress uses it.

  • ACME DNS-01 on DuckDNS, Hurricane Electric and FreeDNS: an order for a domain and its wildcard could not hold both values and failed at validation, and the ACME proxy replaced the CA's value with its clients' own. Such an order, or on DuckDNS one naming two hosts under the same subdomain, is now refused at the request before the CA is asked, and the proxy publishes only the CA's value there.

  • ACME client: a CA refusal of replaces whose message merely contained "rateLimited" was taken for a rate limit and not retried. A rate limit is now recognised by its problem type or a 429 only (RFC 8555 §6.7).

  • WSTEP: a certificate issued or renewed under a template did not record it, so the template's usage count left it out, the template could be deleted while in use, and its policies did not apply. The matched template is now recorded at issuance, so its approval policies cover revocation and renewal and an in-place renewal applies its SAN types, policy OIDs and custom extensions; certificates issued before are left as they are.

  • EST: the encrypted serverkeygen key came in a SignedData of type data, signed by the generated key itself and without signed attributes. It now comes in an RFC 5958 key package (id-ct-KP-aKeyPackage) with content-type and message-digest attributes, signed by an EST key generator certificate that the issuing CA issues on first use, HSM CAs included (RFC 7030 section 4.4.2, RFC 5652 section 5.3).

  • EST: without a requested encryption key, serverkeygen returned a PKCS#8 encrypted with the Basic password, or a CMS over mTLS. The key now comes as unencrypted application/pkcs8 under TLS, and a request naming a key UCM does not hold, or a client certificate that does not allow key transport, is refused (RFC 7030 section 4.4.1).

  • Uploads were spooled to the system temp directory, so a restore larger than a small /tmp tmpfs (100 MB in the production compose file) failed while the form was read, and a memory-backed /tmp held the archive in RAM. Uploads are now spooled under DATA_DIR/tmp, falling back to the system temp directory only when that is not writable.

  • EST simplereenroll and WSTEP renewal: a CSR without SubjectAltName got the renewed certificate a name built from the CN, so the device lost the names it served. The renewed certificate now keeps the SubjectAltName of the one being renewed, or none when it had none (RFC 7030 §4.2.2).

  • EST: a CA label named after an operation (simpleenroll, cacerts...) was accepted and served. Such a label is now refused when saved, and ignored and left out of GET /api/v2/est/config when already stored (RFC 7030 §3.2.2).

  • SCEP: a RenewalReq signed by a certificate the CA issued was refused unless it carried the current challenge password, so a client that omitted it, as RFC 8894 §2.4 recommends, or sent the original one after a rotation could not renew. A certificate issued through the same endpoint now authenticates the renewal without the challenge.

  • SCEP: a challenge password sent in the CSR as a BMPString or UniversalString was read as UTF-8 and refused. Every DirectoryString type is now decoded by its type (RFC 2985 §5.4.1).

  • SCEP: GetNextCACert served as the rollover certificate any CA sharing the current CA's display name, even one with another subject and key, or a revoked or offline one. It now serves only a CA with the same parent and subject DN that SCEP can use (software RSA key) and expires later, and answers 404 otherwise.

  • In-app help: the SCEP URL was given as /scep, which serves the web interface, and HTTPS was said to protect the challenge password. The help now gives /scep/pkiclient.exe and the profile URLs, and says the CMS envelope protects the challenge.

  • SCEP: a request enveloped with single DES or an unknown cipher or key transport failed as badMessageCheck, and no failure said why. It now fails as badAlg, and every failure carries its reason in failInfoText, the administrator's rejection reason included (RFC 8894 §3.2.1.4, §3.3.2.2).

  • SCEP: a request refused by a policy or for its subject or names got that answer before its challenge password, Intune validation or renewal signer was checked, so a wrong password got badRequest instead of badMessageCheck and Intune never heard of the refusal. These checks now come after authentication, and Intune is notified of the failure.

  • Certificates: an S/MIME certificate whose CN is dotted like a person's name (john.doe) was checked as a DNS name against the CA's name constraints, so a CA limited to its domain refused it on the issue form, Sign CSR, WSTEP, SCEP and approval. Its CN is no longer checked as a DNS name unless the certificate also carries serverAuth.

  • ACME client: an order or preflight for a name with an all-digit last label (host.123) or an xn-- label that is no A-label went to the CA, which refuses it. Such names are now refused upfront, by the rule issued certificates follow (RFC 5280 §4.2.1.6).

  • SCEP: a POST without operation was answered with the capabilities. It is now processed as the PKIOperation it must be (RFC 8894 §4.3).

  • SSH: deleting a revoked certificate removed it from the CA's KRL, so servers checking the KRL accepted it again. A revoked certificate now stays, and its deletion is refused with 409, until it expires.

  • SSH: any critical option was signed, on host certificates too, so a typo or unknown option produced a certificate sshd rejects. Only force-command, source-address (checked as addresses or CIDR networks), verify-required and name@domain options are signed, on user certificates only (PROTOCOL.certkeys).

  • TSA: a CA certificate expired or not yet valid still signed timestamps no verifier accepts, and the dashboard showed the TSA online. /tsa now answers 503 and the dashboard shows it offline.

  • TSA: the token's messageImprint was rebuilt with NULL hash parameters, so a request sent without them got another imprint back. It is now copied from the request as sent (RFC 3161 §2.4.2).

  • TSA: rejected requests were never counted, so the failed statistic stayed at 0, and the page header showed no total. Rejections are now audited as tsa.timestamp_rejected and counted, and the header shows the total.

  • TSA: the README and in-app help promised openssl ts -verify and jarsigner with the default CA-certificate signer, which both refuse. The help now says these verifiers need a dedicated signer, and the TSA page warns while the CA certificate signs or the policy OID is still the 1.2.3.4.1 example.

  • CRL: two generations running at once, such as a revocation and the scheduler, could sign two different CRLs under the same number, and installations not upgraded through migration 004 lacked the unique index. The number is now allocated under a lock taken before the revocations are read, so a higher number always lists what a lower one did, and the index exists everywhere; migration 134 removes the older duplicates (RFC 5280 §5.2.3).

  • CRL: a CA restored on another server, or deleted and imported again, restarted its CRL numbers at 1 below those already published. The highest number of each CA name and key is now kept apart from the CA, carried by backups and never lowered by a restore, and an uploaded external CRL must carry a number above it.

  • CRL: after each revocation the delta CRL served still referred to the previous complete CRL until the next scheduled delta, hours later. A delta on the new base is now generated right after every complete CRL.

  • CRL: a revocation dated before the complete CRL but recorded after it was on neither that CRL nor its deltas. A delta now lists what its base lacks or lists under another reason (RFC 5280 §5.2.4).

  • Certificates: a certificate on hold could not be revoked for good, only released and then revoked, which made it valid in between. It can now be revoked with another reason from the Revoke button, the bulk action, ACME or an approved revocation request, its related certificate with it, the invalidity date defaulting to the start of the hold (RFC 5280 §5.2.4).

  • OCSP: the statistics gave the number of cached responses as both the requests and the cache hits, so the hit rate always read 100 %. They now count the requests answered since the service started and those served from the cache.

  • OCSP: an answer to a request with a nonce was sent with Cache-Control: no-cache, no-store, and the others with a fixed one-hour Expires that could outlive nextUpdate. Answers now follow RFC 5019 §6.2: no no-cache or no-store, Expires at nextUpdate, max-age never past it, Last-Modified and ETag.

  • OCSP: a POST whose Content-Type was application/ocsp-request in another case was answered malformedRequest. Media types are now compared without case (RFC 9110 §8.3.1).

  • ACME proxy: an order whose authorization the client deactivated was served valid, with its certificate, once another account's order completed the shared upstream order. It now stays invalid without a certificate, and that certificate is served only to an order that may issue (RFC 8555 §7.5.2).

  • ACME server and proxy: a URL or method no ACME route serves, a GET on a POST-only resource included, got a generic 404 without the directory link. It now gets a malformed problem document linking the directory, as 405 with Allow when another method is served (RFC 8555 §6.3, §7.1).

  • ACME proxy: a TXT record whose deferred deletion a restart interrupted stayed published for good when no client read its failed order again. The 15-minute order housekeeping task now deletes it, unless another pending order still needs the value.

  • ACME proxy: renewalInfo was computed locally, so a revocation or an incident at the upstream CA never brought renewals forward. The window of the upstream CA is now relayed with its Retry-After when its directory announces renewalInfo, the local one served otherwise (RFC 9773).

  • ACME server: renewalInfo read the revocation of one stored row of a certificate, so a revoked imported copy still got the normal window. A revocation recorded on any of its rows now gives a window in the past (RFC 9773).

Security

  • Database: on a PostgreSQL server whose messages are not in English, an error still carried the refused row of a NOT NULL or CHECK violation, or the refused input of a data error, private keys included. The message is now rebuilt from the error's fields whatever the server language, and a data error keeps only its SQLSTATE.
  • SSO: with role mapping rules set, a user no rule matched still signed in with the default role, viewer unless changed. Such a user is now refused at every SSO sign-in, with an audit entry; without rules the default role still applies.
  • SSO: an OIDC, SAML or LDAP identity whose username, or e-mail local part, matched an existing account signed into it, the local administrator included, and rebound that account to itself. A name or e-mail now matches only an account of the same provider not yet bound to an identity, never by an e-mail the OIDC IdP marks unverified; a local account or one bound to another identity is refused at sign-in, with an audit entry (OpenID Connect Core §5.7).
  • SSO: an identity that older releases bound to a local account of the same name kept that binding through Link to SSO, so it opened the account and the real user was refused. Link to SSO and migration 137 now drop it, and the next sign-in binds the account.
  • Remote syslog: only seven resource types were forwarded, so audit purges, API keys, roles, settings, policies and SSO login failures never reached the collector, and the ACME box matched almost no ACME event. Every audit event is now forwarded unless its category is unchecked, ACME covers every ACME event, and entries written inside a transaction, the audit purge included, leave once committed.
  • Remote syslog: narrowing the forwarded categories or turning forwarding off was recorded under System once the change applied, so the collector never heard of it, and unchecking Users also muted role, password and MFA changes. The change now reaches both the previous and the new collector, and account changes and the System events that touch keys, encryption, restores, the database or update settings are always forwarded.
  • Audit log on PostgreSQL: an entry whose User-Agent, resource id list, resource name or username was longer than its column, or held a NUL character, was dropped while the action went through, so a client could hide its own actions behind a long User-Agent. These columns are now TEXT, every field is cut to a bounded length and its NUL characters escaped before it is sealed, and an entry that still cannot be written is logged as an error and sent to syslog.
  • Certificate policies: a DNS pattern written *.company.com, as the form suggests, covered only that literal name, so approval and the policy's rules skipped www.company.com, while company.com also caught evilcompany.com. Patterns now match by DNS label, ignoring case and a final dot: *.company.com covers the names under company.com, company.com the domain as well (RFC 4343).
  • WSTEP: on the Kerberos binding, a machine template, a "Build subject from Active Directory" template or a request matching no template kept the subject and SAN the request named, and a template keeping them issued the EKUs the request asked for, so any domain account could obtain a server certificate for any name. The subject and SAN now come from Active Directory unless the matched template lets the client supply them, that template issues only its own EKUs, and a machine template refuses user accounts and an AD-derived template refuses computers.
  • WSTEP: on the Kerberos binding with the AD Connector enabled, a principal of another realm, one naming no account, or a base DN naming no realm, such as one written with spaces (DC=corp, DC=example), skipped the SID lookup, so the certificate was issued without the SID extension (KB5014754). Such a request is now refused, 400 for the principal and 500 for the connector setting, and a spaced base DN is read as written.
  • AD Connector: an account name found twice, as on a Global Catalog that also searches child domains, or an Enroll ACL group name shared by several groups, resolved to the first entry, so a computer account named after a child-domain controller got that controller's SID and DNS name. Both are now refused with 500.
  • TOTP: a code already accepted was accepted again within its window, at a second login or to disable 2FA. Each code is now accepted once and an older one after it is refused (RFC 6238 §5.2, migration 135).
  • Sessions: a login kept the session identifier presented before it, so an identifier planted in a browser became the victim's session. Every login, SSO and LDAP included, now issues a new identifier and drops the old one (RFC 6265 §8.4).
  • Sessions: changing or resetting a password, an admin 2FA reset and "revoke all sessions" left the account's other sessions signed in. They now end them; the session the user acted from stays, under a new identifier.
  • API keys: a key could create a key that never expires, and regenerating a revoked or expired key brought it back for a year. A key created or regenerated with an API key now expires no later than that key, regenerating keeps the original lifetime, and a dead key answers 409.
  • Access log: a password reset token and an OAuth code or state were written in clear with the request line and the Referer. Their values are now replaced by [REDACTED].
  • Login: a locked account answered 429 before its password was checked, an unknown name 401, so the status told which accounts exist. A locked account now gets the same 401 "Invalid credentials" at the password and LDAP doors, and the refusal is audited.
  • Dashboard: the Recent Activity feed returned the latest audit entries, other accounts' actions and names typed at a failed login included, to any signed-in account.
  • mTLS behind a proxy: a certificate presented by its serial number alone, without X-SSL-Client-I-DN or X-SSL-Client-Cert, opened the enrolment bearing that serial, even one of another CA. Such a serial now opens only an enrolment that recorded no issuer, and enrolling a presented certificate requires its issuer (RFC 5280 §4.1.2.2).
  • EST: a client certificate that another CA of this server had revoked, even as compromised, still authenticated /simpleenroll and /serverkeygen and obtained certificates for any name. It is now refused once it, or a CA above it, is revoked, or once it has expired, whichever CA of the server issued it, and /api/v2/mtls/enroll no longer enrols such a certificate (RFC 7030 §3.3.2, RFC 5280 §6.1.3).
  • EST: a CSR whose key was revoked as compromised was certified again. It is now refused with 400, as on the ACME client.
  • mTLS: a certificate issued under a sub-CA whose parent was revoked still opened a session and could be enrolled through /api/v2/mtls/enroll-import. Both are now refused, as on /api/v2/mtls/enroll (RFC 5280 §6.1.3).
  • Private keys: no installation method created a master key, so CA, certificate and SSH CA private keys and stored secrets sat unencrypted in the database until encryption was enabled by hand. UCM now creates /etc/ucm/master.key (0600) at its first start and encrypts what is already stored, and the Dashboard warns while no key is loaded.
  • Private keys: in a container whose /etc/ucm is not a volume of its own (anonymous, tmpfs or emptyDir), Settings › Security now refuses to enable encryption (409) rather than write a key the next recreation would lose. Once a master key is created at start, the plaintext key copies under data/private are removed.

Upgrade notes

  • SSO: a provider with role mapping rules now refuses the users none of them matches; add a rule for a group every allowed user is in, or remove the rules to fall back to the default role.
  • SSO: a local account is no longer merged into an SSO identity of the same name at sign-in; an administrator links it with Link to SSO first. An account also belongs to one provider: a user who signed in through two providers under the same name (LDAP and OIDC, for example) is refused on the second one.
  • SSO: an identity whose stable identifier changes is refused instead of being linked again, for example an LDAP entry that moves in a directory without entryUUID or objectGUID, a change of the LDAP unique ID attribute, or a SAML NameID in emailAddress or unspecified format that changes; use Unlink then Link to SSO on the account.
  • SSO: an SSO account that a same-named identity of another provider signed into before this release stays bound to that identity, since nothing records the original one; look for sign-ins that rebound a provider in the audit log and use Unlink SSO then Link to SSO on each such account. Local accounts are unbound by migration 137.
  • SSO: the users of a provider deleted before this release are not taken back by a new provider; use Unlink then Link to SSO on each account. After this release, a provider created again for the same IdP takes them back.
  • Remote syslog: the collector now receives every audit event type, so expect more volume; unchecking a category still stops its own events. Migration 133 turns four audit_logs columns into TEXT on PostgreSQL without rewriting the table.
  • Remote syslog: the Users box now mutes sign-ins and logouts only, the System box no longer mutes key, encryption, restore, database and update settings changes, and syslog_config_updated is recorded with resource type settings and the previous configuration in its details; leaving every box unchecked is saved as every box.
  • Certificate policies: a *.x DNS pattern now applies to the names under x, so requests issued directly until now may wait for approval or meet the policy's rules; an x pattern no longer applies to names such as evilx or x.other.org. A pattern holding a comma or a space, which matched nothing, is now refused when the policy is saved: create one policy per domain.
  • WSTEP Kerberos binding: a request naming its own subject under a machine or "Build subject from Active Directory" template, or under no template, now gets the subject from Active Directory, and is refused when the AD Connector cannot resolve the requester. A script that chooses the subject must use a template whose subject the client supplies, which must configure its EKUs, or the Username/Password binding. A user account requesting a Web Server, VPN or OCSP Signing template over Kerberos (certreq from a user session) is now refused, and must use such a template or that binding.
  • WSTEP: a certificate 2.251 issued to a request without Extended Key Usage carries the OCSP Signing and serverAuth EKUs of the built-in OCSP Signing template; its renewal is now refused and autoenrollment leaves it in place, so enroll such clients again under another template rather than renewing it.
  • Sessions: migration 135 adds users.totp_last_step and users.session_version; sessions open at the upgrade stay valid. A script that signs in with a TOTP code must not reuse a code within its 30 s step.
  • Private keys: an installation that never enabled encryption creates /etc/ucm/master.key at its next start (in a container, only when /etc/ucm is a named volume or a host directory) and removes the plaintext copies under data/private, so back that file up and give any service that reads a copy a deployment target; a container without such a volume gets no key, and enabling encryption there is refused until /etc/ucm is mounted; UCM_AUTO_MASTER_KEY=false, or disabling encryption in Settings › Security, keeps the previous behaviour. Every start with a key now encrypts the values still stored unencrypted, and a key that does not decrypt the stored ones enters safe mode.
  • mTLS behind a proxy: a proxy that forwards the serial number without X-SSL-Client-I-DN (nginx $ssl_client_i_dn, Apache SSL_CLIENT_I_DN) or X-SSL-Client-Cert must now forward one of them, or certificate login fails with a warning in the log; the I-DN is read in RFC 4514, RFC 1779, OpenSSL one-line and legacy forms.
  • EST: a client that reads the encrypted serverkeygen answer must take the key from the RFC 5958 package and verify it with the EST key generator certificate the SignedData carries, which chains to the issuing CA from /cacerts and is listed, and revoked when needed, with the CA's certificates like a delegated OCSP responder; the next encrypted request then gets a new one. Migration 132 adds the est_signer_certs table that names the current one; backups carry the certificate but not that table, so a restored installation issues a new one on first use.
  • mTLS: a client whose certificate comes from another CA of the trusted CA's hierarchy, an EST client re-enrolling with a certificate from a sibling CA included, is now refused at the TLS handshake; choose their common parent as trusted CA to keep accepting it.
  • EST: serverkeygen without AsymmetricDecryptKeyIdentifier now returns the key unencrypted, without the Basic password, and over mTLS the key type is chosen as over Basic, ML-DSA under an ML-DSA CA included.
  • PostgreSQL: on DEB and RPM the upgrade's pip install pulls psycopg 3, compiled against the system libpq under UCM_FIPS_BUILD=1 with the build tools it already requires; Docker images need nothing. A postgresql+psycopg2:// URL keeps working unchanged. psycopg 3 prepares a statement after five executions on a connection, so behind a transaction-mode pooler without prepared statements (older PgBouncer, some managed poolers) set UCM_PG_PREPARE_THRESHOLD=0.
  • EST: a FortiGate or other client that stops enrolling after the /csrattrs change gets the former answer back with {"csrattrs_legacy_encoding": true} on PATCH /api/v2/est/config.
  • EST and mTLS: a device or user that authenticates with a certificate revoked or expired on this server, or issued under a revoked CA, must be given a valid one.
  • EST: a client that puts a password in the CSR challengePassword keeps enrolling with {"ignore_challenge_password": true}.
  • EST: a CA label named after an operation no longer shows on the EST page and is dropped on its next save; give its clients a label under another name.
  • Certificates and ACME: rename the hosts whose DNS name holds an underscore and shorten any CN over 64 characters before their next renewal, since ACME orders (cert-manager, certbot, lego), initial EST and WSTEP enrollments, Sign CSR requests and server certificates from the issue form carrying them are now refused.
  • SCEP and Intune: a profile whose SAN DNS is built from a device name holding spaces, apostrophes or underscores (such as {{DeviceName}} on iOS) must use a DNS-safe value before the upgrade, since each enrollment carrying it, Intune renewals included, is now refused with the reason.
  • WSTEP: with the AD Connector enabled, users and computers of a trusted realm other than the connector's domain are now refused on the Kerberos binding instead of getting a certificate without SID; they can use the Username/Password binding, or a UCM whose connector points at their own domain.
  • AD Connector: name an Enroll ACL group whose cn other groups share by its DN, and move a connector saved on a Global Catalog port (3268, 3269), or with a port in a server name, to 389 or 636 of a domain controller.

Full history: CHANGELOG.md


Installation

Docker (Recommended)

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

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

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

Debian/Ubuntu

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

Fedora

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

RHEL, AlmaLinux, Rocky Linux 9

wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.254/ucm-el9-2.254-1.el9.noarch.rpm
sudo dnf install ./ucm-el9-2.254-1.el9.noarch.rpm

FIPS mode (preview)

FIPS mode is a preview: feedback is welcome on #399. Full guide: FIPS Mode.

Docker, any Linux host (amd64, arm64):

docker run -d -p 8443:8443 -e UCM_FIPS=1 \
  -v ucm-data:/opt/ucm/data -v ucm-config:/etc/ucm \
  --name ucm neyslim/ultimate-ca-manager:2.254-fips

Ubuntu 24.04 with Ubuntu Pro (sudo pro enable fips-updates, then reboot):

sudo apt-get install -y gcc pkg-config python3-dev python3-venv libpq-dev libxml2-dev libxslt1-dev \
    zlib1g-dev libssl-dev libxmlsec1-dev libxmlsec1t64-openssl rustc-1.91 cargo-1.91
sudo install -d -m 750 /etc/ucm && echo 'UCM_FIPS_BUILD=1' | sudo tee /etc/ucm/ucm.env
sudo apt install ./ucm_2.254_all.deb

RHEL, AlmaLinux, Rocky Linux 9 (sudo fips-mode-setup --enable, then reboot; on RHEL, enable CodeReady Builder instead of CRB):

sudo dnf config-manager --set-enabled crb
sudo dnf install -y python3.12-devel gcc pkgconf-pkg-config libpq-devel libxml2-devel libxslt-devel \
    openssl-devel xmlsec1-devel xmlsec1-openssl libtool-ltdl-devel rust cargo
sudo install -d -m 750 /etc/ucm && echo 'UCM_FIPS_BUILD=1' | sudo tee /etc/ucm/ucm.env
sudo dnf install ./ucm-el9-2.254-1.el9.noarch.rpm

The package builds its crypto libraries against the system OpenSSL (about five minutes), sets UCM_FIPS=1 on a host in FIPS mode and prints a readiness report. Fedora, Debian and other distributions are not supported in FIPS mode: use the Docker image.

Silent/Automated Install

# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.254_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.