Skip to content

Windows MDM client certificate is backdated by the renewal period, halving its lifetime and opening the renewal window at issuance #52601

Description

@getvictor

Fleet versions


💥 Actual behavior

Fleet backdates every Windows MDM client certificate by the full renewal period, so the certificate is issued already 180 days old and expires 185 days after enrollment instead of the 365 days Fleet advertises.

populateClientCert computes NotBefore by subtracting the renewal period from the current time, then adds a hardcoded 365 days to get NotAfter:

certRenewalPeriodInSecsInt, err := strconv.Atoi(syncml.PolicyCertRenewalPeriodInSecs)  // 15552000s = 180 days
notBeforeDuration := time.Now().Add(time.Duration(certRenewalPeriodInSecsInt) * -time.Second)  // now - 180d
yearDuration := 365 * 24 * time.Hour
NotBefore: notBeforeDuration,                    // now - 180d
NotAfter:  notBeforeDuration.Add(yearDuration),  // now + 185d

Three separate problems fall out of this.

The certificate lives half as long as advertised. Fleet tells Windows one thing and issues another.

The renewal window is already open when the certificate is installed. The certificate is renewal-eligible from the instant it is issued, 180 days before the device even enrolled. Windows then retries every RetryInterval = 4 days for the entire life of the enrollment. This is why the EnrollmentState = 3 symptom in #50611 shows up on freshly enrolled hosts rather than only on hosts approaching expiry.

RenewPeriod and the advertised renewal period disagree. GetPolicies advertises RenewalPeriodSeconds = 15552000 (180 days) while the provisioning doc sends RenewPeriod = 365 days. The backdating has to be fixed for the renewal window to land anywhere sensible.

🛠️ Expected behavior

Fleet should issue a certificate whose validity matches what it advertises, and whose renewal window opens near the end of that validity rather than at the start.

  1. Set NotBefore to the current time. If a clock-skew allowance is wanted, use minutes or a few hours, not the 180 day renewal period.
  2. Derive NotAfter from syncml.PolicyCertValidityPeriodInSecs (the same constant Fleet advertises in GetPolicies) instead of the hardcoded yearDuration, so the advertised policy and the issued certificate cannot drift apart.
  3. Make the provisioning doc's RenewPeriod agree with PolicyCertRenewalPeriodInSecs, so a 365 day certificate with a 180 day renewal period opens its renewal window at day 185 rather than at day 0.

This bug is independent of the ROBO question. #51468 set WstepROBOSupport = "false" to stop Windows from attempting a renewal Fleet cannot service, and #52492 tracks the fact that Windows attempts renewal anyway. Even with ROBO fully implemented, the certificate lifetime and the renewal window would still be wrong.

🧑‍💻 Steps to reproduce

These steps:

  • Have been confirmed to consistently lead to reproduction in multiple Fleet instances.
  • Describe the workflow that led to the error, but have not yet been reproduced in multiple Fleet instances.
  1. Turn on Windows MDM on a Fleet server and enroll a Windows 10 or 11 host.
  2. On the host, open certlm.msc and find the Fleet-issued client certificate in Personal > Certificates (issued by the Fleet WSTEP identity CA, with OU = Fleet). Alternatively run Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Issuer -like '*Fleet*' } | Select-Object Subject, NotBefore, NotAfter.
  3. Observe that NotBefore is roughly 180 days in the past, dated well before the host enrolled, and that NotAfter is roughly 185 days in the future rather than 365 days.
  4. Read the provisioning doc values Fleet sent by checking HKLM\SOFTWARE\Microsoft\Enrollments\<GUID> for the enrollment with ProviderID = Fleet, and confirm the WSTEP renew settings show a 365 day RenewPeriod against a certificate with 185 days of remaining life.
  5. Confirm that Windows treats the certificate as renewal-eligible immediately: the scheduled task "Schedule created by enrollment client for renewal of certificate warning" under \Microsoft\Windows\EnterpriseMgmt\<GUID> runs and attempts renewal within the first RetryInterval window rather than waiting until the certificate is near expiry.

🕯️ More info (optional)

The enrollment client logs its renewal schedule as a single line, event ID 3004 in Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Enrollment:

(Get-WinEvent -FilterHashtable @{
   LogName = "Microsoft-Windows-DeviceManagement-Enterprise-Diagnostics-Provider/Enrollment"; Id = 3004
 } -MaxEvents 1 -ErrorAction SilentlyContinue).Message -replace "\s+"," "

On a freshly enrolled host this prints:

[MDM Schedule Enrollment Cert Renew Session Start] EnrollmentId: 6AAC703B-..., Renew period: 180; Renew retry interval: 4; Robo mode: 1; Cert Expiration: 2027-09-08T15:02:42.00

The same line on pre-fix code:

[MDM Schedule Enrollment Cert Renew Session Start] EnrollmentId: B77E051E-..., Renew period: 365; Renew retry interval: 4; Robo mode: 1; Cert Expiration: 2027-03-12T13:52:51.00

Activity

  1. added
    bugSomething isn't working as documented
    #g-power-to-pcPower to the PC working group
    on Sep 4, 2026
  2. moved this from 📨 Inbox to 🥚 Ready in ⚡ #g-power-to-pcon Sep 4, 2026
  3. self-assigned this
    on Sep 8, 2026
  4. moved this from 🥚 Ready to 🐣 In progress in ⚡ #g-power-to-pcon Sep 8, 2026
  5. moved this from 🐣 In progress to 🦤 ‎In review in ⚡ #g-power-to-pcon Sep 8, 2026
  6. added a commit that references this issue on Sep 9, 2026
    cad8366
  7. moved this from 🦤 ‎In review to ✔️Awaiting QA in ⚡ #g-power-to-pcon Sep 9, 2026
  8. added this to the 4.93.0 milestone on Sep 9, 2026
  9. added
    ~assisting qaThis issue can be QA'd by anyone outside the QA team when capacity allows
    on Sep 22, 2026
  10. thisisjoegrant commented on Oct 1, 2026

    @thisisjoegrant
    Member

    Confirmed cert issued today is now valid from roughly [Today - 1 day] to [Today + 1 year]

  11. fleet-release commented on Oct 8, 2026

    @fleet-release
    Contributor

    Certificates backdated
    now renewed when just issued
    fix the time to heal

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

Metadata

Metadata

Assignees

Labels

#g-power-to-pcPower to the PC working groupbugSomething isn't working as documented~assisting qaThis issue can be QA'd by anyone outside the QA team when capacity allows

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions