Skip to content

Offer the PCP provider's EK first on Windows - #14

Merged
joshdrake merged 4 commits into
surrogatefrom
fix/windows-provider-ek
Oct 19, 2026
Merged

joshdrake merged 4 commits into
surrogatefrom
fix/windows-provider-ek

Conversation

@joshdrake

@joshdrake joshdrake commented Oct 16, 2026 •

Copy link
Copy Markdown

Windows credential activation fails on TPMs with more than one EK (EFF-122): the agent sends the attestation CA one EK, and the Platform Crypto Provider activates with a different one.

The PCP provider always activates with its own EK, PCP_EKPUB (the RSA 2048 EK at 0x81010001). It has no property for choosing another one, and ActivateCredential ignores the ek argument on Windows. eks() returned the EK certificates in the order the provider lists them. On ST33KTPM2XSPI firmware 9.258 that order is RSA 3072, RSA 2048, P-384, P-256, so crypto's getPreferredEK and the agent's endorsementkey.GetPreferred, which both take the first RSA EK, sent the CA the RSA 3072 key. Activation then failed with 0x80280084 (TPM_20_E_VALUE).

Changes

  • Windows eks() returns PCP_EKPUB first.
    • It is paired with the EK certificate whose public key matches it.
    • The remaining certificates follow in their original order, so tools that list every EK still see all of them.
    • If no certificate matches, PCP_EKPUB is returned without one, with the Intel EK certificate URL on Intel TPMs. That is what already happened for TPMs with no certificates at all.
    • If PCP_EKPUB can't be read, the old order is kept.
    • The ordering lives in providerEKFirst, in an untagged file, so Linux CI tests it.
  • TPM 2.0 HRESULT names in tpmErrNums. 80280084 used to print as "The operation completed successfully" and now prints as TPM_20_E_VALUE.
  • ActivateCredential reads the secret with getNCryptBufferProperty, instead of a fixed 256-byte buffer. This is adapted from 45b025d.

What this changes for devices

TPM EKs offered before EKs offered now
Single EK, certified (vTPM, Intel PTT, most dTPMs) RSA 2048 + cert Unchanged
ST33KTPM2XSPI 9.258 (all four EK profiles certified) RSA 3072 + cert first RSA 2048 + cert first, then the rest
Nuvoton NPCT75x (certs for RSA 3072 and P-384 only) RSA 3072 + cert first RSA 2048 without a cert first, then the rest
No certs (Intel) PCP_EKPUB + Intel URL Unchanged

On the two TPMs that change, the device's permanent identifier changes from the RSA 3072 EK to the RSA 2048 one. That matches what Linux already uses, and it is the only key Windows can prove it holds. In prod, none of the affected devices has ever completed an attestation, because they all failed activation.

Relation to other work

Testing

  • go test ./attest/ passes, including the new TestProviderEKFirst, which covers the ST33K, Nuvoton, single-EK, already-first and no-cert cases.
  • GOOS=windows go build ./... succeeds.
  • On a Nuvoton NPCT75x laptop (Windows 11, via smallstep/agent#1266): EKs() returns PCP_EKPUB (RSA 2048, no cert) first, then RSA 3072 and P-384 with their certificates. Credential activation run as SYSTEM succeeds with the RSA 2048 EK and the secret matches. The previous pin fails with 80280084 against the RSA 3072 EK. The new error text shows up in the non-admin failure: NCryptGetProperty returned 0,80280084 (tpm or subsystem failure: TPM_20_E_VALUE).
  • On an ST33KTPM2XSPI 9.258 device at a customer: PCP_EKPUB equals the certified RSA 2048 EK.

Windows CI only builds this package, so the Windows wiring is checked by hand.

https://claude.ai/code/session_01MQW2AdyeyzF1cwEZwvUu4c

The Platform Crypto Provider activates credentials with its own EK,
PCP_EKPUB, and has no way to select another. eks() returned the EK
certificates in the order the provider lists them, so on a TPM with
several EKs (ST33KTPM2XSPI firmware 9.258 lists RSA 3072 before RSA
2048) callers that take the first RSA EK sent the attestation CA a key
the provider could not activate with, and activation failed with
0x80280084 (TPM_RC_VALUE).

eks() now returns PCP_EKPUB first, paired with the certificate whose
public key matches it, followed by the remaining certificates. When no
certificate matches, PCP_EKPUB is returned without one, with the Intel
EK certificate URL on Intel TPMs, as it already was for TPMs with no
certificates at all. If PCP_EKPUB can't be read, the certificates are
returned in the provider's order as before.

Claude-Session: https://claude.ai/code/session_01MQW2AdyeyzF1cwEZwvUu4c
tpmErrNums only covered TPM 1.2 and TBS codes, so a TPM 2.0 failure
such as 0x80280084 (TPM_20_E_VALUE) fell through to the syscall's last
error and was reported as "The operation completed successfully". Add
the TPM_20_E_* codes from winerror.h, as listed in
golang.org/x/sys/windows.

Claude-Session: https://claude.ai/code/session_01MQW2AdyeyzF1cwEZwvUu4c
ActivateCredential read PCP_TPM12_IDACTIVATION into a fixed 256-byte
buffer. Use getNCryptBufferProperty, which sizes the buffer from the
provider first, like the other property reads. Adapted from 45b025d on
herman/rsa3072-aes256, which was exercised end to end on a Nuvoton
NPCT75x.

Claude-Session: https://claude.ai/code/session_01MQW2AdyeyzF1cwEZwvUu4c
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@hslatman

hslatman commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

CI blocked on the OpenSSL version. You can get the required changes from this: 54fd967.

Homebrew no longer provides openssl@1.1, so the macOS job failed at
install. Mirror the CI changes from herman/rsa3072-aes256 (4666655,
7126e13, 54fd967): Go 1.24.x across all jobs, golangci-lint v1.64.8,
and openssl@3.5 on macOS.
@joshdrake
joshdrake marked this pull request as ready for review October 19, 2026 17:16
@joshdrake
joshdrake merged commit ae62bc4 into surrogate Oct 19, 2026
5 of 6 checks passed
@joshdrake
joshdrake deleted the fix/windows-provider-ek branch October 19, 2026 17:42
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.

3 participants