feat(shadow): KB5014754 strong-mapping fixes for shadow-cred PFX#1
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
certigo shadow addnow passes PKINIT against KB5014754-strict KDCs (CVE-2022-26931 era).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)orKDC_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 ./...cleango test ./...— 146 passedcertigo shadow add --action add --target-dn 'CN=user,...' --out user.pfx --pfx-password shadowagainst a patched DC, thencertipy auth -pfx user.pfx -password shadow -domain corp.localshould now show bothSAN UPNandSecurity Extension SIDin the parsed identities🤖 Generated with Claude Code