`X509Chain.Build()` returns `true` for a leaf not signed by the pinned root on Android (`CustomRootTrust`)
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
On .NET for Android, `X509Chain.Build()` returns `true` for a leaf certificate that was **not**
signed by the certificate in `CustomTrustStore`, while `ChainStatus` simultaneously reports
`PartialChain`. The return value and the status contradict each other.
The same code on the same runtime build returns the correct `false` on Windows and Linux.
This silently bypasses certificate pinning: an app that pins a root and checks the return value of
`Build()` will accept a leaf signed by an unrelated CA.
### Reproduction Steps
Full self-contained repro: https://github.com/AlexNik4/ChainValidationRepro
1. Generate two different self-signed root CAs, `Root A` and `Root B`, with an **identical subject DN**.
2. Issue a leaf from `Root B`, including an Authority Key Identifier naming Root B's key.
3. Build the leaf with `TrustMode = CustomRootTrust` and `CustomTrustStore = { Root A }`.
4. Run on Android.
### Expected behavior
`Build()` returns `false`. Root A did not sign the leaf.
### Actual behavior
Root A is placed in the chain and marked error-free, despite `ChainStatus` reporting that the path
chains to no trust anchor.
### Regression?
Unknown.
### Known Workarounds
Manually loop through the chain and look for PartialChain errors
### Configuration
| | |
|---|---|
| Runtime | .NET 10.0.11 (identical build on all three platforms) |
| Affected | Android 15, API level 35, arm64 (Pixel 6) |
| Not affected | Windows 10.0.26100 x64; Linux x64 |
### Other information
Removing the AKI from the leaf makes the failure disappear.
Contributor guide
Research direction
Start with the self-contained ChainValidationRepro linked in the issue and run it on Android, comparing X509Chain.Build() with ChainStatus for the mismatched roots and AKI. Trace the Android certificate-chain path reached by X509Chain.Build(); done means Build() returns false when the leaf is signed by unrelated Root B, matching the reported PartialChain status without regressing Windows or Linux behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- cryptography, mobile-dev, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100