**Related issue:** N/A (internal security hardening)
## Summary
The SCEP certificate renewal flow accepted a CSR with any CommonName as
long as the requester proved possession of the old certificate's private
key. This allowed a host to obtain a certificate for a different host's
identity during renewal.
## Reproduction
Code-level verification (no running server required):
1. Read `ee/server/service/hostidentity/scep.go` `renewalMiddleware`
(lines 142-216).
2. Confirmed that after PoP signature verification (line 191), the CSR
is passed directly to `next.SignCSRContext(ctx, m)` (line 198) with no
comparison of `m.CSR.Subject.CommonName` against
`oldCertData.CommonName`.
3. Confirmed that `UpdateHostIdentityCertHostIDBySerial` (line 205) then
binds the new cert's serial to the old cert's `host_id`, completing the
identity takeover.
## Fix
Added a CN equality check after signature verification: if the CSR's CN
does not match the original certificate's CN, the renewal is rejected
with an error.
## Testing
### Unit tests (`ee/server/service/hostidentity/scep_test.go`)
Exercises `renewalMiddleware` directly with a mock datastore:
- `mismatched CN is rejected` -- constructs a renewal CSR with
`CN=attacker-identity` against an original cert with
`CN=original-host-identity`. Verifies the middleware returns an error
containing "common name does not match" and the next signer is never
called.
- `matching CN is accepted` -- constructs a renewal CSR with the same CN
as the original cert. Verifies the middleware passes through to the next
signer successfully.
### Integration tests (local, real MySQL)
Ran `TestHostIdentity` against a local MySQL 8.0.44 instance
(`MYSQL_TEST=1 REDIS_TEST=1`). All 28 subtests pass, including:
- **Certificate renewal flows** (ECC P256 orbit, ECC P384 orbit, ECC
P384 osquery) -- renewed certs preserve the original CN, host_id
binding, and work for authenticated requests.
- **Renewal replay protection** -- reusing a revoked cert's serial for
renewal is rejected.
- **Wrong cert authentication** -- cross-host cert usage is rejected (9
subtests).
- **Real SecureHW + SCEP** -- full TPM-simulated renewal flow succeeds.
- **Failure cases** -- empty/wrong challenge, oversized CN, non-ECC
algorithm all correctly rejected.
Linter passes (`make lint-go-incremental` -- 0 issues).
# Checklist for submitter
- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
## Testing
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Tightened certificate renewal checks so renewal requests now fail if
the new certificate request uses a different common name than the
existing certificate.
* Renewal requests with matching common names continue to work as
expected.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->