Skip to content

feat(shadow): KB5014754 strong-mapping fixes for shadow-cred PFX#1

Merged
ajm4n merged 4 commits into
mainfrom
feat/shadow-pkinit-strong-mapping
Apr 30, 2026
Merged

feat(shadow): KB5014754 strong-mapping fixes for shadow-cred PFX#1
ajm4n merged 4 commits into
mainfrom
feat/shadow-pkinit-strong-mapping

Conversation

@ajm4n

@ajm4n ajm4n commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • Self-signed PFX produced by certigo shadow add now passes PKINIT against KB5014754-strict KDCs (CVE-2022-26931 era).
  • Adds the four cert-side defenses: pre-dated NotBefore (2018), szOID_NTDS_CA_SECURITY_EXT with SID, SAN otherName UPN, Smart Card Logon EKU.
  • Switches both extensions to cryptobyte for clean ASN.1 nesting.
  • New lookupShadowIdentities() in cmd/certigo/shadow.go auto-pulls objectSid + userPrincipalName off the live LDAP conn so the operator doesn't have to pass them manually.

Why

Shadow-cred PFX certs minted with the legacy template fail PKINIT on patched DCs with KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED (75) or KDC_ERROR_CLIENT_NOT_TRUSTED (62). Same fixes the upstream umber relay's shadow-creds chain landed on after live-testing against a Win2016 DC.

Test plan

  • go build ./... clean
  • go test ./... — 146 passed
  • Reviewer: certigo shadow add --action add --target-dn 'CN=user,...' --out user.pfx --pfx-password shadow against a patched DC, then certipy auth -pfx user.pfx -password shadow -domain corp.local should now show both SAN UPN and Security Extension SID in the parsed identities

🤖 Generated with Claude Code

ajm4n and others added 4 commits April 29, 2026 00:00
Self-signed PFX produced by 'certigo shadow add' now passes PKINIT
against KB5014754-strict KDCs (CVE-2022-26931 era). Same fixes the
upstream umber relay's shadow-creds chain landed on after live-testing
against a Win2016 DC.

Cert-side defenses, all controlled by the caller via
shadow.Options.TargetSID / TargetUPN:

1. NotBefore pinned to 2018-01-01. DCs in compat mode
   (StrongCertificateBindingEnforcement=1, the post-patch default)
   accept pre-dated certs via legacy implicit mapping.

2. szOID_NTDS_CA_SECURITY_EXT extension (1.3.6.1.4.1.311.25.2)
   embedding the user's SID — required by DCs in strict mode (=2).

3. SAN otherName under id-ms-principalName (1.3.6.1.4.1.311.20.2.3)
   carrying the UPN. PKINIT clients (certipy, etc.) match the cert
   to a principal via this SAN.

4. Smart Card Logon EKU (1.3.6.1.4.1.311.20.2.2) — Windows KDCs
   reject PKINIT certs without it as
   KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED (75).

Both extensions are encoded with cryptobyte (clean nesting). The
encoding/asn1 struct-tag-rewrite trick produced lengths that pointed
at the wrong layer and broke parsers downstream — verified by
running certipy auth against the resulting PFX.

Caller side (cmd/certigo/shadow.go):
- new lookupShadowIdentities() that pulls objectSid + userPrincipalName
  off the live LDAP conn for the resolved targetDN, then feeds both
  into shadow.Options. Failure is non-fatal — empty SID/UPN reverts
  to the legacy pre-dated-cert path which still works on compat-mode
  DCs.

Verified: 146 tests still pass; shadow add against the lab DC writes
a cert that certipy reads cleanly with both the SAN UPN and the SID
security extension.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real GOAD targets (and most modern AD CS deployments) reject HTTP Basic
on /certsrv/ and require Negotiate/NTLM, and many lab IIS configs only
bind port 80. The existing web flow assumed HTTPS-with-Basic, which fails
on both counts.

Wraps the HTTP transport with go-ntlmssp's Negotiator (with
AllowBasicAuth=true so existing Basic-on-HTTPS targets still work) and
adds a hand-rolled NTLM round-tripper for pass-the-hash that calls
NewAuthenticateMessage with PasswordHashed=true. When the operator gives
a bare hostname (no scheme), we try HTTPS first and fall back to HTTP on
connection refused / dial errors. Domain is auto-prefixed onto the
username (DOMAIN\user) when needed for NTLM. New Options fields: NTHash
(16-byte) and Domain.

Verified against GOAD essos.local (braavos.essos.local has IIS bound on
:80 only): daenerys.targaryen NT-hash PtH -> ESC1 ESSOS-CA template ->
administrator UPN cert issued via HTTP fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The AnotherName SEQUENCE for UPN otherName carries a UTF8String wrapped
in [0] EXPLICIT — not a SEQUENCE. The previous Go encoding produced
`AnotherName { OID, [0] EXPLICIT SEQUENCE { UTF8String } }` which
Windows and certipy refused to parse — `Subject Alternative Name`
rendered as raw bytes ("No identities found in this certificate") and
PKINIT failed with KRB-ERR-GENERIC because the KDC saw no parseable UPN
identity in the cert.

Drop the upnValue struct and tag the string field as `utf8` directly so
the encoded value is `[0] EXPLICIT UTF8String` matching MS-ADTS and
every other CA's output.

Verified live: certs issued by certigo `req` against ESSOS-CA and
SEVENKINGDOMS-CA now show:
- openssl x509 -text:  `othername: UPN:user@domain` (was raw bytes)
- certipy auth:        `SAN UPN: 'user@domain'` (was "No identities found")
- PKINIT AS-REP:       success on sevenkingdoms.local (cersei.lannister
  → unPAC → NT hash) — used to error KRB-ERR-GENERIC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add end-to-end walkthroughs for the four canonical AD CS abuse paths
(ESC1 web enrollment + PKINIT, ESC8 NTLM relay, Shadow Credentials,
Golden Certificate) that certigo now ships, plus the matching common
flag table. Reflects that web enrollment now does NTLM-over-HTTP with
auto HTTP fallback, so operators don't need a working HTTPS endpoint
to run /certsrv/ enrollment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ajm4n ajm4n merged commit 2f4982c into main Apr 30, 2026
9 of 10 checks passed
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.

1 participant